D issues are now tracked on GitHub. This Bugzilla instance remains as a read-only archive.
Issue 19721 - Cannot take address of scope local variable even with dip1000 if a member variable is a delegate
Summary: Cannot take address of scope local variable even with dip1000 if a member var...
Status: RESOLVED INVALID
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: All All
: P1 normal
Assignee: No Owner
URL:
Keywords: rejects-valid, safe
Depends on:
Blocks:
 
Reported: 2019-03-06 11:06 UTC by Atila Neves
Modified: 2020-03-04 08:01 UTC (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Atila Neves 2019-03-06 11:06:05 UTC
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 ag0aep6g 2020-02-09 21:38:10 UTC
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 Walter Bright 2020-03-04 05:14:47 UTC
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 Walter Bright 2020-03-04 08:01:30 UTC
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.