[clang] [clang][bytecode] Add `StringPointer` (PR #216736)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Wed Aug 19 00:46:59 PDT 2026
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/216736
>From 152277d776ea5152c94ce0d6bf9217e332d68b5f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Fri, 14 Aug 2026 16:46:31 +0200
Subject: [PATCH] StringPOinter
---
clang/lib/AST/ByteCode/Compiler.cpp | 17 +-
clang/lib/AST/ByteCode/Context.cpp | 68 ++++++--
clang/lib/AST/ByteCode/EvalEmitter.cpp | 4 -
clang/lib/AST/ByteCode/Integral.h | 8 +
clang/lib/AST/ByteCode/Interp.cpp | 36 ++---
clang/lib/AST/ByteCode/Interp.h | 64 +++++---
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 150 +++++++++++-------
.../lib/AST/ByteCode/InterpBuiltinBitCast.cpp | 94 +++++++++--
clang/lib/AST/ByteCode/InterpHelpers.h | 14 +-
clang/lib/AST/ByteCode/InterpState.h | 6 +-
clang/lib/AST/ByteCode/Opcodes.td | 4 +
clang/lib/AST/ByteCode/Pointer.cpp | 47 ++++--
clang/lib/AST/ByteCode/Pointer.h | 144 ++++++++++++++++-
clang/lib/AST/ByteCode/PrimType.h | 10 ++
clang/lib/AST/ByteCode/Primitives.h | 2 +
clang/lib/AST/ByteCode/Program.cpp | 51 ------
clang/lib/AST/ByteCode/Program.h | 4 -
clang/test/AST/ByteCode/builtin-functions.cpp | 2 +-
clang/test/AST/ByteCode/cxx20.cpp | 4 +-
clang/test/AST/ByteCode/strings.cpp | 46 ++++++
clang/unittests/AST/ByteCode/Pointer.cpp | 61 +++++++
21 files changed, 614 insertions(+), 222 deletions(-)
create mode 100644 clang/test/AST/ByteCode/strings.cpp
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index a8e2cb4a3c076..9627cc65933d2 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -3113,10 +3113,8 @@ bool Compiler<Emitter>::VisitStringLiteral(const StringLiteral *E) {
if (DiscardResult)
return true;
- if (!Initializing) {
- unsigned StringIndex = P.createGlobalString(E);
- return this->emitGetPtrGlobal(StringIndex, E);
- }
+ if (!Initializing)
+ return this->emitGetStringPtr(E, E);
// We are initializing an array on the stack.
const ConstantArrayType *CAT =
@@ -3202,9 +3200,7 @@ bool Compiler<Emitter>::VisitSYCLUniqueStableNameExpr(
StringLiteral *SL =
StringLiteral::Create(A, ResultStr, StringLiteralKind::Ordinary,
/*Pascal=*/false, ArrayTy, E->getLocation());
-
- unsigned StringIndex = P.createGlobalString(SL);
- return this->emitGetPtrGlobal(StringIndex, E);
+ return this->emitGetStringPtr(SL, E);
}
template <class Emitter>
@@ -3734,11 +3730,8 @@ bool Compiler<Emitter>::VisitPredefinedExpr(const PredefinedExpr *E) {
if (DiscardResult)
return true;
- if (!Initializing) {
- unsigned StringIndex = P.createGlobalString(E->getFunctionName(), E);
- return this->emitGetPtrGlobal(StringIndex, E);
- }
-
+ if (!Initializing)
+ return this->emitGetStringPtr(E, E);
return this->delegate(E->getFunctionName());
}
diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp
index ce7a95ed49c06..de537aad6779e 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -209,17 +209,23 @@ bool Context::evaluateStringRepr(State &Parent, const Expr *SizeExpr,
}
if (!Ptr.isLive() || !Ptr.isInitialized() || Ptr.isUnknownSizeArray() ||
- !Ptr.getFieldDesc()->isPrimitiveArray())
+ !Ptr.inArray())
return false;
// Must be char.
- if (Ptr.getFieldDesc()->getElemDataSize() != 1 /*bytes*/)
+ if (Ptr.isBlockPointer() &&
+ Ptr.getFieldDesc()->getElemDataSize() != 1 /*bytes*/)
+ return false;
+ if (Ptr.isStringPointer() &&
+ !Ptr.asStringPointer().getLiteral()->isOrdinary())
return false;
+ bool Limited = false;
if (Size > Ptr.getNumElems()) {
S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_past_end)
<< AK_Read;
Size = Ptr.getNumElems();
+ Limited = true;
}
if constexpr (std::is_same_v<ResultT, APValue>) {
@@ -236,7 +242,13 @@ bool Context::evaluateStringRepr(State &Parent, const Expr *SizeExpr,
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);
+
+ const char *Addr = reinterpret_cast<const char *>(Ptr.getRawAddress());
+
+ if (Ptr.isStringPointer())
+ Result.assign(Addr, Size - static_cast<unsigned>(Limited));
+ else
+ Result.assign(Addr, Size);
}
return true;
@@ -274,11 +286,7 @@ bool Context::evaluateString(State &Parent, const Expr *E,
auto PtrRes = C.interpretAsPointer(E, [&](InterpState &S, CodePtr OpPC,
const Pointer &Ptr) {
- if (!Ptr.isBlockPointer())
- return false;
-
- const Descriptor *FieldDesc = Ptr.getFieldDesc();
- if (!FieldDesc->isPrimitiveArray())
+ if (!Ptr.isReadablePointerType())
return false;
if (!Ptr.isConst())
@@ -288,6 +296,10 @@ bool Context::evaluateString(State &Parent, const Expr *E,
if (Ptr.elemSize() == 1 /* bytes */) {
const char *Chars = reinterpret_cast<const char *>(Ptr.getRawAddress());
+ if (Ptr.isStringPointer()) {
+ Result.assign(Chars, N - 1);
+ return true;
+ }
unsigned Length = strnlen(Chars, N);
// Wasn't null terminated.
if (N == Length)
@@ -296,10 +308,22 @@ bool Context::evaluateString(State &Parent, const Expr *E,
return true;
}
- PrimType ElemT = FieldDesc->getPrimType();
+ PrimType ElemT;
+ if (Ptr.isBlockPointer()) {
+ ElemT = Ptr.getFieldDesc()->getPrimType();
+ } else {
+ // It may happen here that the string literal has not been decayed or
+ // indexed, so check the element type in that case.
+ assert(Ptr.isStringPointer());
+ if (!Ptr.asStringPointer().Decayed)
+ ElemT =
+ *classify(Ptr.getType()->getAsArrayTypeUnsafe()->getElementType());
+ else
+ ElemT = *classify(Ptr.getType());
+ }
for (unsigned I = Ptr.getIndex(); I != N; ++I) {
INT_TYPE_SWITCH(ElemT, {
- auto Elem = Ptr.elem<T>(I);
+ auto Elem = Ptr.loadElem<T>(I);
if (Elem.isZero())
return true;
Result.push_back(static_cast<char>(Elem));
@@ -324,14 +348,34 @@ std::optional<uint64_t> Context::evaluateStrlen(State &Parent, const Expr *E) {
std::optional<uint64_t> Result;
auto PtrRes = C.interpretAsPointer(E, [&](InterpState &S, CodePtr OpPC,
const Pointer &Ptr) {
- if (!Ptr.isBlockPointer())
+ if (!Ptr.isReadablePointerType())
+ return false;
+
+ if (Ptr.isPastEnd())
return false;
+ if (Ptr.isStringPointer()) {
+
+ const auto *Lit = Ptr.asStringPointer().getLiteral();
+ int64_t Off = Ptr.getByteOffset();
+ if (Off < 0)
+ return false;
+
+ unsigned Length = 0;
+ for (uint64_t I = Off; I != Lit->getLength(); ++I) {
+ if (Lit->getCodeUnit(I) == 0)
+ break;
+ ++Length;
+ }
+ Result = Length;
+ return true;
+ }
+
const Descriptor *FieldDesc = Ptr.getFieldDesc();
if (!FieldDesc->isPrimitiveArray())
return false;
- if (Ptr.isDummy() || Ptr.isUnknownSizeArray() || Ptr.isPastEnd())
+ if (Ptr.isDummy() || Ptr.isUnknownSizeArray())
return false;
PrimType ElemT = FieldDesc->getPrimType();
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp b/clang/lib/AST/ByteCode/EvalEmitter.cpp
index a54b6d2c18fa9..124366194d6bb 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.cpp
+++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp
@@ -243,7 +243,6 @@ template <PrimType OpType> bool EvalEmitter::emitRet(SourceInfo Info) {
}
template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) {
- // llvm::errs()<< __PRETTY_FUNCTION__ << "Ret\n";
if (!isActive())
return true;
@@ -268,9 +267,6 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) {
if (Ptr.isPastEnd())
return false;
- if (Ptr.pointsToStringLiteral() && Ptr.isArrayRoot())
- return false;
-
if (!Ptr.isZero() && !CheckFinalLoad(S, CodePtr(), Ptr))
return false;
diff --git a/clang/lib/AST/ByteCode/Integral.h b/clang/lib/AST/ByteCode/Integral.h
index 170bc8f8f12dd..543d7f7fd43a9 100644
--- a/clang/lib/AST/ByteCode/Integral.h
+++ b/clang/lib/AST/ByteCode/Integral.h
@@ -205,6 +205,10 @@ template <unsigned Bits, bool Signed> class Integral final {
CharUnits::fromQuantity(Ptr.Offset),
APValue::NoLValuePath{});
}
+ case IntegralKind::ExprAddress: {
+ return APValue((const Expr *)Ptr.P, CharUnits::fromQuantity(Ptr.Offset),
+ APValue::NoLValuePath{});
+ }
case IntegralKind::LabelAddress: {
return APValue((const Expr *)Ptr.P, CharUnits::Zero(),
APValue::NoLValuePath{});
@@ -300,6 +304,9 @@ template <unsigned Bits, bool Signed> class Integral final {
case IntegralKind::Address:
OS << Ptr.P << " + " << Ptr.Offset << " (Address)";
break;
+ case IntegralKind::ExprAddress:
+ OS << Ptr.P << " + " << Ptr.Offset << " (ExprAddress)";
+ break;
case IntegralKind::BlockAddress:
OS << Ptr.P << " + " << Ptr.Offset << " (BlockAddress)";
break;
@@ -333,6 +340,7 @@ template <unsigned Bits, bool Signed> class Integral final {
case IntegralKind::AddrLabelDiff:
return Integral(V.getLabel1(), V.getLabel2());
case IntegralKind::Address:
+ case IntegralKind::ExprAddress:
case IntegralKind::BlockAddress:
case IntegralKind::LabelAddress:
case IntegralKind::FunctionAddress:
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 43020f5ad84c1..41b062e239f12 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -23,6 +23,7 @@
#include "clang/AST/ExprCXX.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/TargetInfo.h"
+#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringExtras.h"
using namespace clang;
@@ -541,17 +542,6 @@ bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
return false;
}
-bool CheckRange(InterpState &S, CodePtr OpPC, PtrView Ptr, AccessKinds AK) {
- if (!Ptr.isOnePastEnd() && !Ptr.isZeroSizeArray())
- return true;
- if (S.getLangOpts().CPlusPlus) {
- const SourceInfo &Loc = S.Current->getSource(OpPC);
- S.FFDiag(Loc, diag::note_constexpr_access_past_end)
- << AK << S.Current->getRange(OpPC);
- }
- return false;
-}
-
bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
CheckSubobjectKind CSK) {
if (!Ptr.isElementPastEnd() && !Ptr.isZeroSizeArray())
@@ -897,11 +887,11 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
S.FFDiag(Src, diag::note_constexpr_access_null) << AK;
return false;
}
- // Block pointers are the only ones we can actually read from.
- if (!Ptr.isBlockPointer())
+ // Block and string pointers are the only ones we can actually read from.
+ if (!Ptr.isReadablePointerType())
return false;
- if (!Ptr.block()->isAccessible()) {
+ if (Ptr.isBlockPointer() && !Ptr.block()->isAccessible()) {
if (!CheckLive(S, OpPC, Ptr, AK))
return false;
if (!CheckExtern(S, OpPC, Ptr))
@@ -921,7 +911,7 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
return diagnoseUninitialized(S, OpPC, Ptr, AK);
if (!CheckLifetime(S, OpPC, Ptr, AK))
return false;
- if (!CheckTemporary(S, OpPC, Ptr.block(), AK))
+ if (Ptr.isBlockPointer() && !CheckTemporary(S, OpPC, Ptr.block(), AK))
return false;
if (!CheckMutable(S, OpPC, Ptr))
@@ -931,7 +921,7 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
if (isConstexprUnknown(Ptr))
return false;
- if (!Ptr.isArrayRoot()) {
+ if (Ptr.isBlockPointer() && !Ptr.isArrayRoot()) {
// According to GCC info page:
//
// 6.28 Compound Literals
@@ -965,10 +955,10 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
/// EvalEmitter to do the final lvalue-to-rvalue conversion.
bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
assert(!Ptr.isZero());
- if (!Ptr.isBlockPointer())
+ if (!Ptr.isReadablePointerType())
return false;
- if (!Ptr.block()->isAccessible()) {
+ if (Ptr.isBlockPointer() && !Ptr.block()->isAccessible()) {
if (!CheckLive(S, OpPC, Ptr, AK_Read))
return false;
if (!CheckExtern(S, OpPC, Ptr))
@@ -987,7 +977,7 @@ bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
return false;
if (!Ptr.isInitialized())
return diagnoseUninitialized(S, OpPC, Ptr, AK_Read);
- if (!CheckTemporary(S, OpPC, Ptr.block(), AK_Read))
+ if (Ptr.isBlockPointer() && !CheckTemporary(S, OpPC, Ptr.block(), AK_Read))
return false;
if (!CheckMutable(S, OpPC, Ptr))
return false;
@@ -2851,8 +2841,8 @@ bool DiagTypeid(InterpState &S, CodePtr OpPC) {
bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
const Pointer &RHS) {
- if (!LHS.pointsToStringLiteral() || !RHS.pointsToStringLiteral())
- return false;
+ assert(LHS.isStringPointer());
+ assert(RHS.isStringPointer());
unsigned LHSOffset = LHS.isOnePastEnd() ? LHS.getNumElems() : LHS.getIndex();
unsigned RHSOffset = RHS.isOnePastEnd() ? RHS.getNumElems() : RHS.getIndex();
@@ -2879,11 +2869,11 @@ bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS,
StringRef Shorter;
StringRef Longer;
if (LHSLength < RHSLength) {
- ShorterCharWidth = LHS.getFieldDesc()->getElemDataSize();
+ ShorterCharWidth = LHSLit->getCharByteWidth();
Shorter = LHSStr;
Longer = RHSStr;
} else {
- ShorterCharWidth = RHS.getFieldDesc()->getElemDataSize();
+ ShorterCharWidth = RHSLit->getCharByteWidth();
Shorter = RHSStr;
Longer = LHSStr;
}
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 9c90321da1c36..74d45b4f54917 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -34,7 +34,6 @@
#include "clang/AST/Expr.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APSInt.h"
-#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/Compiler.h"
#include <type_traits>
@@ -1359,18 +1358,6 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
return true;
}
- // FIXME: The source check here isn't entirely correct.
- if (LHS.pointsToStringLiteral() && RHS.pointsToStringLiteral() &&
- LHS.getFieldDesc()->asExpr() != RHS.getFieldDesc()->asExpr()) {
- if (arePotentiallyOverlappingStringLiterals(LHS, RHS)) {
- const SourceInfo &Loc = S.Current->getSource(OpPC);
- S.FFDiag(Loc, diag::note_constexpr_literal_comparison)
- << LHS.toDiagnosticString(S.getASTContext())
- << RHS.toDiagnosticString(S.getASTContext());
- return false;
- }
- }
-
if (Pointer::hasSameBase(LHS, RHS)) {
std::optional<size_t> A = LHS.computeOffsetForComparison(S.getASTContext());
std::optional<size_t> B = RHS.computeOffsetForComparison(S.getASTContext());
@@ -1380,17 +1367,24 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
S.Stk.push<BoolT>(BoolT::from(Fn(Compare(*A, *B))));
return true;
}
-
// Otherwise we need to do a bunch of extra checks before returning Unordered.
- if (LHS.isOnePastEnd() && !RHS.isOnePastEnd() && RHS.isBlockPointer() &&
- RHS.getOffset() == 0) {
+
+ if (LHS.isStringPointer() && RHS.isStringPointer() &&
+ arePotentiallyOverlappingStringLiterals(LHS, RHS)) {
+ S.FFDiag(S.Current->getSource(OpPC),
+ diag::note_constexpr_literal_comparison)
+ << LHS.toDiagnosticString(S.getASTContext())
+ << RHS.toDiagnosticString(S.getASTContext());
+ return false;
+ }
+
+ if (LHS.isOnePastEnd() && !RHS.isOnePastEnd()) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_past_end)
<< LHS.toDiagnosticString(S.getASTContext());
return false;
}
- if (RHS.isOnePastEnd() && !LHS.isOnePastEnd() && LHS.isBlockPointer() &&
- LHS.getOffset() == 0) {
+ if (RHS.isOnePastEnd() && !LHS.isOnePastEnd()) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_pointer_comparison_past_end)
<< RHS.toDiagnosticString(S.getASTContext());
@@ -2241,11 +2235,11 @@ bool Load(InterpState &S, CodePtr OpPC) {
const Pointer &Ptr = S.Stk.peek<Pointer>();
if (!CheckLoad(S, OpPC, Ptr))
return false;
- if (!Ptr.isBlockPointer())
+ if (!Ptr.isReadablePointerType())
return false;
if (!Ptr.canDeref(Name))
return false;
- S.Stk.push<T>(Ptr.deref<T>());
+ S.Stk.push<T>(Ptr.load<T>());
return true;
}
@@ -2254,11 +2248,11 @@ bool LoadPop(InterpState &S, CodePtr OpPC) {
const Pointer &Ptr = S.Stk.pop<Pointer>();
if (!CheckLoad(S, OpPC, Ptr))
return false;
- if (!Ptr.isBlockPointer())
+ if (!Ptr.isReadablePointerType())
return false;
if (!Ptr.canDeref(Name))
return false;
- S.Stk.push<T>(Ptr.deref<T>());
+ S.Stk.push<T>(Ptr.load<T>());
return true;
}
@@ -2582,6 +2576,20 @@ std::optional<Pointer> OffsetHelper(InterpState &S, CodePtr OpPC,
S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
<< N << /*non-array*/ true << 0;
return Pointer(Ptr.asFunctionPointer().Func, N);
+ } else if (Ptr.isStringPointer()) {
+ int64_t NewOffset;
+ if constexpr (Op == ArithOp::Add)
+ NewOffset = Ptr.getRawOffset() + static_cast<int64_t>(Offset);
+ else
+ NewOffset = Ptr.getRawOffset() - static_cast<int64_t>(Offset);
+ if (NewOffset < 0 ||
+ NewOffset > (Ptr.asStringPointer().getLiteral()->getLength() + 1)) {
+ S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
+ << NewOffset << /*non-array*/ false
+ << (Ptr.asStringPointer().getLiteral()->getLength() + 1);
+ return std::nullopt;
+ }
+ return Pointer(Ptr.asStringPointer(), NewOffset);
} else if (!Ptr.isBlockPointer()) {
return std::nullopt;
}
@@ -3020,6 +3028,9 @@ bool CastPointerIntegral(InterpState &S, CodePtr OpPC) {
} else if (Ptr.isFunctionPointer()) {
const void *FuncDecl = Ptr.asFunctionPointer().Func->getDecl();
S.Stk.push<T>(IntegralKind::FunctionAddress, FuncDecl, /*Offset=*/0);
+ } else if (Ptr.isStringPointer()) {
+ S.Stk.push<T>(IntegralKind::ExprAddress,
+ (const void *)Ptr.asStringPointer().getLiteral(), 0);
} else {
S.Stk.push<T>(T::from(Ptr.getIntegerRepresentation()));
}
@@ -3571,6 +3582,10 @@ inline bool ArrayDecay(InterpState &S, CodePtr OpPC) {
}
if (Ptr.isRoot() || !Ptr.isUnknownSizeArray()) {
+ if (Ptr.isStringPointer()) {
+ S.Stk.push<Pointer>(Ptr.asStringPointer().decay());
+ return true;
+ }
S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
return true;
}
@@ -3622,6 +3637,11 @@ inline bool GetIntPtr(InterpState &S, CodePtr OpPC, const Type *Ty) {
return true;
}
+inline bool GetStringPtr(InterpState &S, const Expr *Base) {
+ S.Stk.push<Pointer>(Base, S.newStringID());
+ return true;
+}
+
bool GetMemberPtr(InterpState &S, const ValueDecl *D);
bool GetMemberPtrBase(InterpState &S);
bool GetMemberPtrDecl(InterpState &S);
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 9386a12ac13c2..12bacaeee5f2a 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -81,7 +81,7 @@ static bool popToAPSInt(InterpState &S, QualType T, APSInt &Out) {
static bool isReadable(const Pointer &P) {
if (P.isDummy())
return false;
- if (!P.isBlockPointer())
+ if (!P.isReadablePointerType())
return false;
if (!P.isLive())
return false;
@@ -157,6 +157,14 @@ static void assignIntegral(InterpState &S, const Pointer &Dest, PrimType ValueT,
}
static QualType getElemType(const Pointer &P) {
+ if (P.isStringPointer()) {
+ return P.asStringPointer()
+ .getLiteral()
+ ->getType()
+ ->getAsArrayTypeUnsafe()
+ ->getElementType();
+ }
+
const Descriptor *Desc = P.getFieldDesc();
QualType T = Desc->getType();
if (Desc->isPrimitive())
@@ -296,25 +304,21 @@ static bool interp__builtin_strcmp(InterpState &S, CodePtr OpPC,
if (!CheckLive(S, OpPC, A, AK_Read) || !CheckLive(S, OpPC, B, AK_Read))
return false;
- if (A.isDummy() || B.isDummy())
- return false;
- if (!A.isBlockPointer() || !B.isBlockPointer())
+ if (!A.isReadablePointerType() || !B.isReadablePointerType())
return false;
- if (!A.getFieldDesc()->isPrimitiveArray() ||
- !B.getFieldDesc()->isPrimitiveArray())
+ if (A.isDummy() || B.isDummy())
return false;
bool IsWide = ID == Builtin::BIwcscmp || ID == Builtin::BIwcsncmp ||
ID == Builtin::BI__builtin_wcscmp ||
ID == Builtin::BI__builtin_wcsncmp;
- assert(A.getFieldDesc()->isPrimitiveArray());
- assert(B.getFieldDesc()->isPrimitiveArray());
+ QualType ElemTy = getElemType(A);
// Different element types shouldn't happen, but with casts they can.
- if (!S.getASTContext().hasSameUnqualifiedType(getElemType(A), getElemType(B)))
+ if (!S.getASTContext().hasSameUnqualifiedType(ElemTy, getElemType(B)))
return false;
- PrimType ElemT = *S.getContext().classify(getElemType(A));
+ PrimType ElemT = *S.getContext().classify(ElemTy);
auto returnResult = [&](int V) -> bool {
pushInteger(S, V, Call->getType());
@@ -323,22 +327,25 @@ static bool interp__builtin_strcmp(InterpState &S, CodePtr OpPC,
unsigned IndexA = A.getIndex();
unsigned IndexB = B.getIndex();
+ unsigned NumElemsA = A.getNumElems();
+ unsigned NumElemsB = B.getNumElems();
uint64_t Steps = 0;
for (;; ++IndexA, ++IndexB, ++Steps) {
if (Steps >= Limit)
break;
- PtrView PA = A.view().atIndex(IndexA);
- PtrView PB = B.view().atIndex(IndexB);
- if (!CheckRange(S, OpPC, PA, AK_Read) ||
- !CheckRange(S, OpPC, PB, AK_Read)) {
+
+ // Diagnose this as a read of one-past-the-end.
+ if (IndexA >= NumElemsA || IndexB >= NumElemsB) {
+ S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_past_end)
+ << AK_Read << S.Current->getRange(OpPC);
return false;
}
if (IsWide) {
INT_TYPE_SWITCH(ElemT, {
- T CA = PA.deref<T>();
- T CB = PB.deref<T>();
+ T CA = A.loadElem<T>(IndexA);
+ T CB = B.loadElem<T>(IndexB);
if (CA > CB)
return returnResult(1);
if (CA < CB)
@@ -349,8 +356,8 @@ static bool interp__builtin_strcmp(InterpState &S, CodePtr OpPC,
continue;
}
- uint8_t CA = PA.deref<uint8_t>();
- uint8_t CB = PB.deref<uint8_t>();
+ uint8_t CA = A.loadElem<uint8_t>(IndexA);
+ uint8_t CB = B.loadElem<uint8_t>(IndexB);
if (CA > CB)
return returnResult(1);
@@ -380,6 +387,27 @@ static bool interp__builtin_strlen(InterpState &S, CodePtr OpPC,
if (!CheckLive(S, OpPC, StrPtr, AK_Read))
return false;
+ // For string literal pointers, this is pretty simple.
+ if (StrPtr.isStringPointer()) {
+ if (StrPtr.isOnePastEnd())
+ return CheckRange(S, OpPC, StrPtr, AK_Read);
+
+ const auto *Lit = StrPtr.asStringPointer().getLiteral();
+ int64_t Off = StrPtr.getByteOffset();
+ if (Off < 0)
+ return false;
+
+ unsigned Length = 0;
+ for (uint64_t I = Off; I != Lit->getLength(); ++I) {
+ if (Lit->getCodeUnit(I) == 0)
+ break;
+ ++Length;
+ }
+
+ pushInteger(S, Length, Call->getType());
+ return true;
+ }
+
if (!StrPtr.isBlockPointer())
return false;
@@ -429,34 +457,47 @@ static bool interp__builtin_nan(InterpState &S, CodePtr OpPC,
if (!CheckLoad(S, OpPC, Arg))
return false;
- if (!Arg.getFieldDesc()->isPrimitiveArray())
- return Invalid(S, OpPC);
-
// Convert the given string to an integer using StringRef's API.
llvm::APInt Fill;
- std::string Str;
- unsigned ArgLength = Arg.getNumElems();
- bool FoundZero = false;
- for (unsigned I = 0; I != ArgLength; ++I) {
- if (!Arg.isElementInitialized(I))
- return false;
+ if (Arg.isBlockPointer()) {
+ if (!Arg.getFieldDesc()->isPrimitiveArray())
+ return Invalid(S, OpPC);
+
+ std::string Str;
+ unsigned ArgLength = Arg.getNumElems();
+ bool FoundZero = false;
+ for (unsigned I = 0; I != ArgLength; ++I) {
+ if (!Arg.isElementInitialized(I))
+ return false;
- if (Arg.elem<int8_t>(I) == 0) {
- FoundZero = true;
- break;
+ if (Arg.loadElem<int8_t>(I) == 0) {
+ FoundZero = true;
+ break;
+ }
+ Str += Arg.elem<char>(I);
}
- Str += Arg.elem<char>(I);
- }
- // If we didn't find a NUL byte, diagnose as a one-past-the-end read.
- if (!FoundZero)
- return CheckRange(S, OpPC, Arg.atIndex(ArgLength), AK_Read);
+ // If we didn't find a NUL byte, diagnose as a one-past-the-end read.
+ if (!FoundZero)
+ return CheckRange(S, OpPC, Arg.atIndex(ArgLength), AK_Read);
- // Treat empty strings as if they were zero.
- if (Str.empty())
- Fill = llvm::APInt(32, 0);
- else if (StringRef(Str).getAsInteger(0, Fill))
+ // Treat empty strings as if they were zero.
+ if (Str.empty())
+ Fill = llvm::APInt(32, 0);
+ else if (StringRef(Str).getAsInteger(0, Fill))
+ return false;
+ } else if (Arg.isStringPointer()) {
+ if (!Arg.asStringPointer().getLiteral()->isOrdinary())
+ return false;
+ StringRef Str = Arg.asStringPointer().getLiteral()->getString();
+ // Treat empty strings as if they were zero.
+ if (Str.empty())
+ Fill = llvm::APInt(32, 0);
+ else if (StringRef(Str).getAsInteger(0, Fill))
+ return false;
+ } else {
return false;
+ }
const llvm::fltSemantics &TargetSemantics =
S.getASTContext().getFloatTypeSemantics(
@@ -1279,8 +1320,11 @@ static bool interp__builtin_is_aligned_up_down(InterpState &S, CodePtr OpPC,
}
assert(FirstArgT == PT_Ptr);
const Pointer &Ptr = S.Stk.pop<Pointer>();
- if (!Ptr.isBlockPointer())
+ if (!Ptr.isBlockPointer()) {
+ S.FFDiag(Call->getArg(0), diag::note_constexpr_alignment_compute)
+ << Alignment;
return false;
+ }
const ValueDecl *PtrDecl = Ptr.getDeclDesc()->asValueDecl();
// We need a pointer for a declaration here.
@@ -1469,13 +1513,11 @@ interp__builtin_ptrauth_string_discriminator(InterpState &S, CodePtr OpPC,
const InterpFrame *Frame,
const CallExpr *Call) {
const auto &Ptr = S.Stk.pop<Pointer>();
- assert(Ptr.getFieldDesc()->isPrimitiveArray());
+ if (!Ptr.isStringPointer())
+ return false;
- // This should be created for a StringLiteral, so always holds at least
- // one array element.
- assert(Ptr.getFieldDesc()->getNumElems() >= 1);
uint64_t Result = getPointerAuthStableSipHash(
- cast<StringLiteral>(Ptr.getFieldDesc()->asExpr())->getString());
+ cast<StringLiteral>(Ptr.getRootExpr())->getString());
pushInteger(S, Result, Call->getType());
return true;
}
@@ -1973,7 +2015,7 @@ static bool interp__builtin_memcpy(InterpState &S, CodePtr OpPC,
}
size_t RemainingDestElems;
- if (DestPtr.getFieldDesc()->isArray()) {
+ if (DestPtr.inArray()) {
RemainingDestElems = DestPtr.isUnknownSizeArray()
? 0
: (DestPtr.getNumElems() - DestPtr.getIndex());
@@ -1997,7 +2039,7 @@ static bool interp__builtin_memcpy(InterpState &S, CodePtr OpPC,
QualType SrcElemType = getElemType(SrcPtr);
size_t RemainingSrcElems;
- if (SrcPtr.getFieldDesc()->isArray()) {
+ if (SrcPtr.inArray()) {
RemainingSrcElems = SrcPtr.isUnknownSizeArray()
? 0
: (SrcPtr.getNumElems() - SrcPtr.getIndex());
@@ -2080,7 +2122,7 @@ static bool interp__builtin_memcmp(InterpState &S, CodePtr OpPC,
return true;
}
- if (!PtrA.isBlockPointer() || !PtrB.isBlockPointer())
+ if (!PtrA.isReadablePointerType() || !PtrB.isReadablePointerType())
return false;
bool IsWide =
@@ -2106,7 +2148,7 @@ static bool interp__builtin_memcmp(InterpState &S, CodePtr OpPC,
// Now, read both pointers to a buffer and compare those.
BitcastBuffer BufferA(
Bits(ASTCtx.getTypeSize(ElemTypeA) * PtrA.getNumElems()));
- readPointerToBuffer(S.getContext(), PtrA, BufferA, false);
+ readPointerToBuffer(S.getContext(), PtrA, BufferA, /*ReturnOnUninit=*/false);
// FIXME: The swapping here is UNDOING something we do when reading the
// data into the buffer.
@@ -2115,7 +2157,7 @@ static bool interp__builtin_memcmp(InterpState &S, CodePtr OpPC,
BitcastBuffer BufferB(
Bits(ASTCtx.getTypeSize(ElemTypeB) * PtrB.getNumElems()));
- readPointerToBuffer(S.getContext(), PtrB, BufferB, false);
+ readPointerToBuffer(S.getContext(), PtrB, BufferB, /*ReturnOnUninit=*/false);
// FIXME: The swapping here is UNDOING something we do when reading the
// data into the buffer.
if (ASTCtx.getTargetInfo().isBigEndian())
@@ -2219,12 +2261,10 @@ static bool interp__builtin_memchr(InterpState &S, CodePtr OpPC,
return false;
}
- if (!Ptr.isBlockPointer())
+ if (!Ptr.isReadablePointerType())
return false;
- QualType ElemTy = Ptr.getFieldDesc()->isArray()
- ? Ptr.getFieldDesc()->getElemQualType()
- : Ptr.getFieldDesc()->getType();
+ QualType ElemTy = getElemType(Ptr);
bool IsRawByte = ID == Builtin::BImemchr || ID == Builtin::BI__builtin_memchr;
// Give up on byte-oriented matching against multibyte elements.
@@ -2281,7 +2321,7 @@ static bool interp__builtin_memchr(InterpState &S, CodePtr OpPC,
uint64_t V;
INT_TYPE_SWITCH_NO_BOOL(
- ElemT, { V = static_cast<uint64_t>(ElemPtr.deref<T>().toUnsigned()); });
+ ElemT, { V = static_cast<uint64_t>(ElemPtr.load<T>().toUnsigned()); });
if (V == DesiredVal) {
S.Stk.push<Pointer>(ElemPtr);
diff --git a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
index 2529bfc6c1cbd..6444deef9fc92 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
@@ -78,6 +78,53 @@ using DataFunc =
} \
} while (0)
+// FIXME: It is unfortunate that we have this function at all, but we can read
+// from a StringPointer. In the later callback-based reading and writing paths,
+// we do assume a BlockPointer though.
+static std::pair<Block *, std::unique_ptr<Descriptor>>
+convertToBlockPointer(const Context &Ctx, const StringPointer &SP) {
+ const StringLiteral *S = SP.getLiteral();
+ const size_t CharWidth = S->getCharByteWidth();
+ const size_t BitWidth = CharWidth * Ctx.getCharBit();
+ unsigned StringLength = S->getLength();
+
+ OptPrimType CharType =
+ Ctx.classify(S->getType()->castAsArrayTypeUnsafe()->getElementType());
+ assert(CharType);
+
+ // Create a descriptor for the string.
+ std::unique_ptr<Descriptor> Desc =
+ std::make_unique<Descriptor>(S, S->getType().getTypePtr(), *CharType,
+ Descriptor::GlobalMD, StringLength + 1,
+ /*IsConst=*/true,
+ /*isTemporary=*/false,
+ /*isMutable=*/false,
+ /*IsVolatile=*/false);
+
+ // Allocate storage for the string.
+ // The byte length does not include the null terminator.
+ // unsigned GlobalIndex = Globals.size();
+ auto *Memory = new std::byte[sizeof(Block) + Desc->getAllocSize()];
+ auto *B = new (Memory) Block(Ctx.getEvalID(), Desc.get(), true, false);
+ B->invokeCtor();
+
+ new (B->rawData()) GlobalInlineDescriptor{GlobalInitState::Initialized};
+
+ Pointer Ptr(B);
+ if (CharWidth == 1) {
+ std::memcpy(&Ptr.elem<char>(0), S->getString().data(), StringLength);
+ } else {
+ // Construct the string in storage.
+ for (unsigned I = 0; I <= StringLength; ++I) {
+ uint32_t CodePoint = I == StringLength ? 0 : S->getCodeUnit(I);
+ INT_TYPE_SWITCH_NO_BOOL(*CharType,
+ Ptr.elem<T>(I) = T::from(CodePoint, BitWidth););
+ }
+ }
+ Ptr.initializeAllElements();
+ return std::make_pair(std::move(B), std::move(Desc));
+}
+
/// We use this to recursively iterate over all fields and elements of a pointer
/// and extract relevant data for a bitcast.
static Result enumerateData(PtrView P, const Context &Ctx, Bits Offset,
@@ -182,6 +229,17 @@ static bool enumeratePointerFields(const Pointer &P, const Context &Ctx,
Bits BitsToRead, DataFunc F,
bool Initialize) {
+ if (P.isStringPointer()) {
+ auto [B, Desc] = convertToBlockPointer(Ctx, P.asStringPointer());
+
+ if (enumerateData(Pointer(B).atIndex(P.getIndex()).view(), Ctx,
+ Bits::zero(), BitsToRead, F,
+ Initialize) == Result::Failure)
+ return false;
+ delete[] B;
+ return true;
+ }
+
return enumerateData(P.view(), Ctx, Bits::zero(), BitsToRead, F,
Initialize) != Result::Failure;
}
@@ -525,18 +583,36 @@ using PrimTypeVariant =
bool clang::interp::DoMemcpy(InterpState &S, CodePtr OpPC,
const Pointer &SrcPtr, const Pointer &DestPtr,
Bits Size) {
- assert(SrcPtr.isBlockPointer());
+ assert(SrcPtr.isReadablePointerType());
assert(DestPtr.isBlockPointer());
llvm::SmallVector<PrimTypeVariant> Values;
- enumeratePointerFields(
- SrcPtr, S.getContext(), Size,
- [&](const PtrView P, PrimType T, Bits BitOffset, Bits FullBitWidth,
- bool PackedBools) -> Result {
- TYPE_SWITCH(T, { Values.push_back(P.deref<T>()); });
- return Result::Success;
- },
- false);
+
+ if (SrcPtr.isStringPointer()) {
+ const auto &SP = SrcPtr.asStringPointer();
+
+ auto [B, Desc] = convertToBlockPointer(S.getContext(), SP);
+ enumeratePointerFields(
+ Pointer(B).atIndex(SrcPtr.getIndex()), S.getContext(), Size,
+ [&](const PtrView P, PrimType T, Bits BitOffset, Bits FullBitWidth,
+ bool PackedBools) -> Result {
+ TYPE_SWITCH(T, { Values.push_back(P.deref<T>()); });
+ return Result::Success;
+ },
+ false);
+
+ delete[] B;
+ } else {
+
+ enumeratePointerFields(
+ SrcPtr, S.getContext(), Size,
+ [&](const PtrView P, PrimType T, Bits BitOffset, Bits FullBitWidth,
+ bool PackedBools) -> Result {
+ TYPE_SWITCH(T, { Values.push_back(P.deref<T>()); });
+ return Result::Success;
+ },
+ false);
+ }
unsigned ValueIndex = 0;
enumeratePointerFields(
diff --git a/clang/lib/AST/ByteCode/InterpHelpers.h b/clang/lib/AST/ByteCode/InterpHelpers.h
index 1df570ac971c4..61fc864ba62b2 100644
--- a/clang/lib/AST/ByteCode/InterpHelpers.h
+++ b/clang/lib/AST/ByteCode/InterpHelpers.h
@@ -43,14 +43,16 @@ bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
bool CheckDummy(InterpState &S, CodePtr OpPC, const Block *B, AccessKinds AK);
/// Checks if a pointer is in range.
-bool CheckRange(InterpState &S, CodePtr OpPC, PtrView Ptr, AccessKinds AK);
-inline bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
- AccessKinds AK) {
- if (!Ptr.isBlockPointer()) {
- assert(!Ptr.isOnePastEnd());
+template <typename T>
+bool CheckRange(InterpState &S, CodePtr OpPC, T Ptr, AccessKinds AK) {
+ if (!Ptr.isOnePastEnd() && !Ptr.isZeroSizeArray())
return true;
+ if (S.getLangOpts().CPlusPlus) {
+ const SourceInfo &Loc = S.Current->getSource(OpPC);
+ S.FFDiag(Loc, diag::note_constexpr_access_past_end)
+ << AK << S.Current->getRange(OpPC);
}
- return CheckRange(S, OpPC, Ptr.view(), AK);
+ return false;
}
/// Checks if a field from which a pointer is going to be derived is valid.
diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h
index 36302889a499a..8b3c2a0e7dd5a 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -157,12 +157,14 @@ class InterpState final : public State {
/// Return if we're checking if a global variable has a constant destructor
/// and the given pointer is pointing to the variable we're checking that for.
bool checkingConstantDestruction(const Pointer &Ptr) const {
- return checkingConstantDestruction(Ptr.getDeclDesc()->asVarDecl());
+ return checkingConstantDestruction(Ptr.getRootVarDecl());
}
bool checkingConstantDestruction(const VarDecl *VD) const {
return EvalKind == EvaluationKind::Dtor && VD == EvaluatingDecl;
}
+ unsigned newStringID() { return StringID++; }
+
private:
friend class EvaluationResult;
friend class InterpStateCCOverride;
@@ -199,6 +201,8 @@ class InterpState final : public State {
/// ID identifying this evaluation.
const unsigned EvalID;
+ unsigned StringID = 0;
+
EvaluationKind EvalKind = EvaluationKind::None;
/// Things needed to do speculative execution.
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 74d3cc84e06ab..6c7deafa4c4c3 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -1043,3 +1043,7 @@ def PushCC : SuccessOpcode {
def PopCC : SuccessOpcode;
def PushMSVCCE : SuccessOpcode;
def PopMSVCCE : SuccessOpcode;
+
+def GetStringPtr : SuccessOpcode {
+ let Args = [ArgExpr];
+}
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp
index 55903c4cff70c..ce5c9cc0a3f98 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -59,6 +59,9 @@ Pointer::Pointer(const Pointer &P)
case Storage::Typeid:
Typeid = P.Typeid;
break;
+ case Storage::String:
+ Str = P.Str;
+ break;
}
}
@@ -78,6 +81,8 @@ Pointer::Pointer(Pointer &&P) : Offset(P.Offset), StorageKind(P.StorageKind) {
case Storage::Typeid:
Typeid = P.Typeid;
break;
+ case Storage::String:
+ Str = P.Str;
}
}
@@ -127,6 +132,9 @@ Pointer &Pointer::operator=(const Pointer &P) {
break;
case Storage::Typeid:
Typeid = P.Typeid;
+ break;
+ case Storage::String:
+ Str = P.Str;
}
return *this;
}
@@ -166,6 +174,9 @@ Pointer &Pointer::operator=(Pointer &&P) {
break;
case Storage::Typeid:
Typeid = P.Typeid;
+ break;
+ case Storage::String:
+ Str = P.Str;
}
return *this;
}
@@ -206,6 +217,13 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const {
CharUnits::Zero(), {},
/*OnePastTheEnd=*/false, /*IsNull=*/false);
} break;
+ case Storage::String:
+ if (Offset != 0 || Str.Decayed)
+ Path.push_back(APValue::LValuePathEntry::ArrayIndex(Offset));
+
+ return APValue(APValue::LValueBase(Str.Base),
+ CharUnits::fromQuantity(Offset * elemSize()), Path,
+ /*OnePastTheEnd=*/false, /*IsNull=*/false);
}
assert(isBlockPointer());
@@ -366,6 +384,11 @@ void Pointer::print(llvm::raw_ostream &OS) const {
OS << "(Typeid) { " << (const void *)asTypeidPointer().TypePtr << ", "
<< (const void *)asTypeidPointer().TypeInfoType << " + " << Offset
<< "}";
+ break;
+ case Storage::String:
+ OS << "(String) { " << (const void *)Str.getLiteral() << ' ';
+ Str.getLiteral()->outputString(OS);
+ OS << ". ID: " << Str.ID << " + " << Offset << "}";
}
}
@@ -390,6 +413,8 @@ Pointer::computeOffsetForComparison(const ASTContext &ASTCtx) const {
return getIntegerRepresentation();
case Storage::Typeid:
return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset;
+ case Storage::String:
+ return reinterpret_cast<uintptr_t>(Str.getLiteral()) + Offset;
}
auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
@@ -468,6 +493,8 @@ Pointer::computeLayoutOffset(const ASTContext &ASTCtx) const {
return getIntegerRepresentation();
case Storage::Typeid:
return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset;
+ case Storage::String:
+ return Offset * Str.getLiteral()->getCharByteWidth();
}
auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
@@ -821,6 +848,8 @@ bool Pointer::hasSameBase(const Pointer &A, const Pointer &B) {
return true;
if (A.isTypeidPointer() && B.isTypeidPointer())
return A.asTypeidPointer().TypePtr == B.asTypeidPointer().TypePtr;
+ if (A.isStringPointer() && B.isStringPointer())
+ return A.Str.ID == B.Str.ID && A.Str.getLiteral() == B.Str.getLiteral();
if (A.StorageKind != B.StorageKind)
return false;
@@ -886,17 +915,6 @@ bool Pointer::pointsToLiteral() const {
return E && !isa<MaterializeTemporaryExpr, StringLiteral>(E);
}
-bool Pointer::pointsToStringLiteral() const {
- if (isZero() || !isBlockPointer())
- return false;
-
- if (block()->isDynamic())
- return false;
-
- const Expr *E = block()->getDescriptor()->asExpr();
- return isa_and_nonnull<StringLiteral>(E);
-}
-
bool Pointer::pointsToLabel() const {
if (isZero() || !isBlockPointer())
return false;
@@ -1142,9 +1160,12 @@ std::optional<APValue> Pointer::toRValue(const Context &Ctx,
if (OptPrimType T = Ctx.classify(ResultType)) {
if (!canDeref(*T))
return std::nullopt;
- TYPE_SWITCH(*T, return this->deref<T>().toAPValue(ASTCtx));
+ TYPE_SWITCH(*T, return this->load<T>().toAPValue(ASTCtx));
}
+ if (!isBlockPointer())
+ return std::nullopt;
+
// Return the composite type.
APValue Result;
if (!Composite(ResultType, view(), Result))
@@ -1161,6 +1182,8 @@ const VarDecl *Pointer::getRootVarDecl() const {
const Expr *Pointer::getRootExpr() const {
if (isBlockPointer())
return getDeclDesc()->asExpr();
+ if (isStringPointer())
+ return Str.getLiteral();
return nullptr;
}
diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h
index fa16c28712ac5..d6e1177d3d249 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -370,7 +370,20 @@ struct TypeidPointer {
const Type *TypeInfoType;
};
-enum class Storage { Int, Block, Fn, Typeid };
+struct StringPointer {
+ const Expr *Base = nullptr;
+ unsigned ID = 0;
+ bool Decayed = false;
+
+ StringPointer decay() const { return StringPointer{Base, ID, true}; }
+ const StringLiteral *getLiteral() const {
+ if (const auto *PE = dyn_cast<PredefinedExpr>(Base))
+ return PE->getFunctionName();
+ return cast<StringLiteral>(Base);
+ }
+};
+
+enum class Storage { Int, Block, Fn, Typeid, String };
/// A pointer to a memory block, live or dead.
///
@@ -420,6 +433,10 @@ class Pointer {
Typeid.TypePtr = TypePtr;
Typeid.TypeInfoType = TypeInfoType;
}
+ Pointer(const Expr *Base, unsigned Id)
+ : Offset(0), StorageKind(Storage::String), Str{Base, Id} {}
+ Pointer(StringPointer Str, uint64_t Offset = 0)
+ : Offset(Offset), StorageKind(Storage::String), Str(Str) {}
Pointer(Block *Pointee, unsigned Base, uint64_t Offset);
explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {}
@@ -438,6 +455,8 @@ class Pointer {
if (isFunctionPointer())
return P.Fn.Func == Fn.Func && P.Offset == Offset;
+ if (isStringPointer())
+ return Str.Base == P.Str.Base && Offset == P.Offset;
return P.view() == view();
}
@@ -469,12 +488,18 @@ class Pointer {
/// Offsets a pointer inside an array.
[[nodiscard]] Pointer atIndex(uint64_t Idx) const {
- if (isIntegralPointer())
+ switch (StorageKind) {
+ case Storage::Int:
return Pointer(Int.Value, Int.Ty, Idx);
- if (isFunctionPointer())
+ case Storage::Block:
+ return Pointer(view().atIndex(Idx));
+ case Storage::Fn:
return Pointer(Fn.Func, Idx);
-
- return Pointer(view().atIndex(Idx));
+ case Storage::String:
+ return Pointer(Str, Idx);
+ default:
+ llvm_unreachable("Unexpected pointer type in atIndex()");
+ }
}
/// Creates a pointer to a field.
@@ -514,6 +539,7 @@ class Pointer {
case Storage::Fn:
return !Fn.Func;
case Storage::Typeid:
+ case Storage::String:
return false;
}
llvm_unreachable("Unknown clang::interp::Storage enum");
@@ -581,6 +607,13 @@ class Pointer {
return Fn.Func->getDecl()->getType();
case Storage::Typeid:
return QualType(Typeid.TypeInfoType, 0);
+ case Storage::String:
+ if (Str.Decayed)
+ return Str.getLiteral()
+ ->getType()
+ ->getAsArrayTypeUnsafe()
+ ->getElementType();
+ return Str.getLiteral()->getType();
}
llvm_unreachable("Unhandled StorageKind");
}
@@ -596,6 +629,8 @@ class Pointer {
// FIXME: Remove this and handle int ptrs specially?
return 1;
}
+ if (isStringPointer())
+ return Str.getLiteral()->getCharByteWidth();
return view().elemSize();
}
@@ -619,6 +654,8 @@ class Pointer {
bool inArray() const {
if (isBlockPointer())
return view().inArray();
+ if (isStringPointer())
+ return true;
return false;
}
bool inUnion() const {
@@ -676,11 +713,16 @@ class Pointer {
assert(isTypeidPointer());
return Typeid;
}
+ [[nodiscard]] const StringPointer &asStringPointer() const {
+ assert(isStringPointer());
+ return Str;
+ }
bool isBlockPointer() const { return StorageKind == Storage::Block; }
bool isIntegralPointer() const { return StorageKind == Storage::Int; }
bool isFunctionPointer() const { return StorageKind == Storage::Fn; }
bool isTypeidPointer() const { return StorageKind == Storage::Typeid; }
+ bool isStringPointer() const { return StorageKind == Storage::String; }
/// Returns the record descriptor of a class.
const Record *getRecord() const {
@@ -770,6 +812,8 @@ class Pointer {
bool isConst() const {
if (isIntegralPointer())
return true;
+ if (isStringPointer())
+ return true;
return view().isConst();
}
bool isConstInMutable() const {
@@ -805,8 +849,12 @@ class Pointer {
return Offset;
}
+ uint64_t getRawOffset() const { return Offset; }
+
/// Returns the number of elements.
unsigned getNumElems() const {
+ if (isStringPointer())
+ return Str.getLiteral()->getLength() + 1;
if (!isBlockPointer())
return ~0u;
return view().getNumElems();
@@ -814,15 +862,20 @@ class Pointer {
const Block *block() const { return BS.Pointee; }
- /// If backed by actual data (i.e. a block pointer), return
+ /// If backed by actual data (i.e. a block or string pointer), return
/// an address to that data.
const std::byte *getRawAddress() const {
+ if (isStringPointer())
+ return reinterpret_cast<const std::byte *>(
+ Str.getLiteral()->getBytes().data());
assert(isBlockPointer());
return BS.Pointee->rawData() + Offset;
}
/// Returns the index into an array.
int64_t getIndex() const {
+ if (isStringPointer())
+ return Offset;
if (!isBlockPointer())
return getIntegerRepresentation();
@@ -831,6 +884,8 @@ class Pointer {
/// Checks if the index is one past end.
bool isOnePastEnd() const {
+ if (isStringPointer())
+ return Offset == (Str.getLiteral()->getLength() + 1);
if (!isBlockPointer())
return false;
@@ -847,6 +902,8 @@ class Pointer {
bool isPastEnd() const {
if (isIntegralPointer())
return false;
+ if (isStringPointer())
+ return Offset >= Str.getLiteral()->getLength();
return !isZero() && Offset > BS.Pointee->getSize();
}
@@ -865,6 +922,20 @@ class Pointer {
/// Checks whether the pointer can be dereferenced to the given PrimType.
bool canDeref(PrimType T) const {
+ if (isStringPointer()) {
+ switch (Str.getLiteral()->getCharByteWidth()) {
+ case 1:
+ return T == PT_Sint8 || T == PT_Uint8;
+ case 2:
+ return T == PT_Sint16 || T == PT_Uint16;
+ case 4:
+ return T == PT_Sint32 || T == PT_Uint32;
+ }
+
+ return false;
+ }
+
+ assert(isBlockPointer());
if (const Descriptor *FieldDesc = getFieldDesc()) {
return (FieldDesc->isPrimitive() || FieldDesc->isPrimitiveArray()) &&
FieldDesc->getPrimType() == T;
@@ -879,10 +950,36 @@ class Pointer {
assert(BS.Pointee);
assert(isDereferencable());
assert(Offset + sizeof(T) <= BS.Pointee->getDescriptor()->getAllocSize());
-
return view().deref<T>();
}
+ template <typename T> T load() const {
+ assert(isLive() && "Invalid pointer");
+ if (isBlockPointer()) {
+ assert(BS.Pointee);
+ assert(isDereferencable());
+ assert(Offset + sizeof(T) <= BS.Pointee->getDescriptor()->getAllocSize());
+ return view().deref<T>();
+ }
+
+ if (isStringPointer()) {
+ const StringLiteral *Lit = Str.getLiteral();
+
+ if constexpr (isFixedSizeIntegralType<T>()) {
+ // The literal does not include the nul byte.
+ if (Offset >= Lit->getLength())
+ return T::from('\0');
+ return T::from(Lit->getCodeUnit(Offset));
+ } else if constexpr (std::is_integral_v<T>) {
+ if (Offset >= Lit->getLength())
+ return '\0';
+ return Lit->getCodeUnit(Offset);
+ }
+ }
+
+ llvm_unreachable("Unexpected pointer type in load()");
+ }
+
/// Dereferences the element at index \p I.
/// This is equivalent to atIndex(I).deref<T>().
template <typename T> T &elem(unsigned I) const {
@@ -896,6 +993,33 @@ class Pointer {
return view().elem<T>(I);
}
+ template <typename T> T loadElem(unsigned I) const {
+ assert(isLive() && "Invalid pointer");
+ if (isBlockPointer()) {
+ assert(BS.Pointee);
+ assert(isDereferencable());
+ assert(getFieldDesc()->isPrimitiveArray());
+ assert(I < getFieldDesc()->getNumElems());
+
+ return view().elem<T>(I);
+ }
+
+ assert(isStringPointer());
+ const StringLiteral *Lit = Str.getLiteral();
+ unsigned Index = Offset + I;
+ if constexpr (isFixedSizeIntegralType<T>()) {
+ // The literal does not include the nul byte.
+ if (Index >= Lit->getLength())
+ return T::from('\0');
+ return T::from(Lit->getCodeUnit(Index));
+ } else if constexpr (std::is_integral_v<T>) {
+ if (Index >= Lit->getLength())
+ return '\0';
+ return Lit->getCodeUnit(Index);
+ }
+ llvm_unreachable("Unexpected pointer type in loadElem()");
+ }
+
bool isConstexprUnknown() const {
if (!isBlockPointer())
return false;
@@ -917,6 +1041,10 @@ class Pointer {
return true;
}
+ bool isReadablePointerType() const {
+ return StorageKind == Storage::Block || StorageKind == Storage::String;
+ }
+
/// Initializes a field.
void initialize() const {
if (!isBlockPointer())
@@ -1010,7 +1138,6 @@ class Pointer {
/// Whether this points to a block that's been created for a "literal lvalue",
/// i.e. a non-MaterializeTemporaryExpr Expr.
bool pointsToLiteral() const;
- bool pointsToStringLiteral() const;
/// Whether this points to a block created for an AddrLabelExpr.
bool pointsToLabel() const;
/// Returns the AddrLabelExpr the Pointer points to, if any.
@@ -1073,6 +1200,7 @@ class Pointer {
BlockPointer BS;
FunctionPointer Fn;
TypeidPointer Typeid;
+ StringPointer Str;
};
};
diff --git a/clang/lib/AST/ByteCode/PrimType.h b/clang/lib/AST/ByteCode/PrimType.h
index 8f725942fedb9..9423b2bcb6308 100644
--- a/clang/lib/AST/ByteCode/PrimType.h
+++ b/clang/lib/AST/ByteCode/PrimType.h
@@ -146,6 +146,16 @@ template <typename T> constexpr bool isIntegralOrPointer() {
std::is_same_v<T, Integral<64, true>>;
}
+template <typename T> constexpr bool isFixedSizeIntegralType() {
+ return std::is_same_v<T, Char<false>> || std::is_same_v<T, Char<true>> ||
+ std::is_same_v<T, Integral<16, false>> ||
+ std::is_same_v<T, Integral<16, true>> ||
+ std::is_same_v<T, Integral<32, false>> ||
+ std::is_same_v<T, Integral<32, true>> ||
+ std::is_same_v<T, Integral<64, false>> ||
+ std::is_same_v<T, Integral<64, true>>;
+}
+
/// Mapping from primitive types to their representation.
template <PrimType T> struct PrimConv;
template <> struct PrimConv<PT_Sint8> {
diff --git a/clang/lib/AST/ByteCode/Primitives.h b/clang/lib/AST/ByteCode/Primitives.h
index e2d3dacea5726..8b109cb5af0a0 100644
--- a/clang/lib/AST/ByteCode/Primitives.h
+++ b/clang/lib/AST/ByteCode/Primitives.h
@@ -26,6 +26,8 @@ enum class IntegralKind : uint8_t {
Number = 0,
/// A pointer to a ValueDecl.
Address,
+ /// A pointer to an Expr.
+ ExprAddress,
/// A pointer to an interp::Block.
BlockAddress,
/// A pointer to a AddrLabelExpr.
diff --git a/clang/lib/AST/ByteCode/Program.cpp b/clang/lib/AST/ByteCode/Program.cpp
index 564d2d8fc422d..975cace8b2ee7 100644
--- a/clang/lib/AST/ByteCode/Program.cpp
+++ b/clang/lib/AST/ByteCode/Program.cpp
@@ -7,10 +7,8 @@
//===----------------------------------------------------------------------===//
#include "Program.h"
-#include "Char.h"
#include "Context.h"
#include "Function.h"
-#include "Integral.h"
#include "PrimType.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
@@ -32,55 +30,6 @@ const void *Program::getNativePointer(unsigned Idx) const {
return NativePointers[Idx];
}
-unsigned Program::createGlobalString(const StringLiteral *S, const Expr *Base) {
- const size_t CharWidth = S->getCharByteWidth();
- const size_t BitWidth = CharWidth * Ctx.getCharBit();
- unsigned StringLength = S->getLength();
-
- OptPrimType CharType =
- Ctx.classify(S->getType()->castAsArrayTypeUnsafe()->getElementType());
- assert(CharType);
-
- if (!Base)
- Base = S;
-
- // Create a descriptor for the string.
- Descriptor *Desc =
- allocateDescriptor(Base, S->getType().getTypePtr(), *CharType,
- Descriptor::GlobalMD, StringLength + 1,
- /*IsConst=*/true,
- /*isTemporary=*/false,
- /*isMutable=*/false,
- /*IsVolatile=*/false);
-
- // Allocate storage for the string.
- // The byte length does not include the null terminator.
- unsigned GlobalIndex = Globals.size();
- unsigned Sz = Desc->getAllocSize();
- auto *G = new (Allocator, Sz) Global(Ctx.getEvalID(), Desc, /*IsStatic=*/true,
- /*IsExtern=*/false);
- G->block()->invokeCtor();
-
- new (G->block()->rawData())
- GlobalInlineDescriptor{GlobalInitState::Initialized};
- Globals.push_back(G);
-
- const Pointer Ptr(G->block());
- if (CharWidth == 1) {
- std::memcpy(&Ptr.elem<char>(0), S->getString().data(), StringLength);
- } else {
- // Construct the string in storage.
- for (unsigned I = 0; I <= StringLength; ++I) {
- uint32_t CodePoint = I == StringLength ? 0 : S->getCodeUnit(I);
- INT_TYPE_SWITCH_NO_BOOL(*CharType,
- Ptr.elem<T>(I) = T::from(CodePoint, BitWidth););
- }
- }
- Ptr.initializeAllElements();
-
- return GlobalIndex;
-}
-
Pointer Program::getPtrGlobal(unsigned Idx) const {
assert(Idx < Globals.size());
return Pointer(Globals[Idx]->block());
diff --git a/clang/lib/AST/ByteCode/Program.h b/clang/lib/AST/ByteCode/Program.h
index c2299a1e10191..c879b5e82dda7 100644
--- a/clang/lib/AST/ByteCode/Program.h
+++ b/clang/lib/AST/ByteCode/Program.h
@@ -63,10 +63,6 @@ class Program final {
/// Returns the value of a marshalled native pointer.
const void *getNativePointer(unsigned Idx) const;
- /// Emits a string literal among global data.
- unsigned createGlobalString(const StringLiteral *S,
- const Expr *Base = nullptr);
-
/// Returns a pointer to a global.
Pointer getPtrGlobal(unsigned Idx) const;
diff --git a/clang/test/AST/ByteCode/builtin-functions.cpp b/clang/test/AST/ByteCode/builtin-functions.cpp
index 87ffb1cc5b609..55ef1a2128c14 100644
--- a/clang/test/AST/ByteCode/builtin-functions.cpp
+++ b/clang/test/AST/ByteCode/builtin-functions.cpp
@@ -180,7 +180,7 @@ namespace WcsCmp {
/// Copied from constant-expression-cxx11.cpp
namespace strlen {
-constexpr const char *a = "foo\0quux";
+ constexpr const char *a = "foo\0quux";
constexpr char b[] = "foo\0quux";
constexpr int f() { return 'u'; }
constexpr char c[] = { 'f', 'o', 'o', 0, 'q', f(), 'u', 'x', 0 };
diff --git a/clang/test/AST/ByteCode/cxx20.cpp b/clang/test/AST/ByteCode/cxx20.cpp
index 70ff1045a74da..b06a3ed9149cb 100644
--- a/clang/test/AST/ByteCode/cxx20.cpp
+++ b/clang/test/AST/ByteCode/cxx20.cpp
@@ -119,8 +119,8 @@ static_assert(!b2);
constexpr auto name1() { return "name1"; }
constexpr auto name2() { return "name2"; }
-constexpr auto b3 = name1() == name1(); // ref-error {{must be initialized by a constant expression}} \
- // ref-note {{comparison of addresses of potentially overlapping literals}}
+constexpr auto b3 = name1() == name1(); // both-error {{must be initialized by a constant expression}} \
+ // both-note {{comparison of addresses of potentially overlapping literals}}
constexpr auto b4 = name1() == name2();
static_assert(!b4);
diff --git a/clang/test/AST/ByteCode/strings.cpp b/clang/test/AST/ByteCode/strings.cpp
new file mode 100644
index 0000000000000..33fca213f529a
--- /dev/null
+++ b/clang/test/AST/ByteCode/strings.cpp
@@ -0,0 +1,46 @@
+// RUN: %clang_cc1 -triple x86_64-linux -verify=both,expected %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -triple x86_64-linux -verify=both,ref %s
+
+
+
+static_assert("foo"[0] == 'f');
+static_assert("foo"[1] == 'o');
+static_assert("foo"[2] == 'o');
+static_assert("foo"[3] == '\0');
+
+static_assert(+"foo" == +"foo"); // both-error {{not an integral constant expression}} \
+ // both-note {{comparison of addresses of potentially overlapping literals}}
+
+static_assert("foo"[4] == '\0'); // both-error {{not an integral constant expression}} \
+ // both-note {{read of dereferenced one-past-the-end pointer}}
+
+static_assert("foo"[5] == '\0'); // both-error {{not an integral constant expression}} \
+ // both-note {{cannot refer to element 5 of array of 4 elements}}
+
+constexpr const wchar_t *wide = L"bar";
+static_assert(wide[0] == L'b', "");
+
+constexpr const char32_t *u32 = U"abc";
+static_assert(u32[1] == U'b', "");
+
+constexpr int testMemcpy() {
+ char s[5] = {0, 0, 0, 0, 0};
+ __builtin_memcpy(s, "abcd", 5);
+ return s[0] == 'a';
+}
+static_assert(testMemcpy() == 1, "");
+
+constexpr const auto *wp = L"abc";
+static_assert(&wp[2] - &wp[0] == 2);
+
+
+constexpr int checkMemcpy() {
+ char a[3] = {};
+
+ __builtin_memcpy(a, &"abcdef"[3], 3);
+ return __builtin_strncmp(a, "def", 3) == 0;
+}
+static_assert(checkMemcpy());
+
+
+
diff --git a/clang/unittests/AST/ByteCode/Pointer.cpp b/clang/unittests/AST/ByteCode/Pointer.cpp
index 0b93c40aaa202..8661903df53c0 100644
--- a/clang/unittests/AST/ByteCode/Pointer.cpp
+++ b/clang/unittests/AST/ByteCode/Pointer.cpp
@@ -272,3 +272,64 @@ TEST(Pointer, TypesPrimitive) {
ASSERT_EQ(GlobalPtr, GlobalPtr.atIndex(2).narrow().expand().getArray());
}
}
+
+TEST(Pointer, Strings) {
+ constexpr char Code[] = "constexpr const char *str1 = \"foobar\";\n"
+ "constexpr const auto *str2 = L\"foobar\";\n"
+ "constexpr const auto *c = &L\"foobar\"[5];\n";
+ auto AST = tooling::buildASTFromCodeWithArgs(
+ Code, {"-fexperimental-new-constant-interpreter"});
+ ASTContext &ASTCtx = AST->getASTContext();
+ const VarDecl *D =
+ match(varDecl(hasGlobalStorage(), hasName("str1")).bind("str1"),
+ ASTCtx)[0]
+ .getNodeAs<VarDecl>("str1");
+ ASSERT_NE(D, nullptr);
+
+ const auto &Ctx = AST->getASTContext().getInterpContext();
+ Program &Prog = Ctx.getProgram();
+ ASSERT_TRUE(Prog.getGlobal(D));
+
+ Pointer GlobalPtr = Prog.getPtrGlobal(*Prog.getGlobal(D));
+ ASSERT_TRUE(GlobalPtr.isBlockPointer());
+ ASSERT_TRUE(GlobalPtr.getFieldDesc()->getPrimType() == PT_Ptr);
+
+ Pointer Pointee = GlobalPtr.load<Pointer>();
+ ASSERT_TRUE(Pointee.isStringPointer());
+ ASSERT_EQ(Pointee.getNumElems(), 7u);
+ ASSERT_EQ(Pointee.elemSize(), 1u);
+
+ D = match(varDecl(hasGlobalStorage(), hasName("str2")).bind("str2"),
+ ASTCtx)[0]
+ .getNodeAs<VarDecl>("str2");
+ ASSERT_NE(D, nullptr);
+ GlobalPtr = Prog.getPtrGlobal(*Prog.getGlobal(D));
+ ASSERT_TRUE(GlobalPtr.isBlockPointer());
+ ASSERT_TRUE(GlobalPtr.getFieldDesc()->getPrimType() == PT_Ptr);
+
+ Pointee = GlobalPtr.load<Pointer>();
+ ASSERT_TRUE(Pointee.isStringPointer());
+ ASSERT_EQ(Pointee.getNumElems(), 7u);
+ ASSERT_EQ(Pointee.elemSize(), __WCHAR_WIDTH__ / 8u);
+
+ D = match(varDecl(hasGlobalStorage(), hasName("c")).bind("c"), ASTCtx)[0]
+ .getNodeAs<VarDecl>("c");
+ ASSERT_NE(D, nullptr);
+ GlobalPtr = Prog.getPtrGlobal(*Prog.getGlobal(D));
+ ASSERT_TRUE(GlobalPtr.isBlockPointer());
+ ASSERT_TRUE(GlobalPtr.getFieldDesc()->getPrimType() == PT_Ptr);
+
+ Pointee = GlobalPtr.load<Pointer>();
+ ASSERT_TRUE(Pointee.isStringPointer());
+ ASSERT_EQ(Pointee.getNumElems(), 7u);
+ ASSERT_EQ(Pointee.elemSize(), __WCHAR_WIDTH__ / 8u);
+ ASSERT_EQ(Pointee.getIndex(), 5u);
+ APValue APV = Pointee.toAPValue(ASTCtx);
+ ASSERT_TRUE(APV.isLValue());
+ ASSERT_FALSE(APV.isLValueOnePastTheEnd());
+ ASSERT_EQ(APV.getLValueOffset().getQuantity(), 5u * (__WCHAR_WIDTH__ / 8u));
+ ASSERT_TRUE(APV.hasLValuePath());
+ const auto &Path = APV.getLValuePath();
+ ASSERT_EQ(Path.size(), 1u);
+ ASSERT_EQ(Path[0].getAsArrayIndex(), 5u);
+}
More information about the cfe-commits
mailing list