With DMD v2.101.0-beta.1, the following code is accepted:
int global;
int* foo(scope int* p)@safe{
auto dg=(return scope int* q)@safe return scope{
return p;
};
return dg(&global);
}
auto qux()@safe{
int x=10;
int* p=&x;
int* q=foo(p);
return q;
}
void main()@safe{
import std.stdio;
auto p=qux();
version(SMASH_STACK) writeln("smashing stack");
writeln(*p);
}
Comment #1 by bugzilla — 2022-11-01T00:33:26Z
Reducing to:
int global;
int* foo(scope int* p)@safe{
auto dg=(return scope int* q)@safe return scope{
return p;
};
return dg(&global); <== returns a pointer to p
}
This is not a problem, because the compiler recognizes the escape of p and allocates p in a closure on the heap. This heap allocate is done whenever a nested function is turned into a delegate.
For nested functions not turned into a delegate, p is placed in a heap allocated closure, and an error is produced for `return dg(&global);`
Comment #2 by timon.gehr — 2022-11-01T08:33:09Z
My initial example literally escapes a reference to a dead stack variable in `@safe` code. Of course this is a problem.
Comment #3 by scienticman — 2022-11-01T10:56:14Z
The following code (It's timon's except it calls a couple more intermediate functions):
```d
import std.stdio:writeln;
int global;
int* foo(scope int* p)@safe{
auto dg=(return scope int* q)@safe return scope{
return p;
};
return dg(&global);
}
auto qux()@safe{
int x=10;
int* p=&x;
int* q=foo(p);
return q;
}
void func(int[6] a) @safe{
int[6] b = a.dup;
int[6] c = a.dup;
writeln(a, b, c);
}
void main()@safe{
import std.stdio;
auto p=qux();
version(SMASH_STACK) writeln("smashing stack");
func([1,2,3,4,5,6]);
func([0,8,2,0,9,7]);
writeln(*p);
func([0,8,2,9,6,1]);
writeln(*p);
}
```
When executing via normal dmd, the output is:
smashing stack
[1, 2, 3, 4, 5, 6][1, 2, 3, 4, 5, 6][1, 2, 3, 4, 5, 6]
[0, 8, 2, 0, 9, 7][0, 8, 2, 0, 9, 7][0, 8, 2, 0, 9, 7]
-960565922
[0, 8, 2, 9, 6, 1][0, 8, 2, 9, 6, 1][0, 8, 2, 9, 6, 1]
-960565848 <----- Notice the value changing if read after invoking another function
When executed via dmd-beta, the output is:
smashing stack
[1, 2, 3, 4, 5, 6][1, 2, 3, 4, 5, 6][1, 2, 3, 4, 5, 6]
[0, 8, 2, 0, 9, 7][0, 8, 2, 0, 9, 7][0, 8, 2, 0, 9, 7]
-1630675618
[0, 8, 2, 9, 6, 1][0, 8, 2, 9, 6, 1][0, 8, 2, 9, 6, 1]
-1630675544 <-------- Ditto
When executed via ldc, the output is:
smashing stack
[1, 2, 3, 4, 5, 6][1, 2, 3, 4, 5, 6][1, 2, 3, 4, 5, 6]
[0, 8, 2, 0, 9, 7][0, 8, 2, 0, 9, 7][0, 8, 2, 0, 9, 7]
21942
[0, 8, 2, 9, 6, 1][0, 8, 2, 9, 6, 1][0, 8, 2, 9, 6, 1]
21942 <------- Here it doesn't
ldc-beta output:
smashing stack
[1, 2, 3, 4, 5, 6][1, 2, 3, 4, 5, 6][1, 2, 3, 4, 5, 6]
[0, 8, 2, 0, 9, 7][0, 8, 2, 0, 9, 7][0, 8, 2, 0, 9, 7]
22061
[0, 8, 2, 9, 6, 1][0, 8, 2, 9, 6, 1][0, 8, 2, 9, 6, 1]
22061 <------- Neither over here
Surely this is not expected behaviour?
Comment #4 by destructionator — 2022-11-01T14:53:18Z
(In reply to timon.gehr from comment #2)
> My initial example literally escapes a reference to a dead stack variable in
> `@safe` code. Of course this is a problem.
It is not a stack variable, though. It is allocated on the heap.
Comment #6 by timon.gehr — 2022-11-01T18:58:13Z
The delegate context is allocated on the heap. There is a `scope int* p` in that context, and this pointer points to the stack variable `x`.
`qux` uses `foo` to escape a pointer to the local stack variable `x`. The delegate context is not even being referenced anymore when the UB happens. `foo` is just a device that allows `scope`-laundering an arbitrary pointer. `scope` pointer in, non-`scope` pointer out.
Comment #7 by bugzilla — 2022-11-01T20:41:08Z
(In reply to timon.gehr from comment #6)
> The delegate context is allocated on the heap. There is a `scope int* p` in
> that context, and this pointer points to the stack variable `x`.
>
> `qux` uses `foo` to escape a pointer to the local stack variable `x`. The
> delegate context is not even being referenced anymore when the UB happens.
> `foo` is just a device that allows `scope`-laundering an arbitrary pointer.
> `scope` pointer in, non-`scope` pointer out.
You're right. I was mistaken.
Comment #8 by dlang-bot — 2022-11-02T06:30:44Z
@WalterBright created dlang/dmd pull request #14610 "fix Issue 23445 - Can leak scope variable through delegate context" fixing this issue:
- fix Issue 23445 - Can leak scope variable through delegate context
https://github.com/dlang/dmd/pull/14610
Comment #9 by robert.schadek — 2024-12-13T19:25:19Z