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
19 changes: 19 additions & 0 deletions changelog/dmd.pragma-cpp_use_deleting_destructor.dd
Original file line number Diff line number Diff line change
@@ -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()
{
}
}
```
9 changes: 9 additions & 0 deletions compiler/include/dmd/aggregate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dmd/aggregate.d
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
89 changes: 88 additions & 1 deletion compiler/src/dmd/clone.d
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dmd/dclass.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
26 changes: 18 additions & 8 deletions compiler/src/dmd/dsymbolsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dmd/id.d
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ immutable Msgtable[] msgtable =
{ "__xdtor", "__xdtor" },
{ "__fieldDtor", "__fieldDtor" },
{ "__aggrDtor", "__aggrDtor" },
{ "__delDtor", "__delDtor" },
{ "cppdtor", "__cppdtor" },
{ "ticppdtor", "__ticppdtor" },
{ "postblit", "__postblit" },
Expand Down Expand Up @@ -139,6 +140,8 @@ immutable Msgtable[] msgtable =
{ "_body", "body" },
{ "printf" },
{ "scanf" },
{ "stdcpp" },
{ "new_" },

{ "TypeInfo" },
{ "TypeInfo_Class" },
Expand Down Expand Up @@ -309,6 +312,7 @@ immutable Msgtable[] msgtable =
{ "startaddress" },
{ "crt_constructor" },
{ "crt_destructor" },
{ "cpp_use_deleting_destructor" },

// For special functions
{ "tohash", "toHash" },
Expand All @@ -333,6 +337,7 @@ immutable Msgtable[] msgtable =
{ "_d_arrayappendcTX" },
{ "_d_arraycatnTX" },
{ "_d_assocarrayliteralTX" },
{ "__cpp_delete" },

// varargs implementation
{ "stdc" },
Expand Down
19 changes: 19 additions & 0 deletions compiler/src/dmd/mangle/cpp.d
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Expand Down
50 changes: 50 additions & 0 deletions compiler/src/dmd/pragmasem.d
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import dmd.globals;
import dmd.location;
import dmd.id;
import dmd.statement;
import dmd.mtype;

/**
* Run semantic on `pragma` declaration.
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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`
*
Expand Down
Loading
Loading