Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions compiler/src/dmd/dsymbolsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -4502,10 +4502,20 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor
if (auto cldec = ad.isClassDeclaration())
{
assert (cldec.cppDtorVtblIndex == -1); // double-call check already by dd.type
if (cldec.baseClass && cldec.baseClass.cppDtorVtblIndex != -1)
// Walk up the base chain: an intermediate class may have no explicit
// dtor (cppDtorVtblIndex == -1) yet still inherit a dtor vtbl slot.
// https://github.com/dlang/dmd/issues/22709
int inheritedDtorVtblIndex = -1;
for (auto base = cldec.baseClass; base; base = base.baseClass)
if (base.cppDtorVtblIndex != -1)
{
inheritedDtorVtblIndex = base.cppDtorVtblIndex;
break;
}
if (inheritedDtorVtblIndex != -1)
{
// override the base virtual
cldec.cppDtorVtblIndex = cldec.baseClass.cppDtorVtblIndex;
cldec.cppDtorVtblIndex = inheritedDtorVtblIndex;
}
else if (!dd.isFinal())
{
Expand Down
15 changes: 15 additions & 0 deletions compiler/test/compilable/test22709.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// https://github.com/dlang/dmd/issues/22709
// extern(C++) destructor in base class should not be flagged as hidden

extern(C++):
class A
{
~this();
}
class B : A
{
}
class C : B
{
~this();
}
34 changes: 34 additions & 0 deletions compiler/test/runnable_cxx/cpp_dtor.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// https://github.com/dlang/dmd/issues/22709
// Virtual dtor dispatch through an intermediate class with no explicit dtor.
// Before the fix, C's vtbl had a stale entry for A's dtor at slot 0, causing
// virtual dtor dispatch through A* to call A's dtor instead of C's.
// EXTRA_CPP_SOURCES: cpp_dtor.cpp

extern(C) __gshared int aDestroyed;
extern(C) __gshared int cDestroyed;

extern(C++) void runCPPTests();

extern(C++):

class A
{
~this() { aDestroyed = 1; }
}

class B : A
{
}

class C : B
{
~this() { cDestroyed = 1; }
}

// D-side factory: C++ calls this to get a C object typed as A*
A makeC() { return new C; }

extern(D) void main()
{
runCPPTests();
}
33 changes: 33 additions & 0 deletions compiler/test/runnable_cxx/extra-files/cpp_dtor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// https://github.com/dlang/dmd/issues/22709
// C++ side: verify virtual dtor dispatch calls C's dtor (not A's) when
// destroying a C object through an A*.
#include <assert.h>

extern "C" int aDestroyed;
extern "C" int cDestroyed;

// Forward declaration matching D's extern(C++) class A
class A {
public:
virtual ~A();
};

// D-side factory
extern "C++" A* makeC();

void runCPPTests()
{
A* obj = makeC();

// Invoke the virtual destructor without freeing memory.
// obj->~A() dispatches virtually (calls C's dtor) on all ABIs,
// and does NOT call operator delete, so D-allocated memory is safe.
aDestroyed = 0;
cDestroyed = 0;
obj->~A();

// C's destructor must be dispatched, not A's
assert(cDestroyed);
// A's destructor must be chained from C's aggregate dtor
assert(aDestroyed);
}
Loading