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