From 906662d3149702e5197222e1778f1202c58895c5 Mon Sep 17 00:00:00 2001 From: Tim Schendekehl Date: Sat, 25 Jul 2026 14:50:09 +0200 Subject: [PATCH] Fix #23458 - Add pragma for C++ deleting destructors Currently deleting destructors for C++ are implemented in D by only running the normal destructors and not freeing memory. This can result in memory leaks if instances are deleted from C++. Implementing deleting destructors like in C++ would break existing D code, because it requires linking the C++ standard library. Instead this PR adds a pragma for enabling freeing memory in deleting destructors for classes. The pragma is inherited by subclasses, so it normally only needs to be used on root classes. For classes, which are not `extern(C++)`, or other declarations the pragma is ignored. In a future edition the pragma could be enabled by default. --- .../dmd.pragma-cpp_use_deleting_destructor.dd | 19 ++ compiler/include/dmd/aggregate.h | 9 + compiler/src/dmd/aggregate.d | 1 + compiler/src/dmd/clone.d | 89 +++++- compiler/src/dmd/dclass.d | 6 + compiler/src/dmd/dsymbolsem.d | 26 +- compiler/src/dmd/id.d | 5 + compiler/src/dmd/mangle/cpp.d | 19 ++ compiler/src/dmd/pragmasem.d | 50 ++++ compiler/test/runnable_cxx/del_dtor.d | 260 ++++++++++++++++++ .../runnable_cxx/extra-files/del_dtor.cpp | 62 +++++ .../test/runnable_cxx/extra-files/del_dtor.h | 34 +++ spec/pragma.dd | 30 ++ 13 files changed, 601 insertions(+), 9 deletions(-) create mode 100644 changelog/dmd.pragma-cpp_use_deleting_destructor.dd create mode 100644 compiler/test/runnable_cxx/del_dtor.d create mode 100644 compiler/test/runnable_cxx/extra-files/del_dtor.cpp create mode 100644 compiler/test/runnable_cxx/extra-files/del_dtor.h diff --git a/changelog/dmd.pragma-cpp_use_deleting_destructor.dd b/changelog/dmd.pragma-cpp_use_deleting_destructor.dd new file mode 100644 index 000000000000..0a81c2853bc8 --- /dev/null +++ b/changelog/dmd.pragma-cpp_use_deleting_destructor.dd @@ -0,0 +1,19 @@ +Add pragma for enabling deleting destructors for C++ classes + +Currently deleting destructors for C++ are implemented in D by only +running the normal destructors and not freeing memory. +This can result in memory leaks if instances are deleted from C++. + +The new pragma `cpp_use_deleting_destructor` allows to enable freeing +memory in deleting destructors for classes. The pragma is inherited by +subclasses, so it normally only needs to be used on root classes. + +```d +pragma(cpp_use_deleting_destructor, true) +extern(C++) class Class +{ + ~this() + { + } +} +``` diff --git a/compiler/include/dmd/aggregate.h b/compiler/include/dmd/aggregate.h index ef901c810901..0ff9d2daa205 100644 --- a/compiler/include/dmd/aggregate.h +++ b/compiler/include/dmd/aggregate.h @@ -112,6 +112,7 @@ class AggregateDeclaration : public ScopeDsymbol // (same as aggrDtor, except for C++ classes with virtual dtor on Windows) DtorDeclaration *tidtor; // aggregate destructor used in TypeInfo (must have extern(D) ABI) DtorDeclaration *fieldDtor; // function destructing (non-inherited) fields + DtorDeclaration *delDtor; // deleting destructor for C++ classes if target has twoDtorInVtable set Expression *getRTInfo; // pointer to GC info generated by object.RTInfo(this) Scope* rtInfoScope; // scope to be used when evaluating getRTInfo @@ -284,6 +285,14 @@ class ClassDeclaration : public AggregateDeclaration Baseok baseok() const; Baseok baseok(Baseok v); + // free memory in deleting destructor + d_bool cppUseDelDtor() const; + d_bool cppUseDelDtor(d_bool v); + + // cppUseDelDtor was set explicitly by pragma(cpp_use_deleting_destructor, ...) + d_bool cppUseDelDtorSet() const; + d_bool cppUseDelDtorSet(d_bool v); + int cppDtorVtblIndex; // slot reserved for the virtual destructor [extern(C++)] ObjcClassDeclaration objc; // Data for a class declaration that is needed for the Objective-C integration diff --git a/compiler/src/dmd/aggregate.d b/compiler/src/dmd/aggregate.d index b61b44a374d1..4acfafec493b 100644 --- a/compiler/src/dmd/aggregate.d +++ b/compiler/src/dmd/aggregate.d @@ -136,6 +136,7 @@ extern (C++) abstract class AggregateDeclaration : ScopeDsymbol /// (same as aggrDtor, except for C++ classes with virtual dtor on Windows) DtorDeclaration tidtor; /// aggregate destructor used in TypeInfo (must have extern(D) ABI) DtorDeclaration fieldDtor; /// function destructing (non-inherited) fields + DtorDeclaration delDtor; /// deleting destructor for C++ classes if target has twoDtorInVtable set Expression getRTInfo; /// pointer to GC info generated by object.RTInfo(this) Scope* rtInfoScope; /// scope to be used when evaluating getRTInfo diff --git a/compiler/src/dmd/clone.d b/compiler/src/dmd/clone.d index 1b7c3a4683fc..0b6fdca74ad8 100644 --- a/compiler/src/dmd/clone.d +++ b/compiler/src/dmd/clone.d @@ -1063,6 +1063,67 @@ void buildDtors(AggregateDeclaration ad, Scope* sc) break; } + ClassDeclaration cldec = ad.isClassDeclaration(); + if (cldec && !cldec.cppUseDelDtorSet && cldec.baseClass) + cldec.cppUseDelDtor = cldec.baseClass.cppUseDelDtor; + if (ad.aggrDtor && cldec && cldec.classKind == ClassKind.cpp && target.cpp.twoDtorInVtable) + { + if (!cldec.cppUseDelDtor) + { + // Use normal destructor for backward compatibility + ad.delDtor = ad.aggrDtor; + } + else + { + Expression e = null; + stc = STC.nogc; + { + stc = mergeFuncAttrs(stc, ad.aggrDtor); + if (stc & STC.disable) + { + e = null; + } + else + { + Expression ex = new ThisExp(loc); + ex = new DotVarExp(loc, ex, ad.aggrDtor, false); + CallExp ce = new CallExp(loc, ex); + ce.directcall = true; + e = Expression.combine(e, ce); + } + } + auto mCppNew = loadCoreStdcppNew(); + if (!mCppNew) + { + error(declLoc, "`core.stdcpp.new_` is required for C++ deleting destructors"); + } + else if (!mCppNew.search(Loc.initial, Id.__cpp_delete)) + { + error(declLoc, "`__cpp_delete` not found in `core.stdcpp.new_`, but is required for C++ deleting destructors"); + } + else + { + Expression id = new ScopeExp(loc, mCppNew); + id = new DotIdExp(loc, id, Id.__cpp_delete); + auto arguments = new Expressions(new CastExp(loc, new ThisExp(loc), Type.tvoidptr)); + CallExp ce = new CallExp(loc, id, arguments); + e = Expression.combine(e, ce); + } + auto dd = new DtorDeclaration(declLoc, Loc.initial, stc, Id.__delDtor); + dd.isGenerated = true; + dd.storage_class |= STC.inference; + dd.fbody = new ExpStatement(loc, e); + ad.members.push(dd); + dd.dsymbolSemantic(sc); + ad.delDtor = dd; + } + } + + // If this class has no explicit cpp destructor, but the base class + // has, then set cppDtorVtblIndex, so destructors for fields can be called. + if (cldec && cldec.cppDtorVtblIndex == -1 && cldec.baseClass && cldec.aggrDtor) + cldec.cppDtorVtblIndex = cldec.baseClass.cppDtorVtblIndex; + // Set/build `ad.dtor`. // On Windows, the dtor in the vtable is a shim with different signature. ad.dtor = (ad.aggrDtor && ad.aggrDtor._linkage == LINK.cpp && !target.cpp.twoDtorInVtable) @@ -1107,7 +1168,7 @@ private DtorDeclaration buildWindowsCppDtor(AggregateDeclaration ad, DtorDeclara // void* C::~C(int del) // { // this->~C(); - // // TODO: if (del) delete (char*)this; + // if (del) delete (char*)this; // return (void*) this; // } Parameter delparam = new Parameter(Loc.initial, STC.none, Type.tuns32, Identifier.idPool("del"), new IntegerExp(dtor.loc, 0, Type.tuns32), null, null); @@ -1118,12 +1179,38 @@ private DtorDeclaration buildWindowsCppDtor(AggregateDeclaration ad, DtorDeclara auto func = new DtorDeclaration(dtor.loc, dtor.loc, stc, Id.cppdtor); func.type = ftype; + Statement ifDeleteStatement; + if (cldec.cppUseDelDtor) + { + Expression e = null; + auto mCppNew = loadCoreStdcppNew(); + if (!mCppNew) + { + error(dtor.loc, "`core.stdcpp.new_` is required for C++ deleting destructors"); + } + else if (!mCppNew.search(Loc.initial, Id.__cpp_delete)) + { + error(dtor.loc, "`__cpp_delete` not found in `core.stdcpp.new_`, but is required for C++ deleting destructors"); + } + else + { + Expression id = new ScopeExp(dtor.loc, mCppNew); + id = new DotIdExp(dtor.loc, id, Id.__cpp_delete); + auto arguments = new Expressions(new CastExp(dtor.loc, new ThisExp(dtor.loc), Type.tvoidptr)); + CallExp ce = new CallExp(dtor.loc, id, arguments); + e = Expression.combine(e, ce); + } + ifDeleteStatement = new IfStatement(dtor.loc, null, new IdentifierExp(dtor.loc, Identifier.idPool("del")), new ExpStatement(dtor.loc, e), null, dtor.loc); + } + // Always generate the function with body, because it is not exported from DLLs. const loc = dtor.loc; auto stmts = Statements(); auto call = new CallExp(loc, dtor, null); call.directcall = true; stmts.push(new ExpStatement(loc, call)); + if (ifDeleteStatement) + stmts.push(ifDeleteStatement); stmts.push(new ReturnStatement(loc, new CastExp(loc, new ThisExp(loc), Type.tvoidptr))); func.fbody = new CompoundStatement(loc, stmts.move()); func.isGenerated = true; diff --git a/compiler/src/dmd/dclass.d b/compiler/src/dmd/dclass.d index c081f7b84dfc..d7c3d9114281 100644 --- a/compiler/src/dmd/dclass.d +++ b/compiler/src/dmd/dclass.d @@ -142,6 +142,12 @@ extern (C++) class ClassDeclaration : AggregateDeclaration /// set the progress of base classes resolving Baseok baseok; + + /// free memory in deleting destructor + bool cppUseDelDtor; + + /// cppUseDelDtor was set explicitly by pragma(cpp_use_deleting_destructor, ...) + bool cppUseDelDtorSet; } import dmd.common.bitfields : generateBitFields; diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index 027eb9f0181f..f79744c72f44 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -5986,11 +5986,6 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor buildDtors(cldec, sc2); - // If this class has no explicit cpp destructor, but the base class - // has, then set cppDtorVtblIndex, so destructors for fields can be called. - if (cldec.cppDtorVtblIndex == -1 && cldec.baseClass && cldec.dtor) - cldec.cppDtorVtblIndex = cldec.baseClass.cppDtorVtblIndex; - if (cldec.classKind == ClassKind.cpp && cldec.cppDtorVtblIndex != -1) { // now we've built the aggregate destructor, we'll make it virtual and assign it to the reserved vtable slot @@ -5999,9 +5994,10 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor if (target.cpp.twoDtorInVtable) { - // TODO: create a C++ compatible deleting destructor (call out to `operator delete`) - // for the moment, we'll call the non-deleting destructor and leak - cldec.vtbl[cldec.cppDtorVtblIndex + 1] = cldec.dtor; + // Assign the deleting destructor (call out to `operator delete`) + if (cldec.dtor != cldec.delDtor) + cldec.delDtor.vtblIndex = cldec.cppDtorVtblIndex + 1; + cldec.vtbl[cldec.cppDtorVtblIndex + 1] = cldec.delDtor; } } @@ -7564,6 +7560,20 @@ Module loadCoreStdcConfig() return loadModuleFromLibrary(core_stdc_config, pkgids, Id.config); } +/**************************** + * A Singleton that loads core.stdcpp.new_ + * Returns: + * Module of core.stdcpp.new_, null if couldn't find it + */ +Module loadCoreStdcppNew() +{ + __gshared Module core_stdcpp_new_; + auto pkgids = new Identifier[2]; + pkgids[0] = Id.core; + pkgids[1] = Id.stdcpp; + return loadModuleFromLibrary(core_stdcpp_new_, pkgids, Id.new_); +} + /********************************** * Load a Module from the library. * Params: diff --git a/compiler/src/dmd/id.d b/compiler/src/dmd/id.d index babc2df241dc..5f070be704de 100644 --- a/compiler/src/dmd/id.d +++ b/compiler/src/dmd/id.d @@ -75,6 +75,7 @@ immutable Msgtable[] msgtable = { "__xdtor", "__xdtor" }, { "__fieldDtor", "__fieldDtor" }, { "__aggrDtor", "__aggrDtor" }, + { "__delDtor", "__delDtor" }, { "cppdtor", "__cppdtor" }, { "ticppdtor", "__ticppdtor" }, { "postblit", "__postblit" }, @@ -139,6 +140,8 @@ immutable Msgtable[] msgtable = { "_body", "body" }, { "printf" }, { "scanf" }, + { "stdcpp" }, + { "new_" }, { "TypeInfo" }, { "TypeInfo_Class" }, @@ -309,6 +312,7 @@ immutable Msgtable[] msgtable = { "startaddress" }, { "crt_constructor" }, { "crt_destructor" }, + { "cpp_use_deleting_destructor" }, // For special functions { "tohash", "toHash" }, @@ -333,6 +337,7 @@ immutable Msgtable[] msgtable = { "_d_arrayappendcTX" }, { "_d_arraycatnTX" }, { "_d_assocarrayliteralTX" }, + { "__cpp_delete" }, // varargs implementation { "stdc" }, diff --git a/compiler/src/dmd/mangle/cpp.d b/compiler/src/dmd/mangle/cpp.d index c71c72d6f015..753a16df2781 100644 --- a/compiler/src/dmd/mangle/cpp.d +++ b/compiler/src/dmd/mangle/cpp.d @@ -129,6 +129,23 @@ bool isAggregateDtor(const Dsymbol sym) return dtor == ad.aggrDtor; } +/****************************** + * Determine if sym is a deleting destructor. + * Params: + * sym = Dsymbol + * Returns: + * true if sym is a deleting destructor + */ +bool isDeletingDtor(const Dsymbol sym) +{ + const dtor = sym.isDtorDeclaration(); + if (!dtor) + return false; + const ad = dtor.isMember(); + assert(ad); + return dtor == ad.delDtor; +} + /// Context used when processing pre-semantic AST private struct Context { @@ -1108,6 +1125,8 @@ private final class CppMangleVisitor : Visitor buf.writestring(ctor.isCpCtor ? "C2" : "C1"); else if (d.isAggregateDtor()) buf.writestring("D1"); + else if (d.isDeletingDtor()) + buf.writestring("D0"); else if (d.ident && d.ident == Id.opAssign) buf.writestring("aS"); else if (d.ident && d.ident == Id.opEquals) diff --git a/compiler/src/dmd/pragmasem.d b/compiler/src/dmd/pragmasem.d index 2fc3f1ec6c63..eb2acf6aea33 100644 --- a/compiler/src/dmd/pragmasem.d +++ b/compiler/src/dmd/pragmasem.d @@ -30,6 +30,7 @@ import dmd.globals; import dmd.location; import dmd.id; import dmd.statement; +import dmd.mtype; /** * Run semantic on `pragma` declaration. @@ -66,6 +67,16 @@ void pragmaDeclSemantic(PragmaDeclaration pd, Scope* sc) s.dsymbolSemantic(sc2); continue; } + if (pd.ident == Id.cpp_use_deleting_destructor) + { + if (pd.args && (*pd.args).length) + { + auto ie = (*pd.args)[0].isIntegerExp(); + s.setCppUseDelDtor(ie && ie.value); + } + s.dsymbolSemantic(sc2); + continue; + } s.dsymbolSemantic(sc2); } @@ -206,6 +217,22 @@ void pragmaDeclSemantic(PragmaDeclaration pd, Scope* sc) .error(pd.loc, "%s `%s` takes no argument", pd.kind, pd.toPrettyChars); return declarations(); } + else if (pd.ident == Id.cpp_use_deleting_destructor) + { + if (!pd.args || (*pd.args).length != 1) + { + .error(pd.loc, "%s `%s` one bool argument expected", pd.kind, pd.toPrettyChars); + } + else + { + auto ie = (*pd.args)[0].isIntegerExp(); + if (!ie || ie.type != Type.tbool) + { + .error(pd.loc, "%s `%s` one bool argument expected", pd.kind, pd.toPrettyChars); + } + } + return declarations(); + } else if (!global.params.ignoreUnsupportedPragmas) { error(pd.loc, "unrecognized `pragma(%s)`", pd.ident.toErrMsg()); @@ -463,6 +490,29 @@ private void setPragmaPrintf(Dsymbol s, bool printf) } } +/** + * Apply pragma cpp_use_deleting_destructor to ClassDeclarations under `s`, + * poking through attribute declarations such as `extern(C)` + * but not through aggregates or function bodies. + * + * Params: + * s = symbol to apply + * cppUseDelDtor = argument of pragma + */ +private void setCppUseDelDtor(Dsymbol s, bool cppUseDelDtor) +{ + if (auto cd = s.isClassDeclaration()) + { + cd.cppUseDelDtor = cppUseDelDtor; + cd.cppUseDelDtorSet = true; + } + + if (auto ad = s.isAttribDeclaration()) + { + ad.include(null).foreachDsymbol( (s) { setCppUseDelDtor(s, cppUseDelDtor); } ); + } +} + /*********************************************************** * Evaluate `pragma(startAddress, func)` and store the resolved symbol in `args` * diff --git a/compiler/test/runnable_cxx/del_dtor.d b/compiler/test/runnable_cxx/del_dtor.d new file mode 100644 index 000000000000..81176e4279c0 --- /dev/null +++ b/compiler/test/runnable_cxx/del_dtor.d @@ -0,0 +1,260 @@ +// EXTRA_CPP_SOURCES: del_dtor.cpp +// EXTRA_FILES: extra-files/del_dtor.h +// REQUIRED_ARGS: -extern-std=c++11 +// CXXFLAGS(osx linux freebsd openbsd netbsd dragonflybsd solaris): -std=c++11 + +import core.stdc.string; +import core.stdcpp.new_; + +extern(C++): + +extern __gshared int newCount; +extern __gshared int deleteCount; + +__gshared uint destructorCount; +struct DestructorCall +{ + const(char)[] name; + int value; +} +__gshared DestructorCall[10] destructorValues; + +void logDestructorCall(const(char)* name, int value) +{ + assert(destructorCount < destructorValues.length); + destructorValues[destructorCount] = DestructorCall(name[0 .. strlen(name)], value); + destructorCount++; +} + +extern(D) DestructorCall[] loggedDestructorValues() +{ + return destructorValues[0 .. destructorCount]; +} + +pragma(cpp_use_deleting_destructor, true) +class DBase +{ + int i; + ~this() + { + logDestructorCall(typeof(this).stringof.ptr, i); + } +} +void deleteDBaseFromCPP(DBase inst); +CppBase createCppBaseFromCPP(int i); + +pragma(cpp_use_deleting_destructor, true) +class CppBase +{ + int i; + ~this(); +} +void deleteCppBaseFromCPP(CppBase inst); + +struct StructWithDtor +{ + int i; + ~this() + { + logDestructorCall(typeof(this).stringof.ptr, i); + } +} + +class DDerived1 : CppBase +{ + ~this() + { + logDestructorCall(typeof(this).stringof.ptr, i); + } +} + +class DDerived2 : CppBase +{ +} + +class DDerived3 : CppBase +{ + ~this() + { + logDestructorCall(typeof(this).stringof.ptr, i); + } + StructWithDtor x1; +} + +class DDerived2a : DDerived2 +{ + ~this() + { + logDestructorCall(typeof(this).stringof.ptr, i); + } +} + +class DDerived3a : DDerived3 +{ + ~this() + { + logDestructorCall(typeof(this).stringof.ptr, i); + } +} + +class DDerived4 : CppBase +{ + StructWithDtor x1; +} + +class DDerived5 : CppBase +{ + StructWithDtor x1; + StructWithDtor x2; + StructWithDtor x3; +} + +pragma(cpp_use_deleting_destructor, false) +class DDerived6 : CppBase +{ + ~this() + { + logDestructorCall(typeof(this).stringof.ptr, i); + } +} + +pragma(cpp_use_deleting_destructor, true) // The pragma ignored for structs +struct Struct1 +{ + int i; + ~this() + { + logDestructorCall(typeof(this).stringof.ptr, i); + } +} +void deleteStruct1FromCPP(Struct1* inst); + +void main() +{ + { + auto inst = cpp_new!DBase; + inst.i = 1; + deleteDBaseFromCPP(inst); + assert(newCount == 1); + assert(deleteCount == 1); + assert(loggedDestructorValues() == [DestructorCall("DBase", 1)]); + } + { + newCount = deleteCount = destructorCount = 0; + { + scope inst = new DBase; + inst.i = 2; + } + assert(newCount == 0); + assert(deleteCount == 0); + assert(loggedDestructorValues() == [DestructorCall("DBase", 2)]); + } + { + newCount = deleteCount = destructorCount = 0; + auto inst = cpp_new!CppBase; + inst.i = 3; + deleteCppBaseFromCPP(inst); + assert(newCount == 1); + assert(deleteCount == 1); + assert(loggedDestructorValues() == [DestructorCall("CppBase", 3)]); + } + { + // Test the other direction of creating an object in C++ and deleting + // it in D. This is not affected by the pragma. + // This only works for classes without custom operator delete, + // see https://github.com/dlang/dmd/issues/23509 + newCount = deleteCount = destructorCount = 0; + auto inst = createCppBaseFromCPP(4); + cpp_delete(inst); + assert(newCount == 1); + assert(deleteCount == 1); + assert(loggedDestructorValues() == [DestructorCall("CppBase", 4)]); + } + { + newCount = deleteCount = destructorCount = 0; + auto inst = cpp_new!DDerived1; + inst.i = 100; + deleteCppBaseFromCPP(inst); + assert(newCount == 1); + assert(deleteCount == 1); + assert(loggedDestructorValues() == [DestructorCall("DDerived1", 100), DestructorCall("CppBase", 100)]); + } + { + newCount = deleteCount = destructorCount = 0; + auto inst = cpp_new!DDerived2; + inst.i = 200; + deleteCppBaseFromCPP(inst); + assert(newCount == 1); + assert(deleteCount == 1); + assert(loggedDestructorValues() == [DestructorCall("CppBase", 200)]); + } + { + newCount = deleteCount = destructorCount = 0; + auto inst = cpp_new!DDerived3; + inst.i = 300; + inst.x1.i = 301; + deleteCppBaseFromCPP(inst); + assert(newCount == 1); + assert(deleteCount == 1); + assert(loggedDestructorValues() == [DestructorCall("DDerived3", 300), DestructorCall("StructWithDtor", 301), DestructorCall("CppBase", 300)]); + } + { + newCount = deleteCount = destructorCount = 0; + auto inst = cpp_new!DDerived2a; + inst.i = 210; + deleteCppBaseFromCPP(inst); + assert(newCount == 1); + assert(deleteCount == 1); + assert(loggedDestructorValues() == [DestructorCall("DDerived2a", 210), DestructorCall("CppBase", 210)]); + } + { + newCount = deleteCount = destructorCount = 0; + auto inst = cpp_new!DDerived3a; + inst.i = 310; + inst.x1.i = 311; + deleteCppBaseFromCPP(inst); + assert(newCount == 1); + assert(deleteCount == 1); + assert(loggedDestructorValues() == [DestructorCall("DDerived3a", 310), DestructorCall("DDerived3", 310), DestructorCall("StructWithDtor", 311), DestructorCall("CppBase", 310)]); + } + { + newCount = deleteCount = destructorCount = 0; + auto inst = cpp_new!DDerived4; + inst.i = 400; + inst.x1.i = 401; + deleteCppBaseFromCPP(inst); + assert(newCount == 1); + assert(deleteCount == 1); + assert(loggedDestructorValues() == [DestructorCall("StructWithDtor", 401), DestructorCall("CppBase", 400)]); + } + { + newCount = deleteCount = destructorCount = 0; + auto inst = cpp_new!DDerived5; + inst.i = 500; + inst.x1.i = 501; + inst.x2.i = 502; + inst.x3.i = 503; + deleteCppBaseFromCPP(inst); + assert(newCount == 1); + assert(deleteCount == 1); + assert(loggedDestructorValues() == [DestructorCall("StructWithDtor", 503), DestructorCall("StructWithDtor", 502), DestructorCall("StructWithDtor", 501), DestructorCall("CppBase", 500)]); + } + { + newCount = deleteCount = destructorCount = 0; + auto inst = cpp_new!DDerived6; + inst.i = 600; + deleteCppBaseFromCPP(inst); + assert(newCount == 1); + assert(deleteCount == 0); + assert(loggedDestructorValues() == [DestructorCall("DDerived6", 600), DestructorCall("CppBase", 600)]); + } + { + newCount = deleteCount = destructorCount = 0; + auto inst = cpp_new!Struct1; + inst.i = 700; + deleteStruct1FromCPP(inst); + assert(newCount == 1); + assert(deleteCount == 1); + assert(loggedDestructorValues() == [DestructorCall("Struct1", 700)]); + } +} diff --git a/compiler/test/runnable_cxx/extra-files/del_dtor.cpp b/compiler/test/runnable_cxx/extra-files/del_dtor.cpp new file mode 100644 index 000000000000..df3cae23de69 --- /dev/null +++ b/compiler/test/runnable_cxx/extra-files/del_dtor.cpp @@ -0,0 +1,62 @@ +#include "del_dtor.h" +#include +#include + +int newCount = 0; +int deleteCount = 0; + +void *operator new(std::size_t size) +{ + newCount++; + void *p = std::malloc(size ? size : 1); + if (!p) + throw std::bad_alloc(); + return p; +} + +void operator delete(void *p) noexcept +{ + if (p) + deleteCount++; + std::free(p); +} +void operator delete(void *p, std::size_t) noexcept +{ + if (p) + deleteCount++; + std::free(p); +} + +void deleteDBaseFromCPP(DBase *inst) +{ + delete inst; +} + +CppBase::~CppBase() +{ + logDestructorCall("CppBase", i); +} +void deleteCppBaseFromCPP(CppBase *inst) +{ + delete inst; +} +CppBase *createCppBaseFromCPP(int i) +{ + CppBase *inst = new CppBase; + inst->i = i; + return inst; +} + +void deleteStruct1FromCPP(Struct1 *inst) +{ + delete inst; +} + +CppBase2::~CppBase2() +{ + logDestructorCall("CppBase2", i); +} +void deleteCppBase2FromCPP(CppBase2 *inst) +{ + delete inst; +} diff --git a/compiler/test/runnable_cxx/extra-files/del_dtor.h b/compiler/test/runnable_cxx/extra-files/del_dtor.h new file mode 100644 index 000000000000..912762ceb677 --- /dev/null +++ b/compiler/test/runnable_cxx/extra-files/del_dtor.h @@ -0,0 +1,34 @@ + +void logDestructorCall(const char *name, int value); + +class DBase +{ +public: + int i; + virtual ~DBase(); +}; +void deleteDBaseFromCPP(DBase *inst); + +class CppBase +{ +public: + int i; + virtual ~CppBase(); +}; +void deleteCppBaseFromCPP(CppBase *inst); +CppBase *createCppBaseFromCPP(int i); + +struct Struct1 +{ + int i; + ~Struct1(); +}; +void deleteStruct1FromCPP(Struct1 *inst); + +class CppBase2 +{ +public: + int i; + virtual ~CppBase2(); +}; +void deleteCppBase2FromCPP(CppBase2 *inst); diff --git a/spec/pragma.dd b/spec/pragma.dd index b8c7e3bf04f7..9f382b042dc6 100644 --- a/spec/pragma.dd +++ b/spec/pragma.dd @@ -76,6 +76,7 @@ $(UL $(LI $(LINK2 #printf, pragma printf)) $(LI $(LINK2 #scanf, pragma scanf)) $(LI $(LINK2 #startaddress, pragma startaddress)) + $(LI $(LINK2 #cpp_use_deleting_destructor, pragma cpp_use_deleting_destructor)) ) $(IMPLEMENTATION_DEFINED An implementation may ignore these pragmas.) @@ -464,6 +465,35 @@ pragma(startaddress, foo); ) + +$(H3 $(LNAME2 cpp_use_deleting_destructor, $(D pragma cpp_use_deleting_destructor))) + + $(P There must be one argument and it must be a literal `true` or `false`.) + + $(IMPLEMENTATION_DEFINED Enables or disables deleting destructors for + `extern(C++)` classes. + By default deleting destructor for `extern(C++)` classes defined in + D run the destructors, but don't free the memory. This can result in + memory leaks if instances are deleted from C++. This pragma enables + creating deleting destructors, which free the memory. + This requires linking to the C++ standard library. + The pragma is inherited by subclasses, so it normally only needs to be + used on root classes. + For classes, which are not `extern(C++)`, or other declarations + the pragma is ignored. + +----------------- +pragma(cpp_use_deleting_destructor, true) +extern(C++) class Class +{ + ~this() + { + } +} +----------------- + ) + + $(H2 $(LNAME2 vendor_specific_pragmas, Vendor Specific Pragmas)) $(P Vendor specific pragma $(I Identifier)s can be defined if they