Comment #0 by snarwin+bugzilla — 2023-10-29T21:21:06Z
As of DMD 2.105.2, the following invalid program compiles with -preview=dip1000 and runs without errors:
---
void main() @safe
{
int* escaped;
void escape(int* p) @safe
{
escaped = p;
}
void borrow(scope int* param) @safe
{
escape(param);
}
int n;
borrow(&n);
assert(escaped == &n);
}
---
This program is invalid because, in @safe code, it assigns the address of the variable `n` to the variable `escaped`, which has a longer lifetime than `n`.
The expression `escape(param)` should cause a compile-time error, because it assigns the value of the scope parameter `param` to the non-scope parameter `p`.
Comment #1 by snarwin+bugzilla — 2023-10-29T23:01:57Z
Actually, it turns out the second function is not necessary:
---
void main() @safe
{
int* escaped;
void escape(int* p) @safe
{
escaped = p;
}
int n;
escape(&n);
assert(escaped == &n);
}
---
So, I guess the problem is either that the compiler is incorrectly inferring `p` as `return scope`, or it's *correctly* inferring `p` as `return scope` but failing to notice at the calls site that the "return value" has a longer lifetime than `n`.
If it's the second case, the error should be the same one produced by this program:
---
void main() @safe
{
int* escaped;
static void escape(ref int* ret, return scope int* p) @safe
{
ret = p;
}
int n;
escape(escaped, &n);
// Error: address of variable `n` assigned to `escaped`
// with longer lifetime
assert(escaped == &n);
}
---
Comment #2 by snarwin+bugzilla — 2023-10-29T23:14:08Z
...or maybe the problem is that it's being incorrectly inferred as `scope` instead of `return scope`.
Another example:
---
void main() @safe
{
int* escaped;
void escape1(int* p) @safe
{
escaped = p;
}
void escape2(scope int* p) @safe
{
escaped = p;
}
void escape3(return scope int* p) @safe
{
escaped = p;
}
int n;
escape1(&n); // no error
escape2(&n); // error
escape3(&n); // error
}
---
It's hard to tell what attributes the compiler is inferring here, but the observed behavior is consistent with the hypothesis that `scope int* p` is being inferred for `escape1`, and `return scope int* p` is being inferred for both `escape2` and `escape3`.
Comment #3 by snarwin+bugzilla — 2023-10-29T23:29:26Z
Disregard previous example, I read the errors wrong. They occur in the function bodies, not at the call site. The parameter of `escape` is not allowed to escape at all if it is `scope`, even `return scope`.
Comment #4 by snarwin+bugzilla — 2023-10-30T04:38:52Z
The bug is caused by an undocumented special-case language rule that allows scope pointers to be assigned to non-scope parameters of pure functions in some cases. This special case is implemented in the DMD frontend by the function TypeFunction.parameterStorageClass.
In comment #1's example, `escape` is inferred to be weakly pure. As a result, TypeFunction.parameterStorageClass allows the parameter `p` to accept a scope pointer as an argument, and the error that would normally be issued by checkParamArgumentEscape is suppressed.
Comment #5 by snarwin+bugzilla — 2023-10-30T12:30:33Z
@pbackus created dlang/dmd pull request #15756 "Fix 24208, 24212, 24213 - `scope` escape via `pure` + nested context" fixing this issue:
- Fix 24208 - Scope pointer can escape via non-scope parameter of pure nested function
https://github.com/dlang/dmd/pull/15756
Comment #7 by dlang-bot — 2023-10-30T23:04:42Z
dlang/dmd pull request #15756 "Fix 24208, 24212, 24213 - `scope` escape via `pure` + nested context" was merged into master:
- 9feaebe40d2105e538230eb2e3f909b9a8512ffc by Paul Backus:
Fix 24208 - Scope pointer can escape via non-scope parameter of pure nested function
https://github.com/dlang/dmd/pull/15756