Comment #0 by pro.mathias.lang — 2022-03-09T16:29:16Z
The following code leads to `abort` being triggered.
This happens because the array literal causes an instance to be allocated, which is later called by the GC, even though the instance contains garbage data (printing the member `hash` field will give a non-zero value).
```
import core.stdc.stdlib;
public S* deserializeFull ()
{
version (all)
return &[ getS() ][0]; // This causes a bug
else
{
auto val = getS();
return &[ val ][0]; // This works because the previous line throws
}
}
S getS () { throw new Exception("socket error"); }
struct S
{
~this ()
{
abort();
}
ubyte hash;
}
void foo ()
{
try
{
auto v = deserializeFull();
assert(0, "Exception not thrown?");
}
catch (Exception exc)
{
assert(exc.msg == "socket error");
}
}
void main ()
{
foo();
import core.memory;
GC.collect(); // Abort triggered from here
}
```
This is an old, but IMO serious, regression, that caused memory corruption in our `@safe` code because our `@trusted` wrapper was freeing pointers which were junk.
```
Up to 2.066.0: Success and no output
2.067.1 to 2.071.2: Failure with output: --- killed by signal 6
Since 2.072.2: Failure with output: Error: program killed by signal 6
```
Marking as `wrong-code` but it's a frontend bug (seen in LDC as well).
Comment #1 by razvan.nitu1305 — 2022-03-23T10:58:48Z
This is a gluelayer problem (common for all 3 backends). I haven't looked at what gdc and ldc do, but in dmd the gluelayer first inserts a call to _d_arrayliteral (which allocates the array with the gc) and then it calls getS(). Look ma! I even got the code to prove it: https://github.com/dlang/dmd/blob/master/src/dmd/e2ir.d#L4834. I assume that this is what gdc and ldc do also, so it's not a frontend bug.
The solution could be to first store the initializers on the stack and memcpy them after we allocate the array (essentially, the else branch of the verion in the original report).
Comment #2 by ibuclaw — 2022-03-23T13:04:02Z
Doesn't affect gdc-11
This is the codegen:
---
struct S[] ret;
struct S slot[1];
void* mem;
ret.length = 1;
slot[0] = getS (); [return slot optimization]
mem = _d_arrayliteralTX (&_D25TypeInfo_AS10issue228641S6__initZ, 1);
__builtin_memcpy (mem, &slot, 1);
ret.ptr = mem;
return ret.ptr;
---
The abort occurs if the array allocation and getS() calls are reversed.
---
struct S[] ret;
struct S slot[1];
void* mem;
ret.length = 1;
mem = _d_arrayliteralTX (&_D25TypeInfo_AS10issue228641S6__initZ, 1);
slot[0] = getS (); [return slot optimization]
__builtin_memcpy (mem, &slot, 1);
ret.ptr = mem;
return ret.ptr;
---
So with ldc (and maybe dmd), it's an issue with what order arguments are evaluated in. As at a high level, the lowered code would look like:
---
memcpy (_d_arrayliteralTX(), getS(), 1);
---
I guess it would be enforced LTR evaluation of C function calls.
Comment #3 by dlang-bot — 2022-04-05T10:29:35Z
@RazvanN7 created dlang/dmd pull request #13951 "Fix Issue 22864 - [REG 2.067] Throwing in array literal leads to destructor being called on unconstructed data" fixing this issue:
- Fix Issue 22864 - [REG 2.067] Throwing in array literal leads to destructor being called on unconstructed data
https://github.com/dlang/dmd/pull/13951
Comment #4 by robert.schadek — 2024-12-13T19:21:24Z