// dmd -dip1000 @safe: struct Correct { private int* ptr; int* get() return { return ptr; } } struct Faulty(T) { private T* ptr; T* get() return { return ptr; } } struct Workaround(T) { private T* ptr; T* get() return { return *&ptr; // workaround is the *& } } // fails to compile (as it should) unittest { int* outlive; Correct c; outlive = c.get(); // error } // compiles (but shouldn't) unittest { int* outlive; Faulty!int f; outlive = f.get(); // should be error } // fails to compile (as it should) unittest { int* outlive; Workaround!int w; outlive = w.get(); // error }
Can we actually return a problematic pointer this way? As it is, the test case only returns `null`s. It seems that without attribute inference, DMD assumes `get` might return a problematic pointer. But with attribute inference, DMD looks at the actual expression that is being returned.
Of course we can. What ptr points to and how its value came to be is irrelevant for this test case, thus omitted.
(In reply to Stanislav Blinov from comment #2) > Of course we can. What ptr points to and how its value came to be is > irrelevant for this test case, thus omitted. Please show an example. When I tried returning a problematic pointer, DMD showed the same error as in the `Correct` case.
(In reply to ag0aep6g from comment #3) > Please show an example. When I tried returning a problematic pointer, DMD > showed the same error as in the `Correct` case. ? malloc it in a constructor, free it in a destructor, disable copy and assignment
(In reply to Stanislav Blinov from comment #4) > ? malloc it in a constructor, free it in a destructor, disable copy and > assignment Makes sense. When I think about DIP 1000, I tend to think of pointers to the stack. But yeah, it's also supposed to enable malloc/free, isn't it. For future readers, a full example: ---- import core.stdc.stdlib: free, malloc; struct Faulty { private int* ptr; this(int v) @trusted { ptr = cast(int*) malloc(int.sizeof); *ptr = v; } ~this() @trusted { if (ptr !is null) free(ptr); } @disable this(this); @disable void opAssign(Faulty); int* get1() @safe return { return ptr; } int* get2()() @safe return { return ptr; } } void main() @safe { int* outlive; { auto f1 = Faulty(42); outlive = f1.get1(); /* error */ outlive = f1.get2(); /* should be error */ } /* `outlive` is invalid from here on */ } ----
The method is being inferred as `return scope`. Oddly enough, that doesn't show up when printing with `pragma(msg, ...)`. With this information, we can construct a test case that shows memory corruption even without malloc/free and @trusted: ---- struct Faulty { private int x; private int* unused; int* get1() @safe return scope { return &x; } } int* f(int x) @safe { auto f = Faulty(x); return f.get1(); /* Returns a pointer to the stack. */ } void main() @safe { int* p1 = f(42); int* p2 = f(13); import std.stdio; writeln(*p1); /* Prints "13". Whoopsie. */ } ----
*** Issue 22528 has been marked as a duplicate of this issue. ***
There's multiple things going on, so I'm going to limit this issue to "scope inference turns return-ref into return-scope", the issue that the compiler confuses `return scope` and `return ref` internally should be covered by issue 22108 and issue 22541.
@dkorpel updated dlang/dmd pull request #13693 "Make consistent use of `STC.returnScope`" fixing this issue: - Fix issue 20881, 22790 - make ref-return-scope consistent https://github.com/dlang/dmd/pull/13693
dlang/dmd pull request #13693 "Fix issue 20881, 22790 - make ref-return-scope consistent" was merged into master: - fce2d6529550931dc7ed35f904bbc7a6ac0cd8f0 by dkorpel: Fix issue 20881, 22790 - make ref-return-scope consistent https://github.com/dlang/dmd/pull/13693