Bug 17049 – [scope] member methods not escape checked like free functions
Status
RESOLVED
Resolution
FIXED
Severity
normal
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2016-12-31T16:23:00Z
Last change time
2017-08-07T13:15:27Z
Keywords
safe
Assigned to
nobody
Creator
code
Comments
Comment #0 by code — 2016-12-31T16:23:37Z
cat > bug.d << CODE
version (all) // broken for class refs
class P;
else
alias P = int*; // works for int pointers
struct S
{
@safe P get() return scope;
}
P escape() @safe
{
S s;
P p = s.get();
return p;
}
CODE
----
dmd -c -o- bug -transition=safe -de
----
Should fail to compile and report `scope variable p may not be returned`.
Tested with v2.073.0-scope-e46873f (dmd-scope-2016-12-27 preview build).
Comment #1 by code — 2017-02-21T12:37:01Z
No longer works (not even for int*) with dmd-master-2017-01-04 and -dip1000.
Comment #2 by bugzilla — 2017-02-22T04:26:57Z
The reason no error is detected is because there isn't one. The declaration:
S s;
does not declare a pointer that points to the stack.
Comment #3 by code — 2017-02-24T20:55:10Z
Remember how we agreed on that the compiler shouldn't be too smart when inferring whether the return value could alias any of the arguments.
This is crucial to support ownership idioms such as unique, where the container could for example just wrap an int handle.
Use-after-free for handles is no different from dangling pointers, just as unsafe and able to corrupt memory.
struct S
{
float* ptr; // needs a pointer for the compiler to attach the lifetime of get's return value to S
@safe P get() return scope;
}
P escape() @safe
{
scope S s; // need to explicitly declare this as scope for the compiler to infer get's return value as scope
P p = s.get();
return p;
}
//////////
Here is a simpler example on why this is broken.
struct S
{
@safe S* get() return scope
{
return &this;
}
}
S* escape() @safe
{
S s;
auto ps = s.get();
return ps;
}
In `auto ps = s.get()` the compiler should conservatively assume that ps points to s, simply b/c the signature (w/ return scope) would allow to do so. Even if the return type is seemingly unrelated to the passed in scope arguments type conversions may be done by @trusted functions that are intransparent for the compiler.
Comment #4 by code — 2017-02-24T22:42:51Z
There is a difference from member functions to free function.
static @safe S* get2(return ref scope S _this)
{
return &_this;
}
struct S
{
@safe S* get1() return scope
{
return &this;
}
}
S* escape() @safe
{
S s;
auto ps1 = s.get1();
auto ps2 = s.get2();
return ps1; // works
// return ps2; // doesn't work
}
Comment #5 by code — 2017-02-24T22:44:12Z
(In reply to Martin Nowak from comment #4)
> return ps1; // works
silenty escapes !!!
> // return ps2; // doesn't work
correctly errors on escape !!!
> }
Comment #6 by code — 2017-02-24T22:47:07Z
And the same is supposed to work for foreign pointers?
static @safe float* get2(return ref scope S _this)
{
return convert(&_this);
}
@trusted float* convert(S* s) { return cast(float*)s; }
struct S
{
}
float* escape() @safe
{
S s;
auto pf = s.get2();
return pf; // works
}
----
Error: scope variable pf may not be returned
----
Comment #7 by code — 2017-02-24T22:50:22Z
And this is supposed to not work?
struct Handle { int a; }
static @safe Handle get2(return ref scope S _this)
{
return Handle(1);
}
struct S
{
}
Handle escape() @safe
{
S s;
auto h = s.get2();
return h; // works
}
Comment #8 by code — 2017-02-25T16:52:23Z
The Handle case would need to look like this, w/ a free function dmd correctly prevents escaping.
struct Handle
{
@safe:
int fd;
@disable this(this);
~this() {}
} // not-copyable
static @safe ref int get1(return ref scope Handle _this)
{
return _this.fd;
}
int* escape1() @safe
{
Handle h;
auto p = &h.get1();
return p;
}
static @safe int* get2(return ref scope Handle _this)
{
return &_this.fd;
}
int* escape2() @safe
{
Handle h;
auto p = h.get2();
return p;
}
----
/tmp/tmp.Rm7L5V5FW1/bug.d(17): Error: cannot take address of ref return of get1() in @safe function escape1 // this restriction migth get removed at some point
/tmp/tmp.Rm7L5V5FW1/bug.d(18): Error: scope variable p may not be returned
/tmp/tmp.Rm7L5V5FW1/bug.d(30): Error: scope variable p may not be returned
----
So the problematic bug is that member methods are not correctly checked.