Bug 21762 – object.destroy may silently fail depending on whether a member function is a template

Status
NEW
Severity
regression
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2021-03-24T22:14:54Z
Last change time
2024-12-13T19:15:25Z
Assigned to
No Owner
Creator
thomas.bockman
Moved to GitHub: dmd#18028 →

Comments

Comment #0 by thomas.bockman — 2021-03-24T22:14:54Z
Since 2.092, the program below calls B's destructor, but not C's: /////////////////////////////////////// module app; import core.stdc.stdlib : free, malloc; import std.stdio : writeln; struct A(T) { T* address; this(const(bool) create) { if(create) this.address = cast(T*) malloc(T.sizeof); } ~this() { if(address !is null) { destroy(*address); free(address); address = null; writeln("A destroyed"); } } } void main() { static struct B { @property ref A!B next()(); ~this() { writeln("B destroyed"); } } static struct C { @property ref A!C next(); ~this() { writeln("C destroyed"); } } A!B b = true; A!C c = true; } /////////////////////////////////////// Apparently this is hard to get right: /////////////////////////////////////// Up to 2.067.1: Success with output: ----- C destroyed A destroyed B destroyed A destroyed ----- 2.068.2 to 2.083.1: Success with output: ----- A destroyed B destroyed A destroyed ----- 2.084.1 to 2.091.1: Failure with output similar to: ----- Error: union `object.destroy!(true, C).destroy.UntypedInit` has forward references onlineapp.d(14): Error: template instance `object.destroy!(true, C)` error instantiating onlineapp.d(29): instantiated from here: `A!(C)` ----- Since 2.092.1: Success with output: ----- A destroyed B destroyed A destroyed -----
Comment #1 by moonlightsentinel — 2021-03-24T22:44:19Z
Reduced example: ---------------------------------------------- import core.stdc.stdio : puts; struct A(T) { T* address; ~this() { destroy(*address); } } void main() { static struct B { A!B next()(); ~this() { puts("B destroyed"); } } static struct C { A!C next(); ~this() { puts("C destroyed"); } } B b; destroy(b); C c; destroy(c); } ---------------------------------------------- Introduced by https://github.com/dlang/druntime/pull/1312. But it's probably a DMD issue because the PR introduced __traits(hasMember, S, "__xdtor") which is true for B but false for C
Comment #2 by moonlightsentinel — 2021-03-24T22:59:52Z
This is probably caused by the forward reference of the destructors. The non-templated next() instantiates A!C before the semantic of C is done and before __xdtor is generated. A!C's destructor then instantiates destroy which omits the dtor call because it's not yet available.
Comment #3 by moonlightsentinel — 2021-03-24T23:04:47Z
Test case without dependencies: struct A(T) { ~this() { static assert(__traits(hasMember, T, "__xdtor")); } } void main() { static struct B { A!B next(); ~this() {} } static struct C { A!C next()(); ~this() {} } C().next(); } B fails while C compiles just fine.
Comment #4 by moonlightsentinel — 2021-03-24T23:09:10Z
Interestingly, the test case works if B and C are declared outside of main.
Comment #5 by moonlightsentinel — 2021-03-24T23:38:16Z
(In reply to moonlightsentinel from comment #4) > Interestingly, the test case works if B and C are declared outside of main. Seems like this was enabled by https://github.com/dlang/dmd/pull/5075
Comment #6 by moonlightsentinel — 2021-03-24T23:39:25Z
(In reply to moonlightsentinel from comment #5) > (In reply to moonlightsentinel from comment #4) > > Interestingly, the test case works if B and C are declared outside of main. > > Seems like this was enabled by https://github.com/dlang/dmd/pull/5075 Wrong link: https://github.com/dlang/dmd/pull/5075/commits/8e4676303a688ce3a034b38508b5e5b8c7bfa7e0
Comment #7 by thomas.bockman — 2021-07-01T01:37:34Z
I ran into this again in a different context. One workaround for the compiler bug is to change the definition of object.destroy to do the work of __xdtor itself when the type has a destructor, but no __xdtor is found: ////////////////////////////////////////////////////////////////// import core.internal.lifetime : emplaceInitializer; import core.internal.traits : hasElaborateDestructor; void destroy(bool initialize = true, T)(ref T obj) if(is(T == struct)) { static if(initialize) { destroy!false(obj); emplaceInitializer(obj); } else static if(hasElaborateDestructor!T) { static if(__traits(hasMember, T, `__xdtor`) && __traits(isSame, T, __traits(parent, obj.__xdtor))) obj.__xdtor(); else { static if(__traits(hasMember, T, `__dtor`) && __traits(isSame, T, __traits(parent, obj.__dtor))) obj.__dtor(); foreach_reverse(ref objField; obj.tupleof) destroy!false(objField); } } } ////////////////////////////////////////////////////////////////// Should I submit a druntime pull request for this?
Comment #8 by robert.schadek — 2024-12-13T19:15:25Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/18028 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB