Skip to content

Commit

Permalink
[clang][Interp] Run record destructors when deallocating dynamic memory
Browse files Browse the repository at this point in the history
  • Loading branch information
tbaederr committed Jul 18, 2024
1 parent 3eb666e commit fc65a96
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 1 deletion.
75 changes: 75 additions & 0 deletions clang/lib/AST/Interp/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,81 @@ bool CheckNonNullArgs(InterpState &S, CodePtr OpPC, const Function *F,
return true;
}

// FIXME: This is similar to code we already have in Compiler.cpp.
// I think it makes sense to instead add the field and base destruction stuff
// to the destructor Function itself. Then destroying a record would really
// _just_ be calling its destructor. That would also help with the diagnostic
// difference when the destructor or a field/base fails.
static bool runRecordDestructor(InterpState &S, CodePtr OpPC,
const Pointer &BasePtr,
const Descriptor *Desc) {
assert(Desc->isRecord());
const Record *R = Desc->ElemRecord;
assert(R);

// Fields.
for (const Record::Field &Field : llvm::reverse(R->fields())) {
const Descriptor *D = Field.Desc;
if (D->isRecord()) {
if (!runRecordDestructor(S, OpPC, BasePtr.atField(Field.Offset), D))
return false;
} else if (D->isCompositeArray()) {
const Descriptor *ElemDesc = Desc->ElemDesc;
assert(ElemDesc->isRecord());
for (unsigned I = 0; I != Desc->getNumElems(); ++I) {
if (!runRecordDestructor(S, OpPC, BasePtr.atIndex(I).narrow(),
ElemDesc))
return false;
}
}
}

// Destructor of this record.
if (const CXXDestructorDecl *Dtor = R->getDestructor();
Dtor && !Dtor->isTrivial()) {
const Function *DtorFunc = S.getContext().getOrCreateFunction(Dtor);
if (!DtorFunc)
return false;

S.Stk.push<Pointer>(BasePtr);
if (!Call(S, OpPC, DtorFunc, 0))
return false;
}

// Bases.
for (const Record::Base &Base : llvm::reverse(R->bases())) {
if (!runRecordDestructor(S, OpPC, BasePtr.atField(Base.Offset), Base.Desc))
return false;
}

return true;
}

bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B) {
assert(B);
const Descriptor *Desc = B->getDescriptor();

if (Desc->isPrimitive() || Desc->isPrimitiveArray())
return true;

assert(Desc->isRecord() || Desc->isCompositeArray());

if (Desc->isCompositeArray()) {
const Descriptor *ElemDesc = Desc->ElemDesc;
assert(ElemDesc->isRecord());

Pointer RP(const_cast<Block *>(B));
for (unsigned I = 0; I != Desc->getNumElems(); ++I) {
if (!runRecordDestructor(S, OpPC, RP.atIndex(I).narrow(), ElemDesc))
return false;
}
return true;
}

assert(Desc->isRecord());
return runRecordDestructor(S, OpPC, Pointer(const_cast<Block *>(B)), Desc);
}

bool Interpret(InterpState &S, APValue &Result) {
// The current stack frame when we started Interpret().
// This is being used by the ops to determine wheter
Expand Down
6 changes: 5 additions & 1 deletion clang/lib/AST/Interp/Interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -2872,8 +2872,8 @@ inline bool AllocCN(InterpState &S, CodePtr OpPC, const Descriptor *ElementDesc,
return true;
}

bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B);
static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) {

if (!CheckDynamicMemoryAllocation(S, OpPC))
return false;

Expand Down Expand Up @@ -2904,6 +2904,10 @@ static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) {
assert(Source);
assert(BlockToDelete);

// Invoke destructors before deallocating the memory.
if (!RunDestructors(S, OpPC, BlockToDelete))
return false;

DynamicAllocator &Allocator = S.getAllocator();
bool WasArrayAlloc = Allocator.isArrayAllocation(Source);
const Descriptor *BlockDesc = BlockToDelete->getDescriptor();
Expand Down
73 changes: 73 additions & 0 deletions clang/test/AST/Interp/new-delete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,80 @@ constexpr Sp ss[] = {Sp{new int{154}}}; // both-error {{must be initialized by a
// both-note {{pointer to heap-allocated object}} \
// both-note {{allocation performed here}}

namespace DeleteRunsDtors {
struct InnerFoo {
int *mem;
constexpr ~InnerFoo() {
delete mem;
}
};

struct Foo {
int *a;
InnerFoo IF;

constexpr Foo() {
a = new int(13);
IF.mem = new int(100);
}
constexpr ~Foo() { delete a; }
};

constexpr int abc() {
Foo *F = new Foo();
int n = *F->a;
delete F;

return n;
}
static_assert(abc() == 13);

constexpr int abc2() {
Foo *f = new Foo[3];

delete[] f;

return 1;
}
static_assert(abc2() == 1);
}

/// FIXME: There is a slight difference in diagnostics here, because we don't
/// create a new frame when we delete record fields or bases at all.
namespace FaultyDtorCalledByDelete {
struct InnerFoo {
int *mem;
constexpr ~InnerFoo() {
if (mem) {
(void)(1/0); // both-warning {{division by zero is undefined}} \
// both-note {{division by zero}}
}
delete mem;
}
};

struct Foo {
int *a;
InnerFoo IF;

constexpr Foo() {
a = new int(13);
IF.mem = new int(100);
}
constexpr ~Foo() { delete a; }
};

constexpr int abc() {
Foo *F = new Foo();
int n = *F->a;
delete F; // both-note {{in call to}} \
// ref-note {{in call to}}

return n;
}
static_assert(abc() == 13); // both-error {{not an integral constant expression}} \
// both-note {{in call to 'abc()'}}
}


#else
Expand Down

0 comments on commit fc65a96

Please sign in to comment.