Bug 24208 – [DIP1000] Scope pointer can escape via non-scope parameter of pure nested function

Status
RESOLVED
Resolution
FIXED
Severity
normal
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2023-10-29T21:21:06Z
Last change time
2023-10-30T23:04:42Z
Keywords
accepts-invalid, pull, safe
Assigned to
No Owner
Creator
Paul Backus
See also
https://issues.dlang.org/show_bug.cgi?id=24212, https://issues.dlang.org/show_bug.cgi?id=24213

Comments

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
My mistake, this rule is documented, here: https://dlang.org/spec/function.html#pure-scope-inference
Comment #6 by dlang-bot — 2023-10-30T20:04:24Z
@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