Index: clang/lib/AST/Interp/ByteCodeExprGen.h =================================================================== --- clang/lib/AST/Interp/ByteCodeExprGen.h +++ clang/lib/AST/Interp/ByteCodeExprGen.h @@ -340,7 +340,7 @@ public: LocalScope(ByteCodeExprGen *Ctx) : VariableScope(Ctx) {} - ~LocalScope() override { this->emitDestruction(); } + virtual ~LocalScope() override { this->emitDestruction(); } void addLocal(const Scope::Local &Local) override { if (!Idx) { @@ -354,6 +354,29 @@ void emitDestruction() override { if (!Idx) return; + + // Emit destructor calls for local variables of record + // type with a destructor. + for (Scope::Local &Local : this->Ctx->Descriptors[*Idx]) { + const Record *TypeRecord = Local.Desc->ElemRecord; + if (!TypeRecord) + continue; + + const auto *RDecl = dyn_cast(TypeRecord->getDecl()); + if (!RDecl) + continue; + + if (const auto *DtorDecl = RDecl->getDestructor()) { + const Function *Dtor = this->Ctx->getFunction(DtorDecl); + if (Dtor && Dtor->isConstexpr()) { + assert(Dtor->hasThisPointer()); + assert(Dtor->getNumParams() == 1); + // Emit destructor call. + this->Ctx->emitGetPtrLocal(Local.Offset, DtorDecl); + this->Ctx->emitCall(Dtor, DtorDecl); + } + } + } this->Ctx->emitDestroy(*Idx, SourceInfo{}); } Index: clang/lib/AST/Interp/ByteCodeExprGen.cpp =================================================================== --- clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -27,10 +27,12 @@ namespace interp { /// Scope used to handle temporaries in toplevel variable declarations. -template class DeclScope final : public LocalScope { +template class DeclScope final : public VariableScope { public: DeclScope(ByteCodeExprGen *Ctx, const VarDecl *VD) - : LocalScope(Ctx), Scope(Ctx->P, VD) {} + : VariableScope(Ctx), Scope(Ctx->P, VD) {} + + virtual ~DeclScope() override { this->emitDestruction(); } void addExtended(const Scope::Local &Local) override { return this->addLocal(Local); Index: clang/lib/AST/Interp/ByteCodeStmtGen.cpp =================================================================== --- clang/lib/AST/Interp/ByteCodeStmtGen.cpp +++ clang/lib/AST/Interp/ByteCodeStmtGen.cpp @@ -375,6 +375,7 @@ if (!BreakLabel) return false; + this->emitCleanup(); return this->jump(*BreakLabel); } @@ -383,6 +384,7 @@ if (!ContinueLabel) return false; + this->emitCleanup(); return this->jump(*ContinueLabel); } Index: clang/test/AST/Interp/cxx20.cpp =================================================================== --- clang/test/AST/Interp/cxx20.cpp +++ clang/test/AST/Interp/cxx20.cpp @@ -166,3 +166,93 @@ // ref-error {{must be initialized by a constant expression}} }; + +namespace Destructors { + + class Inc final { + public: + int &I; + constexpr Inc(int &I) : I(I) {} + constexpr ~Inc() { + I++; + } + }; + + class Dec final { + public: + int &I; + constexpr Dec(int &I) : I(I) {} + constexpr ~Dec() { + I--; + } + }; + + + + constexpr int m() { + int i = 0; + { + Inc f1(i); + Inc f2(i); + Inc f3(i); + } + return i; + } + static_assert(m() == 3, ""); + + + constexpr int C() { + int i = 0; + + while (i < 10) { + Inc inc(i); + continue; + Dec dec(i); + } + return i; + } + static_assert(C() == 10, ""); + + + constexpr int D() { + int i = 0; + + { + Inc i1(i); + { + Inc i2(i); + return i; + } + } + + return i; + } + static_assert(D() == 0, ""); + + constexpr int E() { + int i = 0; + + for(;;) { + Inc i1(i); + break; + } + return i; + } + static_assert(E() == 1, ""); + + + /// FIXME: This should be rejected, since we call the destructor + /// twice. However, GCC doesn't care either. + constexpr int ManualDtor() { + int i = 0; + { + Inc I(i); // ref-note {{destroying object 'I' whose lifetime has already ended}} + I.~Inc(); + } + return i; + } + static_assert(ManualDtor() == 1, ""); // expected-error {{static assertion failed}} \ + // expected-note {{evaluates to '2 == 1'}} \ + // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to 'ManualDtor()'}} +};