Jump to page: 1 2
Thread overview
[Issue 23445] Can leak scope variable through delegate context
Nov 01, 2022
Walter Bright
Nov 01, 2022
timon.gehr@gmx.ch
Nov 01, 2022
Tejas_Garhewal
Nov 01, 2022
Adam D. Ruppe
Nov 01, 2022
Walter Bright
Nov 01, 2022
Walter Bright
Nov 01, 2022
timon.gehr@gmx.ch
Nov 01, 2022
Walter Bright
Nov 02, 2022
Dlang Bot
Dec 17, 2022
Iain Buclaw
November 01, 2022
https://issues.dlang.org/show_bug.cgi?id=23445

Walter Bright <bugzilla@digitalmars.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|NEW                         |RESOLVED
                 CC|                            |bugzilla@digitalmars.com
         Resolution|---                         |INVALID

--- Comment #1 from Walter Bright <bugzilla@digitalmars.com> ---
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);`

--
November 01, 2022
https://issues.dlang.org/show_bug.cgi?id=23445

timon.gehr@gmx.ch changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|RESOLVED                    |REOPENED
         Resolution|INVALID                     |---

--- Comment #2 from timon.gehr@gmx.ch ---
My initial example literally escapes a reference to a dead stack variable in `@safe` code. Of course this is a problem.

--
November 01, 2022
https://issues.dlang.org/show_bug.cgi?id=23445

Tejas_Garhewal <scienticman@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |scienticman@gmail.com

--- Comment #3 from Tejas_Garhewal <scienticman@gmail.com> ---
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?

--
November 01, 2022
https://issues.dlang.org/show_bug.cgi?id=23445

Adam D. Ruppe <destructionator@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |destructionator@gmail.com

--- Comment #4 from Adam D. Ruppe <destructionator@gmail.com> ---
likely same root cause as https://issues.dlang.org/show_bug.cgi?id=23440

--
November 01, 2022
https://issues.dlang.org/show_bug.cgi?id=23445

--- Comment #5 from Walter Bright <bugzilla@digitalmars.com> ---
(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.

--
November 01, 2022
https://issues.dlang.org/show_bug.cgi?id=23445

Walter Bright <bugzilla@digitalmars.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           See Also|                            |https://issues.dlang.org/sh
                   |                            |ow_bug.cgi?id=23440

--
November 01, 2022
https://issues.dlang.org/show_bug.cgi?id=23445

--- Comment #6 from timon.gehr@gmx.ch ---
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.

--
November 01, 2022
https://issues.dlang.org/show_bug.cgi?id=23445

--- Comment #7 from Walter Bright <bugzilla@digitalmars.com> ---
(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.

--
November 02, 2022
https://issues.dlang.org/show_bug.cgi?id=23445

Dlang Bot <dlang-bot@dlang.rocks> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Keywords|                            |pull

--- Comment #8 from Dlang Bot <dlang-bot@dlang.rocks> ---
@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

--
December 17, 2022
https://issues.dlang.org/show_bug.cgi?id=23445

Iain Buclaw <ibuclaw@gdcproject.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Priority|P1                          |P3

--
« First   ‹ Prev
1 2