Skip to content

Commit fc65a96

Browse files
committed
[clang][Interp] Run record destructors when deallocating dynamic memory
1 parent 3eb666e commit fc65a96

File tree

3 files changed

+153
-1
lines changed

3 files changed

+153
-1
lines changed

clang/lib/AST/Interp/Interp.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,81 @@ bool CheckNonNullArgs(InterpState &S, CodePtr OpPC, const Function *F,
819819
return true;
820820
}
821821

822+
// FIXME: This is similar to code we already have in Compiler.cpp.
823+
// I think it makes sense to instead add the field and base destruction stuff
824+
// to the destructor Function itself. Then destroying a record would really
825+
// _just_ be calling its destructor. That would also help with the diagnostic
826+
// difference when the destructor or a field/base fails.
827+
static bool runRecordDestructor(InterpState &S, CodePtr OpPC,
828+
const Pointer &BasePtr,
829+
const Descriptor *Desc) {
830+
assert(Desc->isRecord());
831+
const Record *R = Desc->ElemRecord;
832+
assert(R);
833+
834+
// Fields.
835+
for (const Record::Field &Field : llvm::reverse(R->fields())) {
836+
const Descriptor *D = Field.Desc;
837+
if (D->isRecord()) {
838+
if (!runRecordDestructor(S, OpPC, BasePtr.atField(Field.Offset), D))
839+
return false;
840+
} else if (D->isCompositeArray()) {
841+
const Descriptor *ElemDesc = Desc->ElemDesc;
842+
assert(ElemDesc->isRecord());
843+
for (unsigned I = 0; I != Desc->getNumElems(); ++I) {
844+
if (!runRecordDestructor(S, OpPC, BasePtr.atIndex(I).narrow(),
845+
ElemDesc))
846+
return false;
847+
}
848+
}
849+
}
850+
851+
// Destructor of this record.
852+
if (const CXXDestructorDecl *Dtor = R->getDestructor();
853+
Dtor && !Dtor->isTrivial()) {
854+
const Function *DtorFunc = S.getContext().getOrCreateFunction(Dtor);
855+
if (!DtorFunc)
856+
return false;
857+
858+
S.Stk.push<Pointer>(BasePtr);
859+
if (!Call(S, OpPC, DtorFunc, 0))
860+
return false;
861+
}
862+
863+
// Bases.
864+
for (const Record::Base &Base : llvm::reverse(R->bases())) {
865+
if (!runRecordDestructor(S, OpPC, BasePtr.atField(Base.Offset), Base.Desc))
866+
return false;
867+
}
868+
869+
return true;
870+
}
871+
872+
bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B) {
873+
assert(B);
874+
const Descriptor *Desc = B->getDescriptor();
875+
876+
if (Desc->isPrimitive() || Desc->isPrimitiveArray())
877+
return true;
878+
879+
assert(Desc->isRecord() || Desc->isCompositeArray());
880+
881+
if (Desc->isCompositeArray()) {
882+
const Descriptor *ElemDesc = Desc->ElemDesc;
883+
assert(ElemDesc->isRecord());
884+
885+
Pointer RP(const_cast<Block *>(B));
886+
for (unsigned I = 0; I != Desc->getNumElems(); ++I) {
887+
if (!runRecordDestructor(S, OpPC, RP.atIndex(I).narrow(), ElemDesc))
888+
return false;
889+
}
890+
return true;
891+
}
892+
893+
assert(Desc->isRecord());
894+
return runRecordDestructor(S, OpPC, Pointer(const_cast<Block *>(B)), Desc);
895+
}
896+
822897
bool Interpret(InterpState &S, APValue &Result) {
823898
// The current stack frame when we started Interpret().
824899
// This is being used by the ops to determine wheter

clang/lib/AST/Interp/Interp.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2872,8 +2872,8 @@ inline bool AllocCN(InterpState &S, CodePtr OpPC, const Descriptor *ElementDesc,
28722872
return true;
28732873
}
28742874

2875+
bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B);
28752876
static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) {
2876-
28772877
if (!CheckDynamicMemoryAllocation(S, OpPC))
28782878
return false;
28792879

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

2907+
// Invoke destructors before deallocating the memory.
2908+
if (!RunDestructors(S, OpPC, BlockToDelete))
2909+
return false;
2910+
29072911
DynamicAllocator &Allocator = S.getAllocator();
29082912
bool WasArrayAlloc = Allocator.isArrayAllocation(Source);
29092913
const Descriptor *BlockDesc = BlockToDelete->getDescriptor();

clang/test/AST/Interp/new-delete.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,80 @@ constexpr Sp ss[] = {Sp{new int{154}}}; // both-error {{must be initialized by a
476476
// both-note {{pointer to heap-allocated object}} \
477477
// both-note {{allocation performed here}}
478478

479+
namespace DeleteRunsDtors {
480+
struct InnerFoo {
481+
int *mem;
482+
constexpr ~InnerFoo() {
483+
delete mem;
484+
}
485+
};
486+
487+
struct Foo {
488+
int *a;
489+
InnerFoo IF;
490+
491+
constexpr Foo() {
492+
a = new int(13);
493+
IF.mem = new int(100);
494+
}
495+
constexpr ~Foo() { delete a; }
496+
};
497+
498+
constexpr int abc() {
499+
Foo *F = new Foo();
500+
int n = *F->a;
501+
delete F;
502+
503+
return n;
504+
}
505+
static_assert(abc() == 13);
506+
507+
constexpr int abc2() {
508+
Foo *f = new Foo[3];
509+
510+
delete[] f;
511+
512+
return 1;
513+
}
514+
static_assert(abc2() == 1);
515+
}
516+
517+
/// FIXME: There is a slight difference in diagnostics here, because we don't
518+
/// create a new frame when we delete record fields or bases at all.
519+
namespace FaultyDtorCalledByDelete {
520+
struct InnerFoo {
521+
int *mem;
522+
constexpr ~InnerFoo() {
523+
if (mem) {
524+
(void)(1/0); // both-warning {{division by zero is undefined}} \
525+
// both-note {{division by zero}}
526+
}
527+
delete mem;
528+
}
529+
};
530+
531+
struct Foo {
532+
int *a;
533+
InnerFoo IF;
479534

535+
constexpr Foo() {
536+
a = new int(13);
537+
IF.mem = new int(100);
538+
}
539+
constexpr ~Foo() { delete a; }
540+
};
541+
542+
constexpr int abc() {
543+
Foo *F = new Foo();
544+
int n = *F->a;
545+
delete F; // both-note {{in call to}} \
546+
// ref-note {{in call to}}
547+
548+
return n;
549+
}
550+
static_assert(abc() == 13); // both-error {{not an integral constant expression}} \
551+
// both-note {{in call to 'abc()'}}
552+
}
480553

481554

482555
#else

0 commit comments

Comments
 (0)