Skip to content

Commit 83fea8b

Browse files
authored
[clang][bytecode] Allow continuing when discarded MemberExpr Base fails (llvm#107231)
We don't need the value in this case, since we're discarding it anyway. Allow continuing the interpretation but note the side effect.
1 parent 3242e77 commit 83fea8b

File tree

16 files changed

+97
-44
lines changed

16 files changed

+97
-44
lines changed

clang/lib/AST/ByteCode/ByteCodeEmitter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class ByteCodeEmitter {
4646

4747
/// Methods implemented by the compiler.
4848
virtual bool visitFunc(const FunctionDecl *E) = 0;
49-
virtual bool visitExpr(const Expr *E) = 0;
49+
virtual bool visitExpr(const Expr *E, bool DestroyToplevelScope) = 0;
5050
virtual bool visitDeclAndReturn(const VarDecl *E, bool ConstantContext) = 0;
5151

5252
/// Emits jumps.

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,8 +1874,12 @@ bool Compiler<Emitter>::VisitMemberExpr(const MemberExpr *E) {
18741874
return false;
18751875
}
18761876

1877-
if (!isa<FieldDecl>(Member))
1878-
return this->discard(Base) && this->visitDeclRef(Member, E);
1877+
if (!isa<FieldDecl>(Member)) {
1878+
if (!this->discard(Base) && !this->emitSideEffect(E))
1879+
return false;
1880+
1881+
return this->visitDeclRef(Member, E);
1882+
}
18791883

18801884
if (Initializing) {
18811885
if (!this->delegate(Base))
@@ -2673,8 +2677,14 @@ bool Compiler<Emitter>::VisitCXXConstructExpr(const CXXConstructExpr *E) {
26732677
if (!this->emitCallVar(Func, VarArgSize, E))
26742678
return false;
26752679
} else {
2676-
if (!this->emitCall(Func, 0, E))
2680+
if (!this->emitCall(Func, 0, E)) {
2681+
// When discarding, we don't need the result anyway, so clean up
2682+
// the instance dup we did earlier in case surrounding code wants
2683+
// to keep evaluating.
2684+
if (DiscardResult)
2685+
(void)this->emitPopPtr(E);
26772686
return false;
2687+
}
26782688
}
26792689

26802690
if (DiscardResult)
@@ -3723,20 +3733,29 @@ const Function *Compiler<Emitter>::getFunction(const FunctionDecl *FD) {
37233733
return Ctx.getOrCreateFunction(FD);
37243734
}
37253735

3726-
template <class Emitter> bool Compiler<Emitter>::visitExpr(const Expr *E) {
3736+
template <class Emitter>
3737+
bool Compiler<Emitter>::visitExpr(const Expr *E, bool DestroyToplevelScope) {
37273738
LocalScope<Emitter> RootScope(this);
3739+
3740+
auto maybeDestroyLocals = [&]() -> bool {
3741+
if (DestroyToplevelScope)
3742+
return RootScope.destroyLocals();
3743+
return true;
3744+
};
3745+
37283746
// Void expressions.
37293747
if (E->getType()->isVoidType()) {
37303748
if (!visit(E))
37313749
return false;
3732-
return this->emitRetVoid(E) && RootScope.destroyLocals();
3750+
return this->emitRetVoid(E) && maybeDestroyLocals();
37333751
}
37343752

37353753
// Expressions with a primitive return type.
37363754
if (std::optional<PrimType> T = classify(E)) {
37373755
if (!visit(E))
37383756
return false;
3739-
return this->emitRet(*T, E) && RootScope.destroyLocals();
3757+
3758+
return this->emitRet(*T, E) && maybeDestroyLocals();
37403759
}
37413760

37423761
// Expressions with a composite return type.
@@ -3754,10 +3773,10 @@ template <class Emitter> bool Compiler<Emitter>::visitExpr(const Expr *E) {
37543773
// We are destroying the locals AFTER the Ret op.
37553774
// The Ret op needs to copy the (alive) values, but the
37563775
// destructors may still turn the entire expression invalid.
3757-
return this->emitRetValue(E) && RootScope.destroyLocals();
3776+
return this->emitRetValue(E) && maybeDestroyLocals();
37583777
}
37593778

3760-
RootScope.destroyLocals();
3779+
(void)maybeDestroyLocals();
37613780
return false;
37623781
}
37633782

clang/lib/AST/ByteCode/Compiler.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
222222

223223
protected:
224224
bool visitStmt(const Stmt *S);
225-
bool visitExpr(const Expr *E) override;
225+
bool visitExpr(const Expr *E, bool DestroyToplevelScope) override;
226226
bool visitFunc(const FunctionDecl *F) override;
227227

228228
bool visitDeclAndReturn(const VarDecl *VD, bool ConstantContext) override;

clang/lib/AST/ByteCode/Context.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,16 @@ bool Context::evaluateAsRValue(State &Parent, const Expr *E, APValue &Result) {
7070
return true;
7171
}
7272

73-
bool Context::evaluate(State &Parent, const Expr *E, APValue &Result) {
73+
bool Context::evaluate(State &Parent, const Expr *E, APValue &Result,
74+
ConstantExprKind Kind) {
7475
++EvalID;
7576
bool Recursing = !Stk.empty();
7677
size_t StackSizeBefore = Stk.size();
7778
Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
7879

79-
auto Res = C.interpretExpr(E);
80+
auto Res = C.interpretExpr(E, /*ConvertResultToRValue=*/false,
81+
/*DestroyToplevelScope=*/Kind ==
82+
ConstantExprKind::ClassTemplateArgument);
8083
if (Res.isInvalid()) {
8184
C.cleanup();
8285
Stk.clearTo(StackSizeBefore);

clang/lib/AST/ByteCode/Context.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ class Context final {
5252
bool evaluateAsRValue(State &Parent, const Expr *E, APValue &Result);
5353

5454
/// Like evaluateAsRvalue(), but does no implicit lvalue-to-rvalue conversion.
55-
bool evaluate(State &Parent, const Expr *E, APValue &Result);
55+
bool evaluate(State &Parent, const Expr *E, APValue &Result,
56+
ConstantExprKind Kind);
5657

5758
/// Evaluates a toplevel initializer.
5859
bool evaluateAsInitializer(State &Parent, const VarDecl *VD, APValue &Result);

clang/lib/AST/ByteCode/EvalEmitter.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,14 @@ EvalEmitter::~EvalEmitter() {
3838
void EvalEmitter::cleanup() { S.cleanup(); }
3939

4040
EvaluationResult EvalEmitter::interpretExpr(const Expr *E,
41-
bool ConvertResultToRValue) {
41+
bool ConvertResultToRValue,
42+
bool DestroyToplevelScope) {
4243
S.setEvalLocation(E->getExprLoc());
4344
this->ConvertResultToRValue = ConvertResultToRValue && !isa<ConstantExpr>(E);
4445
this->CheckFullyInitialized = isa<ConstantExpr>(E);
4546
EvalResult.setSource(E);
4647

47-
if (!this->visitExpr(E)) {
48+
if (!this->visitExpr(E, DestroyToplevelScope)) {
4849
// EvalResult may already have a result set, but something failed
4950
// after that (e.g. evaluating destructors).
5051
EvalResult.setInvalid();

clang/lib/AST/ByteCode/EvalEmitter.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class EvalEmitter : public SourceMapper {
3535
using Local = Scope::Local;
3636

3737
EvaluationResult interpretExpr(const Expr *E,
38-
bool ConvertResultToRValue = false);
38+
bool ConvertResultToRValue = false,
39+
bool DestroyToplevelScope = false);
3940
EvaluationResult interpretDecl(const VarDecl *VD, bool CheckFullyInitialized);
4041

4142
/// Clean up all resources.
@@ -54,7 +55,7 @@ class EvalEmitter : public SourceMapper {
5455
LabelTy getLabel();
5556

5657
/// Methods implemented by the compiler.
57-
virtual bool visitExpr(const Expr *E) = 0;
58+
virtual bool visitExpr(const Expr *E, bool DestroyToplevelScope) = 0;
5859
virtual bool visitDeclAndReturn(const VarDecl *VD, bool ConstantContext) = 0;
5960
virtual bool visitFunc(const FunctionDecl *F) = 0;
6061

clang/lib/AST/ByteCode/Interp.cpp

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -221,17 +221,17 @@ static void popArg(InterpState &S, const Expr *Arg) {
221221
TYPE_SWITCH(Ty, S.Stk.discard<T>());
222222
}
223223

224-
void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC) {
224+
void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC,
225+
const Function *Func) {
225226
assert(S.Current);
226-
const Function *CurFunc = S.Current->getFunction();
227-
assert(CurFunc);
227+
assert(Func);
228228

229-
if (CurFunc->isUnevaluatedBuiltin())
229+
if (Func->isUnevaluatedBuiltin())
230230
return;
231231

232232
// Some builtin functions require us to only look at the call site, since
233233
// the classified parameter types do not match.
234-
if (unsigned BID = CurFunc->getBuiltinID();
234+
if (unsigned BID = Func->getBuiltinID();
235235
BID && S.getASTContext().BuiltinInfo.hasCustomTypechecking(BID)) {
236236
const auto *CE =
237237
cast<CallExpr>(S.Current->Caller->getExpr(S.Current->getRetPC()));
@@ -242,7 +242,7 @@ void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC) {
242242
return;
243243
}
244244

245-
if (S.Current->Caller && CurFunc->isVariadic()) {
245+
if (S.Current->Caller && Func->isVariadic()) {
246246
// CallExpr we're look for is at the return PC of the current function, i.e.
247247
// in the caller.
248248
// This code path should be executed very rarely.
@@ -259,8 +259,8 @@ void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC) {
259259
} else
260260
assert(false && "Can't get arguments from that expression type");
261261

262-
assert(NumArgs >= CurFunc->getNumWrittenParams());
263-
NumVarArgs = NumArgs - (CurFunc->getNumWrittenParams() +
262+
assert(NumArgs >= Func->getNumWrittenParams());
263+
NumVarArgs = NumArgs - (Func->getNumWrittenParams() +
264264
isa<CXXOperatorCallExpr>(CallSite));
265265
for (unsigned I = 0; I != NumVarArgs; ++I) {
266266
const Expr *A = Args[NumArgs - 1 - I];
@@ -270,7 +270,8 @@ void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC) {
270270

271271
// And in any case, remove the fixed parameters (the non-variadic ones)
272272
// at the end.
273-
S.Current->popArgs();
273+
for (PrimType Ty : Func->args_reverse())
274+
TYPE_SWITCH(Ty, S.Stk.discard<T>());
274275
}
275276

276277
bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
@@ -1036,6 +1037,12 @@ bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func,
10361037

10371038
bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
10381039
uint32_t VarArgSize) {
1040+
assert(Func);
1041+
auto cleanup = [&]() -> bool {
1042+
cleanupAfterFunctionCall(S, OpPC, Func);
1043+
return false;
1044+
};
1045+
10391046
if (Func->hasThisPointer()) {
10401047
size_t ArgSize = Func->getArgSize() + VarArgSize;
10411048
size_t ThisOffset = ArgSize - (Func->hasRVO() ? primSize(PT_Ptr) : 0);
@@ -1052,22 +1059,22 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
10521059
assert(ThisPtr.isZero());
10531060
} else {
10541061
if (!CheckInvoke(S, OpPC, ThisPtr))
1055-
return false;
1062+
return cleanup();
10561063
}
10571064
}
10581065

10591066
if (!CheckCallable(S, OpPC, Func))
1060-
return false;
1067+
return cleanup();
10611068

10621069
// FIXME: The isConstructor() check here is not always right. The current
10631070
// constant evaluator is somewhat inconsistent in when it allows a function
10641071
// call when checking for a constant expression.
10651072
if (Func->hasThisPointer() && S.checkingPotentialConstantExpression() &&
10661073
!Func->isConstructor())
1067-
return false;
1074+
return cleanup();
10681075

10691076
if (!CheckCallDepth(S, OpPC))
1070-
return false;
1077+
return cleanup();
10711078

10721079
auto NewFrame = std::make_unique<InterpFrame>(S, Func, OpPC, VarArgSize);
10731080
InterpFrame *FrameBefore = S.Current;

clang/lib/AST/ByteCode/Interp.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,8 @@ enum class ArithOp { Add, Sub };
281281
// Returning values
282282
//===----------------------------------------------------------------------===//
283283

284-
void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC);
284+
void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC,
285+
const Function *Func);
285286

286287
template <PrimType Name, class T = typename PrimConv<Name>::T>
287288
bool Ret(InterpState &S, CodePtr &PC, APValue &Result) {
@@ -302,7 +303,7 @@ bool Ret(InterpState &S, CodePtr &PC, APValue &Result) {
302303
assert(S.Current);
303304
assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame");
304305
if (!S.checkingPotentialConstantExpression() || S.Current->Caller)
305-
cleanupAfterFunctionCall(S, PC);
306+
cleanupAfterFunctionCall(S, PC, S.Current->getFunction());
306307

307308
if (InterpFrame *Caller = S.Current->Caller) {
308309
PC = S.Current->getRetPC();
@@ -322,7 +323,7 @@ inline bool RetVoid(InterpState &S, CodePtr &PC, APValue &Result) {
322323
assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame");
323324

324325
if (!S.checkingPotentialConstantExpression() || S.Current->Caller)
325-
cleanupAfterFunctionCall(S, PC);
326+
cleanupAfterFunctionCall(S, PC, S.Current->getFunction());
326327

327328
if (InterpFrame *Caller = S.Current->Caller) {
328329
PC = S.Current->getRetPC();
@@ -2658,6 +2659,9 @@ inline bool Unsupported(InterpState &S, CodePtr OpPC) {
26582659

26592660
/// Do nothing and just abort execution.
26602661
inline bool Error(InterpState &S, CodePtr OpPC) { return false; }
2662+
inline bool SideEffect(InterpState &S, CodePtr OpPC) {
2663+
return S.noteSideEffect();
2664+
}
26612665

26622666
/// Same here, but only for casts.
26632667
inline bool InvalidCast(InterpState &S, CodePtr OpPC, CastKind Kind,

clang/lib/AST/ByteCode/InterpFrame.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,6 @@ void InterpFrame::destroy(unsigned Idx) {
9696
}
9797
}
9898

99-
void InterpFrame::popArgs() {
100-
for (PrimType Ty : Func->args_reverse())
101-
TYPE_SWITCH(Ty, S.Stk.discard<T>());
102-
}
103-
10499
template <typename T>
105100
static void print(llvm::raw_ostream &OS, const T &V, ASTContext &ASTCtx,
106101
QualType Ty) {

0 commit comments

Comments
 (0)