Skip to content

Commit 49c399c

Browse files
committed
[clang][Interp] Toplevel destructors may fail
We used to run them, but not check if they failed. If they do, the expression is invalid, even if we already have a result. I do have a suspicion that we need to manually call destroyLocals() in more places (everywhere basically?), but I'll wait with that until I have a reproducer at hand.
1 parent 619ee20 commit 49c399c

File tree

5 files changed

+59
-22
lines changed

5 files changed

+59
-22
lines changed

clang/lib/AST/Interp/ByteCodeExprGen.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2587,7 +2587,10 @@ bool ByteCodeExprGen<Emitter>::visitExpr(const Expr *E) {
25872587

25882588
if (!this->emitFinishInit(E))
25892589
return false;
2590-
return this->emitRetValue(E);
2590+
// We are destroying the locals AFTER the Ret op.
2591+
// The Ret op needs to copy the (alive) values, but the
2592+
// destructors may still turn the entire expression invalid.
2593+
return this->emitRetValue(E) && RootScope.destroyLocals();
25912594
}
25922595

25932596
return false;
@@ -3414,14 +3417,15 @@ bool ByteCodeExprGen<Emitter>::emitRecordDestruction(const Record *R) {
34143417
// Now emit the destructor and recurse into base classes.
34153418
if (const CXXDestructorDecl *Dtor = R->getDestructor();
34163419
Dtor && !Dtor->isTrivial()) {
3417-
if (const Function *DtorFunc = getFunction(Dtor)) {
3418-
assert(DtorFunc->hasThisPointer());
3419-
assert(DtorFunc->getNumParams() == 1);
3420-
if (!this->emitDupPtr(SourceInfo{}))
3421-
return false;
3422-
if (!this->emitCall(DtorFunc, 0, SourceInfo{}))
3423-
return false;
3424-
}
3420+
const Function *DtorFunc = getFunction(Dtor);
3421+
if (!DtorFunc)
3422+
return false;
3423+
assert(DtorFunc->hasThisPointer());
3424+
assert(DtorFunc->getNumParams() == 1);
3425+
if (!this->emitDupPtr(SourceInfo{}))
3426+
return false;
3427+
if (!this->emitCall(DtorFunc, 0, SourceInfo{}))
3428+
return false;
34253429
}
34263430

34273431
for (const Record::Base &Base : llvm::reverse(R->bases())) {

clang/lib/AST/Interp/ByteCodeExprGen.h

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ template <class Emitter> class VariableScope {
332332
}
333333

334334
virtual void emitDestruction() {}
335-
virtual void emitDestructors() {}
335+
virtual bool emitDestructors() { return true; }
336336
VariableScope *getParent() const { return Parent; }
337337

338338
protected:
@@ -356,13 +356,18 @@ template <class Emitter> class LocalScope : public VariableScope<Emitter> {
356356
}
357357

358358
/// Overriden to support explicit destruction.
359-
void emitDestruction() override {
359+
void emitDestruction() override { destroyLocals(); }
360+
361+
/// Explicit destruction of local variables.
362+
bool destroyLocals() {
360363
if (!Idx)
361-
return;
362-
this->emitDestructors();
364+
return true;
365+
366+
bool Success = this->emitDestructors();
363367
this->Ctx->emitDestroy(*Idx, SourceInfo{});
364368
removeStoredOpaqueValues();
365369
this->Idx = std::nullopt;
370+
return Success;
366371
}
367372

368373
void addLocal(const Scope::Local &Local) override {
@@ -374,19 +379,25 @@ template <class Emitter> class LocalScope : public VariableScope<Emitter> {
374379
this->Ctx->Descriptors[*Idx].emplace_back(Local);
375380
}
376381

377-
void emitDestructors() override {
382+
bool emitDestructors() override {
378383
if (!Idx)
379-
return;
384+
return true;
380385
// Emit destructor calls for local variables of record
381386
// type with a destructor.
382387
for (Scope::Local &Local : this->Ctx->Descriptors[*Idx]) {
383388
if (!Local.Desc->isPrimitive() && !Local.Desc->isPrimitiveArray()) {
384-
this->Ctx->emitGetPtrLocal(Local.Offset, SourceInfo{});
385-
this->Ctx->emitDestruction(Local.Desc);
386-
this->Ctx->emitPopPtr(SourceInfo{});
389+
if (!this->Ctx->emitGetPtrLocal(Local.Offset, SourceInfo{}))
390+
return false;
391+
392+
if (!this->Ctx->emitDestruction(Local.Desc))
393+
return false;
394+
395+
if (!this->Ctx->emitPopPtr(SourceInfo{}))
396+
return false;
387397
removeIfStoredOpaqueValue(Local);
388398
}
389399
}
400+
return true;
390401
}
391402

392403
void removeStoredOpaqueValues() {

clang/lib/AST/Interp/EvalEmitter.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ EvaluationResult EvalEmitter::interpretExpr(const Expr *E,
3838
this->ConvertResultToRValue = ConvertResultToRValue;
3939
EvalResult.setSource(E);
4040

41-
if (!this->visitExpr(E) && EvalResult.empty())
41+
if (!this->visitExpr(E)) {
42+
// EvalResult may already have a result set, but something failed
43+
// after that (e.g. evaluating destructors).
4244
EvalResult.setInvalid();
45+
}
4346

4447
return std::move(this->EvalResult);
4548
}

clang/lib/AST/Interp/EvaluationResult.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ class EvaluationResult final {
7272
Kind = LValue;
7373
}
7474
void setInvalid() {
75-
assert(empty());
75+
// We are NOT asserting empty() here, since setting it to invalid
76+
// is allowed even if there is already a result.
7677
Kind = Invalid;
7778
}
7879
void setValid() {

clang/test/AST/Interp/cxx20.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %clang_cc1 -fcxx-exceptions -fexperimental-new-constant-interpreter -std=c++20 -verify %s
2-
// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=ref %s
1+
// RUN: %clang_cc1 -fcxx-exceptions -fexperimental-new-constant-interpreter -std=c++20 -verify=both,expected -fcxx-exceptions %s
2+
// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,ref -fcxx-exceptions %s
33

44
void test_alignas_operand() {
55
alignas(8) char dummy;
@@ -799,3 +799,21 @@ void f2() {
799799
// access info for unnamed bit-field
800800
}
801801
}
802+
803+
namespace FailingDestructor {
804+
struct D {
805+
int n;
806+
bool can_destroy;
807+
808+
constexpr ~D() {
809+
if (!can_destroy)
810+
throw "oh no";
811+
}
812+
};
813+
template<D d>
814+
void f() {} // both-note {{invalid explicitly-specified argument}}
815+
816+
void g() {
817+
f<D{0, false}>(); // both-error {{no matching function}}
818+
}
819+
}

0 commit comments

Comments
 (0)