[clang][bytecode] Use bytecode interpreter in EvaluateCharRangeAsString (#138461)

This was always using the ast walker.
This commit is contained in:
Timm Baeder
2025-05-05 09:06:34 +02:00
committed by GitHub
parent 003fa7731d
commit ecc73a6f1c
9 changed files with 130 additions and 6 deletions

View File

@@ -134,6 +134,85 @@ bool Context::evaluateAsInitializer(State &Parent, const VarDecl *VD,
return true;
}
template <typename ResultT>
bool Context::evaluateStringRepr(State &Parent, const Expr *SizeExpr,
const Expr *PtrExpr, ResultT &Result) {
assert(Stk.empty());
Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
// Evaluate size value.
APValue SizeValue;
if (!evaluateAsRValue(Parent, SizeExpr, SizeValue))
return false;
if (!SizeValue.isInt())
return false;
uint64_t Size = SizeValue.getInt().getZExtValue();
auto PtrRes = C.interpretAsPointer(PtrExpr, [&](const Pointer &Ptr) {
if (Size == 0) {
if constexpr (std::is_same_v<ResultT, APValue>)
Result = APValue(APValue::UninitArray{}, 0, 0);
return true;
}
if (!Ptr.isLive() || !Ptr.getFieldDesc()->isPrimitiveArray())
return false;
// Must be char.
if (Ptr.getFieldDesc()->getElemSize() != 1 /*bytes*/)
return false;
if (Size > Ptr.getNumElems()) {
Parent.FFDiag(SizeExpr, diag::note_constexpr_access_past_end) << AK_Read;
Size = Ptr.getNumElems();
}
if constexpr (std::is_same_v<ResultT, APValue>) {
QualType CharTy = PtrExpr->getType()->getPointeeType();
Result = APValue(APValue::UninitArray{}, Size, Size);
for (uint64_t I = 0; I != Size; ++I) {
if (std::optional<APValue> ElemVal =
Ptr.atIndex(I).toRValue(*this, CharTy))
Result.getArrayInitializedElt(I) = *ElemVal;
else
return false;
}
} else {
assert((std::is_same_v<ResultT, std::string>));
if (Size < Result.max_size())
Result.resize(Size);
Result.assign(reinterpret_cast<const char *>(Ptr.getRawAddress()), Size);
}
return true;
});
if (PtrRes.isInvalid()) {
C.cleanup();
Stk.clear();
return false;
}
return true;
}
bool Context::evaluateCharRange(State &Parent, const Expr *SizeExpr,
const Expr *PtrExpr, APValue &Result) {
assert(SizeExpr);
assert(PtrExpr);
return evaluateStringRepr(Parent, SizeExpr, PtrExpr, Result);
}
bool Context::evaluateCharRange(State &Parent, const Expr *SizeExpr,
const Expr *PtrExpr, std::string &Result) {
assert(SizeExpr);
assert(PtrExpr);
return evaluateStringRepr(Parent, SizeExpr, PtrExpr, Result);
}
const LangOptions &Context::getLangOpts() const { return Ctx.getLangOpts(); }
std::optional<PrimType> Context::classify(QualType T) const {

View File

@@ -59,6 +59,11 @@ public:
/// Evaluates a toplevel initializer.
bool evaluateAsInitializer(State &Parent, const VarDecl *VD, APValue &Result);
bool evaluateCharRange(State &Parent, const Expr *SizeExpr,
const Expr *PtrExpr, APValue &Result);
bool evaluateCharRange(State &Parent, const Expr *SizeExpr,
const Expr *PtrExpr, std::string &Result);
/// Returns the AST context.
ASTContext &getASTContext() const { return Ctx; }
/// Returns the language options.
@@ -120,6 +125,10 @@ private:
/// Runs a function.
bool Run(State &Parent, const Function *Func);
template <typename ResultT>
bool evaluateStringRepr(State &Parent, const Expr *SizeExpr,
const Expr *PtrExpr, ResultT &Result);
/// Current compilation context.
ASTContext &Ctx;
/// Interpreter stack, shared across invocations.

View File

@@ -72,6 +72,24 @@ EvaluationResult EvalEmitter::interpretDecl(const VarDecl *VD,
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);
}
void EvalEmitter::emitLabel(LabelTy Label) { CurrentLabel = Label; }
EvalEmitter::LabelTy EvalEmitter::getLabel() { return NextLabel++; }
@@ -170,6 +188,10 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) {
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))

