Bug 17461 – Bad codegen: compiler emit's call to destructor for uninitialised temporary
Status
RESOLVED
Resolution
WORKSFORME
Severity
blocker
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
x86_64
OS
Windows
Creation time
2017-06-02T06:49:49Z
Last change time
2020-03-21T03:56:40Z
Keywords
industry, wrong-code
Assigned to
No Owner
Creator
Manu
Comments
Comment #0 by turkeyman — 2017-06-02T06:49:49Z
DMD32 D Compiler v2.073.0-master-e29f8e7
x86_64 (Win64), standard debug build, no optimisations
This is as simple as I could get it:
----------------------------------------------------
// this function generates bad code; the `?:` introduces some weird locals
void t()
{
A(u1 ? u1 : u2);
}
//------ context ------
B u1() { return B(); }
__gshared B u2;
struct A
{
void *p;
alias get this;
void* get() pure nothrow @nogc @trusted { return p; }
this(void* s)
{
p = s;
}
}
struct B
{
alias a this;
A a;
this(void* s)
{
a = A(s);
}
~this()
{
if (p)
*cast(int*)p = 10; // <- crash if p is uninitialised!
}
}
int main()
{
u2 = B(cast(void*)0x12345678);
t();
return 0;
}
-----------------------------------------------
The disassembly for the t() function which emits the bad code.
Symbol names from debuginfo are visible:
void t()
push rbp
mov rbp,rsp
sub rsp,38h
push rbx
push rsi
push rdi
push r12
push r13
push r14
push r15
{
A(u1 ? u1 : u2);
mov qword ptr [rbp-28h],0
lea rax,[rbp-28h]
mov qword ptr [rbp-20h],rax
; from here we call u1() to get the value to test in the `?:`...
; note, the result is stored in '__tmpfordtor1804'
lea rcx,[__tmpfordtor1804]
sub rsp,20h
call dplug.dplug.u1 (07FFB076611F0h)
add rsp,20h
lea rcx,[__tmpfordtor1804]
sub rsp,20h
call dplug.dplug.A.get (07FFB07661000h)
add rsp,20h
; test if u1() was null (it is!) and jump to the 'else' case
test rax,rax
je dplug.dplug.t+71h (07FFB07661291h) ; does branch!
; if u1() was not null, call u1() again to get the value for the 'if' case
; the result is stored in `__tmpfordtor1805`, which is _INITIALISED HERE_
lea rcx,[__tmpfordtor1805]
sub rsp,20h
call dplug.dplug.u1 (07FFB076611F0h)
add rsp,20h
lea rcx,[__tmpfordtor1805]
sub rsp,20h
call dplug.dplug.A.get (07FFB07661000h)
add rsp,20h
mov rdx,rax
jmp dplug.dplug.t+88h (07FFB076612A8h)
; this is the 'else' branch, which loads u2
; note; we SKIPPED INITIALISATION of `__tmpfordtor1805`
lea rcx,[dplug.dplug.u2 (07FFB07725660h)]
sub rsp,20h
call dplug.dplug.A.get (07FFB07661000h)
add rsp,20h
mov rdx,rax
mov rcx,qword ptr [rbp-20h]
sub rsp,20h
; here we construct A() from the result of the `?:` statement
; note; we didn't actually do anything with A, so there's nothing here
call dplug.dplug.A.this (07FFB07661060h)
add rsp,20h
mov rax,qword ptr [rax]
mov qword ptr [rbp-8],rax
; from here we destruct the temporaries...
; call a fragment of code that destruct's '__tmpfordtor1805'
; note; this is NOT guarded, and '__tmpfordtor1805' was never initialised above!
call dplug.dplug.t+0A7h (07FFB076612C7h)
jmp dplug.dplug.t+0B9h (07FFB076612D9h)
; destruct '__tmpfordtor1805' sub-function thing
07FFB076612C7h:
lea rcx,[__tmpfordtor1805]
sub rsp,20h
call dplug.dplug.B.~this (07FFB076610C0h)
add rsp,20h
ret
; call a fragment of code that destruct's '__tmpfordtor1804'
07FFB076612D9h:
call dplug.dplug.t+0C0h (07FFB076612E0h)
jmp dplug.dplug.t+0D2h (07FFB076612F2h)
; destruct '__tmpfordtor1804' sub-function thing
07FFB076612E0h:
lea rcx,[__tmpfordtor1804]
sub rsp,20h
call dplug.dplug.B.~this (07FFB076610C0h)
add rsp,20h
ret
07FFB076612F2h:
}
pop r15
pop r14
pop r13
pop r12
pop rdi
pop rsi
pop rbx
mov rsp,rbp
pop rbp
ret
I hope I've made that disassembly easy enough to follow.
The problem is, in the `a() ? a() : b` expression, the first call to a() and the second call to a() have different result temporaries.
The second call only happens if the first call to a() evaluates 'true', which in this test, it doesn't! This means that the second temporary is never initialised.
When the function concludes, it calls destructor for both temporaries, even though the second one was never initiailised, and destructing the uninitialised object lead to me weird behaviours and crashes.
Comment #1 by turkeyman — 2017-06-02T07:17:48Z
For clarity; the high-level flow of the disassembly is:
void t()
{
B __tmpfordtor1804 = u1();
B __tmpfordtor1805 = void; // uninitialised
if (__tmpfordtor1804)
{
__tmpfordtor1805 = u1(); // initialised here
A(__tmpfordtor1805);
}
else
{
A(u2);
}
// __tmpfordtor1805 destructs, may not have been initialised!
__tmpfordtor1805.~this();
// __tmpfordtor1804 destructs, no problem
__tmpfordtor1804.~this();
}
Comment #2 by uplink.coder — 2017-06-02T19:01:11Z
Manu this code is amazing.
with -O -inline it crashes in the dwarf-emission.
Comment #3 by uplink.coder — 2017-06-02T19:09:46Z
Please check if the bug is still present in 2.075 ~master.
I cannot reproduce it.
Comment #4 by ag0aep6g — 2017-06-02T19:24:52Z
(In reply to uplink.coder from comment #2)
> with -O -inline it crashes in the dwarf-emission.
For that crash the code can be reduced to this:
----
void t()
{
auto a = A(false ? B().p : null);
}
struct A
{
void* p;
}
struct B
{
void* p;
~this() {}
}
----
`dmd -c -O test.d` crashes with "Internal error: ddmd/backend/dwarfeh.c 205".
Comment #5 by ag0aep6g — 2017-06-02T19:52:29Z
(In reply to uplink.coder from comment #3)
> Please check if the bug is still present in 2.075 ~master.
> I cannot reproduce it.
Here's a reduction of the original code, tweaked to make an assert fail:
----
void t()
{
auto a = A(B().p ? B() : B());
}
struct A
{
int p;
}
struct B
{
int p = 42;
alias p this;
~this()
{
import std.conv: text;
assert(p == 42, text(p)); /* fails; prints "1234567890" */
}
}
void main()
{
stomp();
t();
}
void stomp() { int[5] stomper = 1234567890; }
----
Tested with dmd v2.075.0-devel-7326893 on Linux.
Comment #6 by b2.temp — 2019-11-24T10:10:59Z
pass since 2.084. I'll open a new issue for the ICE with -O