Bug 22864 – [REG 2.067] Throwing in array literal leads to destructor being called on unconstructed data

Status
NEW
Severity
regression
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2022-03-09T16:29:16Z
Last change time
2024-12-13T19:21:24Z
Keywords
industry, pull, safe, wrong-code
Assigned to
No Owner
Creator
Mathias LANG
Moved to GitHub: dmd#18089 →

Comments

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
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/18089 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB