digitalmars.D.learn - pointer escaping return scope bug?
- Nick Treleaven (22/22) Nov 19 2022 Hi,
- ag0aep6g (29/53) Nov 19 2022 Let me rewrite `fp` as a static method. It's easier (for me) to
- Nick Treleaven (2/22) Nov 19 2022 OK, so how do I make `lf` implicitly scope?
- ag0aep6g (9/10) Nov 19 2022 By explicit/implicit I just meant this:
- Dukc (4/29) Nov 19 2022 Have the `int*` inside it to point to a local, or assign another
- Nick Treleaven (19/24) Dec 15 2022 Thanks, this works:
- Nick Treleaven (3/4) Dec 15 2022 Whoops, that can't be @trusted unless I `assert(p)`.
- Paul Backus (15/37) Nov 19 2022 I think this is intended behavior, because you *do* get an error
- ShadoLight (13/54) Nov 25 2022 I follow your rationale, but for the life of me I cannot see how
- ShadoLight (14/37) Nov 25 2022 Perhaps because the local scope is not pushed as a separate
- Nick Treleaven (39/48) Nov 27 2022 Yes, the actual code does have a destructor. It's analogous to
- Paul Backus (5/12) Nov 25 2022 You're right, my terminology here is sloppy. I'm really talking
- Tejas (3/16) Nov 25 2022 Time to use separation logic
Hi, The following seems like a bug to me (reduced code, FILE* changed to int*): ```d safe: struct LockedFile { private int* fps; auto fp() return scope => fps; } void main() { int* p; { auto lf = LockedFile(new int); p = lf.fp; } assert(p != null); // address escaped } ``` There's no error with -dip1000. I'll file this unless I overlooked something.
Nov 19 2022
On 19.11.22 15:07, Nick Treleaven wrote:Hi, The following seems like a bug to me (reduced code, FILE* changed to int*): ```d safe: struct LockedFile { private int* fps; auto fp() return scope => fps; } void main() { int* p; { auto lf = LockedFile(new int); p = lf.fp; } assert(p != null); // address escaped } ``` There's no error with -dip1000. I'll file this unless I overlooked something.Let me rewrite `fp` as a static method. It's easier (for me) to understand what's going on that way: ---- static int* fp(return scope ref LockedFile that) { return that.fps; } ---- That's essentially just a function that returns its pointer parameter. So the program boils down to this: ---- safe: int* fp(return scope int* p) { return p; } void main() { int* p; { auto lf = new int; p = fp(lf); } assert(p != null); // address escaped } ---- Which is fine, as far as I can tell. `lf` is not `scope`. And when you pass it through `fp`, the result is still not `scope`. So escaping it is allowed. You do get an error when you make `lf` `scope` (explicitly or implicitly). So everything seems to be in order.
Nov 19 2022
On Saturday, 19 November 2022 at 14:52:23 UTC, ag0aep6g wrote:That's essentially just a function that returns its pointer parameter. So the program boils down to this: ---- safe: int* fp(return scope int* p) { return p; } void main() { int* p; { auto lf = new int; p = fp(lf); } assert(p != null); // address escaped } ---- Which is fine, as far as I can tell. `lf` is not `scope`. And when you pass it through `fp`, the result is still not `scope`. So escaping it is allowed. You do get an error when you make `lf` `scope` (explicitly or implicitly). So everything seems to be in order.OK, so how do I make `lf` implicitly scope?
Nov 19 2022
On Saturday, 19 November 2022 at 15:02:54 UTC, Nick Treleaven wrote:OK, so how do I make `lf` implicitly scope?By explicit/implicit I just meant this: ---- scope explicit = new int; int x; auto implicit = &x; ---- That's probably not helping with whatever you want to accomplish.
Nov 19 2022
On Saturday, 19 November 2022 at 15:02:54 UTC, Nick Treleaven wrote:On Saturday, 19 November 2022 at 14:52:23 UTC, ag0aep6g wrote:Have the `int*` inside it to point to a local, or assign another `scope int*` to it.That's essentially just a function that returns its pointer parameter. So the program boils down to this: ```D safe: int* fp(return scope int* p) { return p; } void main() { int* p; { auto lf = new int; p = fp(lf); } assert(p != null); // address escaped } ``` Which is fine, as far as I can tell. `lf` is not `scope`. And when you pass it through `fp`, the result is still not `scope`. So escaping it is allowed. You do get an error when you make `lf` `scope` (explicitly or implicitly). So everything seems to be in order.OK, so how do I make `lf` implicitly scope?
Nov 19 2022
On Saturday, 19 November 2022 at 15:24:33 UTC, Dukc wrote:On Saturday, 19 November 2022 at 15:02:54 UTC, Nick Treleaven wrote:Thanks, this works: ```d safe: struct S { int* p; int[0] v; // dummy storage auto f() return trusted => p ? p : v.ptr; } void main() { int* p; { S s = S(new int); p = s.f; // error } } ```OK, so how do I make `lf` implicitly scope?Have the `int*` inside it to point to a local, or assign another `scope int*` to it.
Dec 15 2022
On Thursday, 15 December 2022 at 20:02:38 UTC, Nick Treleaven wrote:auto f() return trusted => p ? p : v.ptr;Whoops, that can't be trusted unless I `assert(p)`.
Dec 15 2022
On Saturday, 19 November 2022 at 14:07:59 UTC, Nick Treleaven wrote:Hi, The following seems like a bug to me (reduced code, FILE* changed to int*): ```d safe: struct LockedFile { private int* fps; auto fp() return scope => fps; } void main() { int* p; { auto lf = LockedFile(new int); p = lf.fp; } assert(p != null); // address escaped } ``` There's no error with -dip1000. I'll file this unless I overlooked something.I think this is intended behavior, because you *do* get an error if you replace `new int` with a pointer to a stack variable; e.g., int local; auto lf = LockedFile(&local); The `return scope` qualifier on the method does *not* mean "the return value of this method is `scope`". It means "this method may return one of this object's pointers, but does not allow them to escape anywhere else." In other words, it lets the compiler determine that the return value of `lf.fp` has *the same* lifetime as `lf` itself. Since, in your example, `lf` has global lifetime, the compiler deduces that `lf.fp` also has global lifetime, and therefore there is nothing wrong with assigning it to `p`.
Nov 19 2022
On Saturday, 19 November 2022 at 15:00:16 UTC, Paul Backus wrote:On Saturday, 19 November 2022 at 14:07:59 UTC, Nick Treleaven wrote:I follow your rationale, but for the life of me I cannot see how `lf` _"has global lifetime"_. Looks to me like `lf` is a value instance of the `LockedFile` struct (so on the stack) in a local scope inside main. I fully agree that the above code is not problematic, but isn't that because `p` is declared outside this local scope, and the allocation that happens inside the local scope (in the `lf` constructor) is on the heap, so the allocation (now assigned to `p`) survives the end of the local scope (and the end of the life of `lf`) since it is `p` that has global lifetime? I don't grok how `lf` can survive the local scope. Or am I missing something?Hi, The following seems like a bug to me (reduced code, FILE* changed to int*): ```d safe: struct LockedFile { private int* fps; auto fp() return scope => fps; } void main() { int* p; { auto lf = LockedFile(new int); p = lf.fp; } assert(p != null); // address escaped } ``` There's no error with -dip1000. I'll file this unless I overlooked something.I think this is intended behavior, because you *do* get an error if you replace `new int` with a pointer to a stack variable; e.g., int local; auto lf = LockedFile(&local); The `return scope` qualifier on the method does *not* mean "the return value of this method is `scope`". It means "this method may return one of this object's pointers, but does not allow them to escape anywhere else." In other words, it lets the compiler determine that the return value of `lf.fp` has *the same* lifetime as `lf` itself. Since, in your example, `lf` has global lifetime, the compiler deduces that `lf.fp` also has global lifetime, and therefore there is nothing wrong with assigning it to `p`.
Nov 25 2022
On Friday, 25 November 2022 at 14:07:28 UTC, ShadoLight wrote:[snip]On Saturday, 19 November 2022 at 14:07:59 UTC, Nick Treleaven```d safe: struct LockedFile { private int* fps; auto fp() return scope => fps; } void main() { int* p; { auto lf = LockedFile(new int); p = lf.fp; } assert(p != null); // address escaped } ```I don't grok how `lf` can survive the local scope. Or am I missing something?Perhaps because the local scope is not pushed as a separate (anonymous) function on the stack... if true then, yes, then `lf` will indeed have the same physical lifetime as main (and `p`)...? On the other hand, if you add a destructor to `LockedFile`, it will be invoked at the end of the local scope, not the end of main. I find it a bit confusing what the term "lifetime" should pertain to in the case of variables declared inside a local scope inside a function - destructor invocation or physical existence of the variable on the stack? But this has no bearing on the heap allocation and the lifetime of `p` in the example.
Nov 25 2022
On Friday, 25 November 2022 at 15:03:57 UTC, ShadoLight wrote:Yes, the actual code does have a destructor. It's analogous to this: ```d safe: struct S { private int* fps; auto fp() return scope => fps; this(int) { fps = new int; } disable this(); disable void opAssign(S); ~this() trusted // how do we make this safe? { import core.memory; GC.free(fps); } } void main() { int* p; { auto lf = S(5); p = lf.fp; } assert(p != null); // address escaped } ``` That compiles with -dip1000. D doesn't seem to have a way to prevent the memory outliving the struct. Just to note there is another problem when the struct is destroyed explicitly whilst the `fp` result is still live. But that could be solved by making `object.destroy` and `std.algorithm.move` be restricted to system for certain structs like this one (either opt-in or some other mechanism). The first problem doesn't seem to have a solution.I don't grok how `lf` can survive the local scope. Or am I missing something?Perhaps because the local scope is not pushed as a separate (anonymous) function on the stack... if true then, yes, then `lf` will indeed have the same physical lifetime as main (and `p`)...? On the other hand, if you add a destructor to `LockedFile`, it will be invoked at the end of the local scope, not the end of main.
Nov 27 2022
On Friday, 25 November 2022 at 14:07:28 UTC, ShadoLight wrote:On Saturday, 19 November 2022 at 15:00:16 UTC, Paul Backus wrote:You're right, my terminology here is sloppy. I'm really talking about the memory pointed to by `lf`, not `lf` itself, so I should really say that `lf` *points to memory* with global lifetime (or perhaps "`*lf` has global lifetime").Since, in your example, `lf` has global lifetime, the compiler deduces that `lf.fp` also has global lifetime, and therefore there is nothing wrong with assigning it to `p`.I follow your rationale, but for the life of me I cannot see how `lf` _"has global lifetime"_.
Nov 25 2022
On Friday, 25 November 2022 at 17:45:57 UTC, Paul Backus wrote:On Friday, 25 November 2022 at 14:07:28 UTC, ShadoLight wrote:Time to use separation logicOn Saturday, 19 November 2022 at 15:00:16 UTC, Paul Backus wrote:You're right, my terminology here is sloppy. I'm really talking about the memory pointed to by `lf`, not `lf` itself, so I should really say that `lf` *points to memory* with global lifetime (or perhaps "`*lf` has global lifetime").Since, in your example, `lf` has global lifetime, the compiler deduces that `lf.fp` also has global lifetime, and therefore there is nothing wrong with assigning it to `p`.I follow your rationale, but for the life of me I cannot see how `lf` _"has global lifetime"_.
Nov 25 2022