Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions clang/lib/AST/ByteCode/Context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/AST/ByteCode/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ class Context final {
/// 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.
Expand Down Expand Up @@ -122,6 +127,10 @@ class Context final {
/// 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.
Expand Down
22 changes: 22 additions & 0 deletions clang/lib/AST/ByteCode/EvalEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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++; }
Expand Down Expand Up @@ -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))
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/AST/ByteCode/EvalEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,14 @@ class EvalEmitter : public SourceMapper {
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();
Expand Down Expand Up @@ -101,6 +104,8 @@ class EvalEmitter : public SourceMapper {
/// 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;
Expand Down
6 changes: 1 addition & 5 deletions clang/lib/AST/ByteCode/EvaluationResult.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,6 @@ class EvaluationResult final {
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;
Expand All @@ -88,6 +83,7 @@ class EvaluationResult final {
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.
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/AST/ByteCode/Pointer.h
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,13 @@ class Pointer {

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())
Expand Down
6 changes: 5 additions & 1 deletion clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18017,10 +18017,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))
Expand Down
1 change: 1 addition & 0 deletions clang/test/SemaCXX/gnu-asm-constexpr.cpp
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
1 change: 1 addition & 0 deletions clang/test/SemaCXX/static-assert-cxx26.cpp
Original file line number Diff line number Diff line change
@@ -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}}
Expand Down