Thread overview
[Issue 21762] object.destroy may silently fail depending on whether a member function is a template
March 24, 2021
https://issues.dlang.org/show_bug.cgi?id=21762

moonlightsentinel@disroot.org changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |moonlightsentinel@disroot.o
                   |                            |rg
           Hardware|x86_64                      |All
                 OS|Linux                       |All
           Severity|normal                      |regression

--- Comment #1 from moonlightsentinel@disroot.org ---
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

--
March 24, 2021
https://issues.dlang.org/show_bug.cgi?id=21762

--- Comment #2 from moonlightsentinel@disroot.org ---
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.

--
March 24, 2021
https://issues.dlang.org/show_bug.cgi?id=21762

--- Comment #3 from moonlightsentinel@disroot.org ---
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.

--
March 24, 2021
https://issues.dlang.org/show_bug.cgi?id=21762

--- Comment #4 from moonlightsentinel@disroot.org ---
Interestingly, the test case works if B and C are declared outside of main.

--
March 24, 2021
https://issues.dlang.org/show_bug.cgi?id=21762

--- Comment #5 from moonlightsentinel@disroot.org ---
(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

--
March 24, 2021
https://issues.dlang.org/show_bug.cgi?id=21762

--- Comment #6 from moonlightsentinel@disroot.org ---
(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

--
July 01, 2021
https://issues.dlang.org/show_bug.cgi?id=21762

--- Comment #7 from thomas.bockman@gmail.com ---
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?

--