diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp index fb63228f8aea88..2be9b5360d055d 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -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(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(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(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 diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index b4f8c03280c85c..17b3157cb40a93 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -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; @@ -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(); diff --git a/clang/test/AST/Interp/new-delete.cpp b/clang/test/AST/Interp/new-delete.cpp index 04ce3ae5f6637f..cb46426c0e3be6 100644 --- a/clang/test/AST/Interp/new-delete.cpp +++ b/clang/test/AST/Interp/new-delete.cpp @@ -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