View File

@@ -32,11 +32,14 @@ public:
using LabelTy = uint32_t;
using AddrTy = uintptr_t;
using Local = Scope::Local;
using PtrCallback = llvm::function_ref<bool(const Pointer &)>;
EvaluationResult interpretExpr(const Expr *E,
bool ConvertResultToRValue = false,
bool DestroyToplevelScope = false);
EvaluationResult interpretDecl(const VarDecl *VD, bool CheckFullyInitialized);
/// Interpret the given Expr to a Pointer.
EvaluationResult interpretAsPointer(const Expr *E, PtrCallback PtrCB);
/// Clean up all resources.
void cleanup();
@@ -104,6 +107,8 @@ private:
/// Whether we should check if the result has been fully
/// initialized.
bool CheckFullyInitialized = false;
/// Callback to call when using interpretAsPointer.
std::optional<PtrCallback> PtrCB;
/// Temporaries which require storage.
llvm::DenseMap<unsigned, std::unique_ptr<char[]>> Locals;

View File

@@ -61,11 +61,6 @@ private:
Value = std::move(V);
Kind = RValue;
}
void setPointer(const Pointer P) {
assert(empty());
Value = P;
Kind = LValue;
}
void setFunctionPointer(const FunctionPointer &P) {
assert(empty());
Value = P;
@@ -88,6 +83,7 @@ public:
bool isInvalid() const { return Kind == Invalid; }
bool isLValue() const { return Kind == LValue; }
bool isRValue() const { return Kind == RValue; }
bool isPointer() const { return std::holds_alternative<Pointer>(Value); }
/// Returns an APValue for the evaluation result. The returned
/// APValue might be an LValue or RValue.

View File

@@ -613,6 +613,13 @@ public:
const Block *block() const { return asBlockPointer().Pointee; }
/// If backed by actual data (i.e. a block pointer), return
/// an address to that data.
const std::byte *getRawAddress() const {
assert(isBlockPointer());
return asBlockPointer().Pointee->rawData() + Offset;
}
/// Returns the index into an array.
int64_t getIndex() const {
if (!isBlockPointer())

View File

@@ -18019,10 +18019,14 @@ static bool EvaluateCharRangeAsStringImpl(const Expr *, T &Result,
const Expr *PtrExpression,
ASTContext &Ctx,
Expr::EvalResult &Status) {
LValue String;
EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantExpression);
Info.InConstantContext = true;
if (Info.EnableNewConstInterp)
return Info.Ctx.getInterpContext().evaluateCharRange(Info, SizeExpression,
PtrExpression, Result);
LValue String;
FullExpressionRAII Scope(Info);
APSInt SizeValue;
if (!::EvaluateInteger(SizeExpression, SizeValue, Info))

View File

@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -triple x86_64-gnu-linux
// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -triple x86_64-gnu-linux -fexperimental-new-constant-interpreter
template <bool Leak>
struct RAIIBase {

View File

@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -std=c++2c -triple=x86_64-linux -fsyntax-only %s -verify
// RUN: %clang_cc1 -std=c++2c -triple=x86_64-linux -fsyntax-only %s -verify -fexperimental-new-constant-interpreter
static_assert(true, "");
static_assert(true, 0); // expected-error {{the message in a static assertion must be a string literal or an object with 'data()' and 'size()' member functions}}