Jump to page: 1 2
Thread overview
pointer escaping return scope bug?
Nov 19, 2022
Nick Treleaven
Nov 19, 2022
ag0aep6g
Nov 19, 2022
Nick Treleaven
Nov 19, 2022
ag0aep6g
Nov 19, 2022
Dukc
Dec 15, 2022
Nick Treleaven
Dec 15, 2022
Nick Treleaven
Nov 19, 2022
Paul Backus
Nov 25, 2022
ShadoLight
Nov 25, 2022
ShadoLight
Nov 27, 2022
Nick Treleaven
Nov 25, 2022
Paul Backus
Nov 26, 2022
Tejas
November 19, 2022

Hi,
The following seems like a bug to me (reduced code, FILE* changed to int*):

@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.

November 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.
November 19, 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*):

@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.

November 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?
November 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.
November 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:

>

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?

Have the int* inside it to point to a local, or assign another scope int* to it.

November 25, 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:

>

Hi,
The following seems like a bug to me (reduced code, FILE* changed to int*):

@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.

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?

November 25, 2022

On Friday, 25 November 2022 at 14:07:28 UTC, ShadoLight wrote:

> >

On Saturday, 19 November 2022 at 14:07:59 UTC, Nick Treleaven

>
@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
}

[snip]

>

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.

November 25, 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:

>

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".

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").

November 26, 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:

>

On Saturday, 19 November 2022 at 15:00:16 UTC, Paul Backus wrote:

>

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".

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").

Time to use separation logic

😈

« First   ‹ Prev
1 2