We used to create a scope for the true- and false expression of a
conditional operator. This was done so e.g. in this example:
```c++
struct A { constexpr A(){}; ~A(); constexpr int get() { return 10; } }; // all-note 2{{declared here}}
static_assert( (false ? A().get() : 1) == 1);
```
we did _not_ evaluate the true branch at all, meaning we did not
register the local variable for the temporary of type `A`, which means
we also didn't call it destructor.
However, this breaks the case where the temporary needs to outlive the
conditional operator and instead be destroyed via the surrounding
`ExprWithCleanups`:
```
constexpr bool test2(bool b) {
unsigned long __ms = b ? (const unsigned long &)0 : __ms;
return true;
}
static_assert(test2(true));
```
Before this patch, we diagnosed this example:
```console
./array.cpp:180:15: error: static assertion expression is not an integral constant expression
180 | static_assert(test2(true));
| ^~~~~~~~~~~
./array.cpp:177:24: note: read of temporary whose lifetime has ended
177 | unsigned long __ms = b ? (const unsigned long &)0 : __ms;
| ^
./array.cpp:180:15: note: in call to 'test2(true)'
180 | static_assert(test2(true));
| ^~~~~~~~~~~
./array.cpp:177:51: note: temporary created here
177 | unsigned long __ms = b ? (const unsigned long &)0 : __ms;
| ^
1 error generated.
```
because the temporary created for the true branch got immediately
destroyed.
The problem in essence is that since the conditional operator doesn't
create a scope at all, we register the local variables for both its
branches, but we later only execute one of them, which means we should
also only destroy the locals of one of the branches.
We fix this similar to clang codgen's `is_active` flag: In the case of a
conditional operator (which is so far the only case where this is
problematic, and this also helps minimize the performance impact of this
change), we make local variables as disabled-by-default and then emit a
`EnableLocal` opcode later, which marks them as enabled. The code
calling their destructors checks whether the local was enabled at all.
382 lines
11 KiB
C++
382 lines
11 KiB
C++
//===--- EvalEmitter.cpp - Instruction emitter for the VM -------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "EvalEmitter.h"
|
|
#include "Context.h"
|
|
#include "IntegralAP.h"
|
|
#include "Interp.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
|
|
using namespace clang;
|
|
using namespace clang::interp;
|
|
|
|
EvalEmitter::EvalEmitter(Context &Ctx, Program &P, State &Parent,
|
|
InterpStack &Stk)
|
|
: Ctx(Ctx), P(P), S(Parent, P, Stk, Ctx, this), EvalResult(&Ctx) {}
|
|
|
|
EvalEmitter::~EvalEmitter() {
|
|
for (auto &V : Locals) {
|
|
Block *B = reinterpret_cast<Block *>(V.get());
|
|
if (B->isInitialized())
|
|
B->invokeDtor();
|
|
}
|
|
}
|
|
|
|
/// Clean up all our resources. This needs to done in failed evaluations before
|
|
/// we call InterpStack::clear(), because there might be a Pointer on the stack
|
|
/// pointing into a Block in the EvalEmitter.
|
|
void EvalEmitter::cleanup() { S.cleanup(); }
|
|
|
|
EvaluationResult EvalEmitter::interpretExpr(const Expr *E,
|
|
bool ConvertResultToRValue,
|
|
bool DestroyToplevelScope) {
|
|
S.setEvalLocation(E->getExprLoc());
|
|
this->ConvertResultToRValue = ConvertResultToRValue && !isa<ConstantExpr>(E);
|
|
this->CheckFullyInitialized = isa<ConstantExpr>(E);
|
|
EvalResult.setSource(E);
|
|
|
|
if (!this->visitExpr(E, DestroyToplevelScope)) {
|
|
// EvalResult may already have a result set, but something failed
|
|
// after that (e.g. evaluating destructors).
|
|
EvalResult.setInvalid();
|
|
}
|
|
|
|
return std::move(this->EvalResult);
|
|
}
|
|
|
|
EvaluationResult EvalEmitter::interpretDecl(const VarDecl *VD, const Expr *Init,
|
|
bool CheckFullyInitialized) {
|
|
assert(VD);
|
|
assert(Init);
|
|
this->CheckFullyInitialized = CheckFullyInitialized;
|
|
S.EvaluatingDecl = VD;
|
|
S.setEvalLocation(VD->getLocation());
|
|
EvalResult.setSource(VD);
|
|
|
|
QualType T = VD->getType();
|
|
this->ConvertResultToRValue = !Init->isGLValue() && !T->isPointerType() &&
|
|
!T->isObjCObjectPointerType();
|
|
EvalResult.setSource(VD);
|
|
|
|
if (!this->visitDeclAndReturn(VD, Init, S.inConstantContext()))
|
|
EvalResult.setInvalid();
|
|
|
|
S.EvaluatingDecl = nullptr;
|
|
updateGlobalTemporaries();
|
|
return std::move(this->EvalResult);
|
|
}
|
|
|
|
EvaluationResult EvalEmitter::interpretAsPointer(const Expr *E,
|
|
PtrCallback PtrCB) {
|
|
|
|
S.setEvalLocation(E->getExprLoc());
|
|
this->ConvertResultToRValue = false;
|
|
this->CheckFullyInitialized = false;
|
|
this->PtrCB = PtrCB;
|
|
EvalResult.setSource(E);
|
|
|
|
if (!this->visitExpr(E, /*DestroyToplevelScope=*/true)) {
|
|
// EvalResult may already have a result set, but something failed
|
|
// after that (e.g. evaluating destructors).
|
|
EvalResult.setInvalid();
|
|
}
|
|
|
|
return std::move(this->EvalResult);
|
|
}
|
|
|
|
bool EvalEmitter::interpretCall(const FunctionDecl *FD, const Expr *E) {
|
|
// Add parameters to the parameter map. The values in the ParamOffset don't
|
|
// matter in this case as reading from them can't ever work.
|
|
for (const ParmVarDecl *PD : FD->parameters()) {
|
|
this->Params.insert({PD, {0, false}});
|
|
}
|
|
|
|
return this->visitExpr(E, /*DestroyToplevelScope=*/false);
|
|
}
|
|
|
|
void EvalEmitter::emitLabel(LabelTy Label) { CurrentLabel = Label; }
|
|
|
|
EvalEmitter::LabelTy EvalEmitter::getLabel() { return NextLabel++; }
|
|
|
|
Scope::Local EvalEmitter::createLocal(Descriptor *D) {
|
|
// Allocate memory for a local.
|
|
auto Memory = std::make_unique<char[]>(sizeof(Block) + D->getAllocSize());
|
|
auto *B = new (Memory.get()) Block(Ctx.getEvalID(), D, /*isStatic=*/false);
|
|
B->invokeCtor();
|
|
|
|
// Initialize local variable inline descriptor.
|
|
InlineDescriptor &Desc = *reinterpret_cast<InlineDescriptor *>(B->rawData());
|
|
Desc.Desc = D;
|
|
Desc.Offset = sizeof(InlineDescriptor);
|
|
Desc.IsActive = false;
|
|
Desc.IsBase = false;
|
|
Desc.IsFieldMutable = false;
|
|
Desc.IsConst = false;
|
|
Desc.IsInitialized = false;
|
|
|
|
// Register the local.
|
|
unsigned Off = Locals.size();
|
|
Locals.push_back(std::move(Memory));
|
|
return {Off, D};
|
|
}
|
|
|
|
bool EvalEmitter::jumpTrue(const LabelTy &Label) {
|
|
if (isActive()) {
|
|
if (S.Stk.pop<bool>())
|
|
ActiveLabel = Label;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EvalEmitter::jumpFalse(const LabelTy &Label) {
|
|
if (isActive()) {
|
|
if (!S.Stk.pop<bool>())
|
|
ActiveLabel = Label;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EvalEmitter::jump(const LabelTy &Label) {
|
|
if (isActive())
|
|
CurrentLabel = ActiveLabel = Label;
|
|
return true;
|
|
}
|
|
|
|
bool EvalEmitter::fallthrough(const LabelTy &Label) {
|
|
if (isActive())
|
|
ActiveLabel = Label;
|
|
CurrentLabel = Label;
|
|
return true;
|
|
}
|
|
|
|
bool EvalEmitter::speculate(const CallExpr *E, const LabelTy &EndLabel) {
|
|
size_t StackSizeBefore = S.Stk.size();
|
|
const Expr *Arg = E->getArg(0);
|
|
if (!this->visit(Arg)) {
|
|
S.Stk.clearTo(StackSizeBefore);
|
|
|
|
if (S.inConstantContext() || Arg->HasSideEffects(S.getASTContext()))
|
|
return this->emitBool(false, E);
|
|
return Invalid(S, OpPC);
|
|
}
|
|
|
|
PrimType T = Ctx.classify(Arg->getType()).value_or(PT_Ptr);
|
|
if (T == PT_Ptr) {
|
|
const auto &Ptr = S.Stk.pop<Pointer>();
|
|
return this->emitBool(CheckBCPResult(S, Ptr), E);
|
|
}
|
|
|
|
// Otherwise, this is fine!
|
|
if (!this->emitPop(T, E))
|
|
return false;
|
|
return this->emitBool(true, E);
|
|
}
|
|
|
|
template <PrimType OpType> bool EvalEmitter::emitRet(SourceInfo Info) {
|
|
if (!isActive())
|
|
return true;
|
|
|
|
using T = typename PrimConv<OpType>::T;
|
|
EvalResult.takeValue(S.Stk.pop<T>().toAPValue(Ctx.getASTContext()));
|
|
return true;
|
|
}
|
|
|
|
template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) {
|
|
if (!isActive())
|
|
return true;
|
|
|
|
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
|
|
|
if (Ptr.isFunctionPointer()) {
|
|
EvalResult.takeValue(Ptr.toAPValue(Ctx.getASTContext()));
|
|
return true;
|
|
}
|
|
|
|
// If we're returning a raw pointer, call our callback.
|
|
if (this->PtrCB)
|
|
return (*this->PtrCB)(Ptr);
|
|
|
|
if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info))
|
|
return false;
|
|
if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr))
|
|
return false;
|
|
|
|
// Implicitly convert lvalue to rvalue, if requested.
|
|
if (ConvertResultToRValue) {
|
|
if (!Ptr.isZero() && !Ptr.isDereferencable())
|
|
return false;
|
|
|
|
if (Ptr.pointsToStringLiteral() && Ptr.isArrayRoot())
|
|
return false;
|
|
|
|
if (!Ptr.isZero() && !CheckFinalLoad(S, OpPC, Ptr))
|
|
return false;
|
|
|
|
// Never allow reading from a non-const pointer, unless the memory
|
|
// has been created in this evaluation.
|
|
if (!Ptr.isZero() && !Ptr.isConst() && Ptr.isBlockPointer() &&
|
|
Ptr.block()->getEvalID() != Ctx.getEvalID())
|
|
return false;
|
|
|
|
if (std::optional<APValue> V =
|
|
Ptr.toRValue(Ctx, EvalResult.getSourceType())) {
|
|
EvalResult.takeValue(std::move(*V));
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
// If this is pointing to a local variable, just return
|
|
// the result, even if the pointer is dead.
|
|
// This will later be diagnosed by CheckLValueConstantExpression.
|
|
if (Ptr.isBlockPointer() && !Ptr.block()->isStatic()) {
|
|
EvalResult.takeValue(Ptr.toAPValue(Ctx.getASTContext()));
|
|
return true;
|
|
}
|
|
|
|
if (!Ptr.isLive() && !Ptr.isTemporary())
|
|
return false;
|
|
|
|
EvalResult.takeValue(Ptr.toAPValue(Ctx.getASTContext()));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EvalEmitter::emitRetVoid(SourceInfo Info) {
|
|
EvalResult.setValid();
|
|
return true;
|
|
}
|
|
|
|
bool EvalEmitter::emitRetValue(SourceInfo Info) {
|
|
const auto &Ptr = S.Stk.pop<Pointer>();
|
|
|
|
if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info))
|
|
return false;
|
|
if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr))
|
|
return false;
|
|
|
|
if (std::optional<APValue> APV =
|
|
Ptr.toRValue(S.getASTContext(), EvalResult.getSourceType())) {
|
|
EvalResult.takeValue(std::move(*APV));
|
|
return true;
|
|
}
|
|
|
|
EvalResult.setInvalid();
|
|
return false;
|
|
}
|
|
|
|
bool EvalEmitter::emitGetPtrLocal(uint32_t I, SourceInfo Info) {
|
|
if (!isActive())
|
|
return true;
|
|
|
|
Block *B = getLocal(I);
|
|
S.Stk.push<Pointer>(B, sizeof(InlineDescriptor));
|
|
return true;
|
|
}
|
|
|
|
template <PrimType OpType>
|
|
bool EvalEmitter::emitGetLocal(uint32_t I, SourceInfo Info) {
|
|
if (!isActive())
|
|
return true;
|
|
|
|
using T = typename PrimConv<OpType>::T;
|
|
|
|
Block *B = getLocal(I);
|
|
|
|
if (!CheckLocalLoad(S, OpPC, B))
|
|
return false;
|
|
|
|
S.Stk.push<T>(*reinterpret_cast<T *>(B->data()));
|
|
return true;
|
|
}
|
|
|
|
template <PrimType OpType>
|
|
bool EvalEmitter::emitSetLocal(uint32_t I, SourceInfo Info) {
|
|
if (!isActive())
|
|
return true;
|
|
|
|
using T = typename PrimConv<OpType>::T;
|
|
|
|
Block *B = getLocal(I);
|
|
*reinterpret_cast<T *>(B->data()) = S.Stk.pop<T>();
|
|
InlineDescriptor &Desc = *reinterpret_cast<InlineDescriptor *>(B->rawData());
|
|
Desc.IsInitialized = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EvalEmitter::emitDestroy(uint32_t I, SourceInfo Info) {
|
|
if (!isActive())
|
|
return true;
|
|
|
|
for (auto &Local : Descriptors[I]) {
|
|
Block *B = getLocal(Local.Offset);
|
|
S.deallocate(B);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EvalEmitter::emitGetLocalEnabled(uint32_t I, SourceInfo Info) {
|
|
if (!isActive())
|
|
return true;
|
|
|
|
Block *B = getLocal(I);
|
|
const InlineDescriptor &Desc =
|
|
*reinterpret_cast<InlineDescriptor *>(B->rawData());
|
|
|
|
S.Stk.push<bool>(Desc.IsActive);
|
|
return true;
|
|
}
|
|
|
|
bool EvalEmitter::emitEnableLocal(uint32_t I, SourceInfo Info) {
|
|
if (!isActive())
|
|
return true;
|
|
|
|
// FIXME: This is a little dirty, but to avoid adding a flag to
|
|
// InlineDescriptor that's only ever useful on the toplevel of local
|
|
// variables, we reuse the IsActive flag for the enabled state. We should
|
|
// probably use a different struct than InlineDescriptor for the block-level
|
|
// inline descriptor of local varaibles.
|
|
Block *B = getLocal(I);
|
|
InlineDescriptor &Desc = *reinterpret_cast<InlineDescriptor *>(B->rawData());
|
|
Desc.IsActive = true;
|
|
return true;
|
|
}
|
|
|
|
/// Global temporaries (LifetimeExtendedTemporary) carry their value
|
|
/// around as an APValue, which codegen accesses.
|
|
/// We set their value once when creating them, but we don't update it
|
|
/// afterwards when code changes it later.
|
|
/// This is what we do here.
|
|
void EvalEmitter::updateGlobalTemporaries() {
|
|
for (const auto &[E, Temp] : S.SeenGlobalTemporaries) {
|
|
UnsignedOrNone GlobalIndex = P.getGlobal(E);
|
|
assert(GlobalIndex);
|
|
const Pointer &Ptr = P.getPtrGlobal(*GlobalIndex);
|
|
APValue *Cached = Temp->getOrCreateValue(true);
|
|
if (OptPrimType T = Ctx.classify(E->getType())) {
|
|
TYPE_SWITCH(*T,
|
|
{ *Cached = Ptr.deref<T>().toAPValue(Ctx.getASTContext()); });
|
|
} else {
|
|
if (std::optional<APValue> APV =
|
|
Ptr.toRValue(Ctx, Temp->getTemporaryExpr()->getType()))
|
|
*Cached = *APV;
|
|
}
|
|
}
|
|
S.SeenGlobalTemporaries.clear();
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Opcode evaluators
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define GET_EVAL_IMPL
|
|
#include "Opcodes.inc"
|
|
#undef GET_EVAL_IMPL
|