Bug 19721 – Cannot take address of scope local variable even with dip1000 if a member variable is a delegate
Status
RESOLVED
Resolution
INVALID
Severity
normal
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2019-03-06T11:06:05Z
Last change time
2020-03-04T08:01:30Z
Keywords
rejects-valid, safe
Assigned to
No Owner
Creator
Atila Neves
Comments
Comment #0 by atila.neves — 2019-03-06T11:06:05Z
This code doesn't compile even with -dip1000 on dmd 2.085.0:
-----------------------------------
void main() @safe {
scope s = Struct();
func(&s);
}
private struct Struct {
void delegate(int) dg;
}
void func(scope Struct* clientData) @safe nothrow {
}
-----------------------------------
It compiles if the `dg` member variable is removed or replaced with, say, an int.
Comment #1 by ag0aep6g — 2020-02-09T21:38:10Z
As far as I understand, it's correct that the code is rejected. `scope` only provides one level of protection. That means, you can't return a `scope` pointer, but you can dereference it, make a copy of the pointee, and return that.
In the example, `func` is allowed to return `*clientData`, regardless of what happens anywhere else in the code. And since `s` is `scope`, `s.dg` might rely on references to the stack. So to avoid a leak, you can't be allowed to call `func` on `s`.
In code:
----
Struct g;
void main() @safe {
main2();
() { int[10] stompy = 13; } ();
g.dg(0); /* Prints "13". We've got memory corruption. */
}
void main2() @safe {
int stack;
scope s = Struct();
s.dg = (int x) @safe { import std.stdio; writeln(stack); };
() @trusted { func(&s); } (); /* Let's pretend this works in @safe. */
}
private struct Struct {
void delegate(int) @safe dg;
}
void func(scope Struct* clientData) @safe nothrow {
g = *clientData;
}
----
(In reply to Atila Neves from comment #0)
> It compiles if the `dg` member variable is removed or replaced with, say, an
> int.
An int doesn't have any indirections, unlike a delegate.
Comment #2 by bugzilla — 2020-03-04T05:14:47Z
It also fails to compile if dg is defined as `int* dg;`:
---------------
void main() @safe {
scope s = Struct();
func(&s);
}
private struct Struct { int* dg; }
void func(scope Struct* clientData) @safe nothrow { }
-----------------------
In fact, it has nothing to do with structs, as this fails to compile the same way:
-----
void main() @safe {
scope int* s = null;
func(&s);
}
void func(scope int** clientData) @safe nothrow { }
------
Comment #3 by bugzilla — 2020-03-04T08:01:30Z
Here's what's happening. `scope int* s;` declares `s` as a pointer that must not be allowed to escape `main()`. The `func(&s);` passes the address of `s` to `func`. `func` declares its parameter as `scope int**`. This ensures that the address of `s` does not escape, but says nothing about `s`'s pointer value, which must not be allowed to escape. I.e. the value of `s` is not protected from escaping `func`, so the call causes a compile error.
If `s` is simply declared as an `int`, the `scope` annotation for `s` is meaningless, as there is no pointer value to protect, and it compiles successfully.
----
Generally, rewriting perplexing examples as simple pointers tends to make what is happening much easier to determine. A delegate is regarded as a pointer. A struct that contains pointer values is itself regarded as a pointer. Hence the simplification of the example.