Timm =?utf-8?q?Bäder?= <[email protected]> Message-ID: In-Reply-To: <llvm.org/llvm/llvm-project/pull/[email protected]>
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/186045 >From 8ad648079e32fef2fc2d8d70e5d8bfe06cd45cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Thu, 12 Mar 2026 06:05:56 +0100 Subject: [PATCH 1/2] 2 --- clang/lib/AST/ByteCode/ByteCodeEmitter.h | 1 + clang/lib/AST/ByteCode/Compiler.cpp | 6 + clang/lib/AST/ByteCode/Context.cpp | 19 +- clang/lib/AST/ByteCode/Disasm.cpp | 5 +- clang/lib/AST/ByteCode/EvalEmitter.cpp | 41 +- clang/lib/AST/ByteCode/EvalEmitter.h | 19 +- clang/lib/AST/ByteCode/EvaluationResult.cpp | 362 +++++++++++++++++- clang/lib/AST/ByteCode/EvaluationResult.h | 41 +- clang/lib/AST/ByteCode/Interp.cpp | 2 +- clang/lib/AST/ExprConstShared.h | 43 +++ clang/lib/AST/ExprConstant.cpp | 144 +++---- clang/test/AST/ByteCode/builtin-functions.cpp | 6 +- .../ByteCode/codegen-constexpr-unknown.cpp | 1 + clang/test/AST/ByteCode/references.cpp | 5 +- clang/test/CodeGenCXX/global-init.cpp | 8 + clang/test/SemaCXX/PR19955.cpp | 3 + 16 files changed, 553 insertions(+), 153 deletions(-) diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.h b/clang/lib/AST/ByteCode/ByteCodeEmitter.h index 4b42b7eb4063b..3577c02337758 100644 --- a/clang/lib/AST/ByteCode/ByteCodeEmitter.h +++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.h @@ -64,6 +64,7 @@ class ByteCodeEmitter { /// We're always emitting bytecode. bool isActive() const { return true; } bool checkingForUndefinedBehavior() const { return false; } + bool constantFolding() const { return false; } /// Callback for local registration. Local createLocal(Descriptor *D); diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 638e6ecafb295..505703ac1a5f4 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -3254,6 +3254,10 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr( bool IsStatic = E->getStorageDuration() == SD_Static; if (IsStatic || (ExtendingDecl && Context::shouldBeGloballyIndexed(ExtendingDecl))) { + + if (this->constantFolding()) + return false; + UnsignedOrNone GlobalIndex = P.createGlobal(E, Inner->getType()); if (!GlobalIndex) return false; @@ -5143,6 +5147,8 @@ const Function *Compiler<Emitter>::getFunction(const FunctionDecl *FD) { template <class Emitter> bool Compiler<Emitter>::visitExpr(const Expr *E, bool DestroyToplevelScope) { + assert(E); + assert(!E->getType().isNull()); LocalScope<Emitter> RootScope(this, ScopeKind::FullExpression); auto maybeDestroyLocals = [&]() -> bool { diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp index 4beb35a9a7b43..ee3365e96803e 100644 --- a/clang/lib/AST/ByteCode/Context.cpp +++ b/clang/lib/AST/ByteCode/Context.cpp @@ -76,7 +76,7 @@ bool Context::evaluateAsRValue(State &Parent, const Expr *E, APValue &Result) { size_t StackSizeBefore = Stk.size(); Compiler<EvalEmitter> C(*this, *P, Parent, Stk); - auto Res = C.interpretExpr(E, /*ConvertResultToRValue=*/E->isGLValue()); + auto Res = C.interpretExpr(E); if (Res.isInvalid()) { C.cleanup(); @@ -96,7 +96,6 @@ bool Context::evaluateAsRValue(State &Parent, const Expr *E, APValue &Result) { } Result = Res.stealAPValue(); - return true; } @@ -105,7 +104,7 @@ bool Context::evaluate(State &Parent, const Expr *E, APValue &Result, ++EvalID; bool Recursing = !Stk.empty(); size_t StackSizeBefore = Stk.size(); - Compiler<EvalEmitter> C(*this, *P, Parent, Stk); + Compiler<EvalEmitter> C(*this, *P, Parent, Stk, Kind); auto Res = C.interpretExpr(E, /*ConvertResultToRValue=*/false, /*DestroyToplevelScope=*/true); @@ -137,8 +136,8 @@ bool Context::evaluateAsInitializer(State &Parent, const VarDecl *VD, Compiler<EvalEmitter> C(*this, *P, Parent, Stk); bool CheckGlobalInitialized = - shouldBeGloballyIndexed(VD) && (VD->getType()->isRecordType() || VD->getType()->isArrayType()); + auto Res = C.interpretDecl(VD, Init, CheckGlobalInitialized); if (Res.isInvalid()) { C.cleanup(); @@ -192,7 +191,8 @@ bool Context::evaluateStringRepr(State &Parent, const Expr *SizeExpr, return false; uint64_t Size = SizeValue.getInt().getZExtValue(); - auto PtrRes = C.interpretAsPointer(PtrExpr, [&](const Pointer &Ptr) { + auto PtrRes = C.interpretAsPointer(PtrExpr, [&](InterpState &S, + const Pointer &Ptr) { if (Size == 0) { if constexpr (std::is_same_v<ResultT, APValue>) Result = APValue(APValue::UninitArray{}, 0, 0); @@ -261,7 +261,8 @@ bool Context::evaluateString(State &Parent, const Expr *E, assert(Stk.empty()); Compiler<EvalEmitter> C(*this, *P, Parent, Stk); - auto PtrRes = C.interpretAsPointer(E, [&](const Pointer &Ptr) { + auto PtrRes = C.interpretAsPointer(E, [&](InterpState &S, + const Pointer &Ptr) { if (!Ptr.isBlockPointer()) return false; @@ -310,7 +311,8 @@ std::optional<uint64_t> Context::evaluateStrlen(State &Parent, const Expr *E) { Compiler<EvalEmitter> C(*this, *P, Parent, Stk); std::optional<uint64_t> Result; - auto PtrRes = C.interpretAsPointer(E, [&](const Pointer &Ptr) { + auto PtrRes = C.interpretAsPointer(E, [&](InterpState &S, + const Pointer &Ptr) { if (!Ptr.isBlockPointer()) return false; @@ -361,7 +363,8 @@ Context::tryEvaluateObjectSize(State &Parent, const Expr *E, unsigned Kind) { std::optional<uint64_t> Result; - auto PtrRes = C.interpretAsLValuePointer(E, [&](const Pointer &Ptr) { + auto PtrRes = C.interpretAsLValuePointer(E, [&](InterpState &S, + const Pointer &Ptr) { const Descriptor *DeclDesc = Ptr.getDeclDesc(); if (!DeclDesc) return false; diff --git a/clang/lib/AST/ByteCode/Disasm.cpp b/clang/lib/AST/ByteCode/Disasm.cpp index 4caf830a0a1b4..535523d49190b 100644 --- a/clang/lib/AST/ByteCode/Disasm.cpp +++ b/clang/lib/AST/ByteCode/Disasm.cpp @@ -655,9 +655,6 @@ LLVM_DUMP_METHOD void EvaluationResult::dump() const { OS << "Invalid\n"; } else { OS << "Value: "; -#ifndef NDEBUG - assert(Ctx); - Value.dump(OS, Ctx->getASTContext()); -#endif + Value.dump(OS, Ctx.getASTContext()); } } diff --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp b/clang/lib/AST/ByteCode/EvalEmitter.cpp index 6e986f4bcbda5..29042435b1db5 100644 --- a/clang/lib/AST/ByteCode/EvalEmitter.cpp +++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp @@ -18,8 +18,9 @@ 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) {} + InterpStack &Stk, ConstantExprKind ConstexprKind) + : Ctx(Ctx), P(P), S(Parent, P, Stk, Ctx, this), EvalResult(Ctx), + ConstexprKind(ConstexprKind) {} EvalEmitter::~EvalEmitter() { for (auto &V : Locals) { @@ -223,6 +224,18 @@ template <PrimType OpType> bool EvalEmitter::emitRet(SourceInfo Info) { return true; } +template <> bool EvalEmitter::emitRet<PT_MemberPtr>(SourceInfo Info) { + if (!isActive()) + return true; + + const MemberPointer &MP = S.Stk.pop<MemberPointer>(); + if (!EvalResult.checkMemberPointer(S, MP, Info, ConstexprKind)) + return false; + + EvalResult.takeValue(MP.toAPValue(Ctx.getASTContext())); + return true; +} + template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) { if (!isActive()) return true; @@ -230,15 +243,19 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) { const Pointer &Ptr = S.Stk.pop<Pointer>(); // If we're returning a raw pointer, call our callback. if (this->PtrCB) - return (*this->PtrCB)(Ptr); + return (*this->PtrCB)(S, Ptr); - if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info)) + if (!EvalResult.checkDynamicAllocations(S, Ctx, Ptr, Info)) return false; + if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr)) return false; // Function pointers are always returned as lvalues. if (Ptr.isFunctionPointer()) { + if (!EvalResult.checkFunctionPointer(S, Ptr, Info, ConstexprKind)) + return false; + EvalResult.takeValue(Ptr.toAPValue(Ctx.getASTContext())); return true; } @@ -260,6 +277,9 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) { Ptr.block()->getEvalID() != Ctx.getEvalID()) return false; + if (!EvalResult.checkLValueFields(S, Ptr, Info, ConstexprKind)) + return false; + if (std::optional<APValue> V = Ptr.toRValue(Ctx, EvalResult.getSourceType())) { EvalResult.takeValue(std::move(*V)); @@ -267,13 +287,8 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) { 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 (!EvalResult.checkLValue(S, Ptr, Info, ConstexprKind)) + return false; if (!Ptr.isLive() && !Ptr.isTemporary()) return false; @@ -292,10 +307,12 @@ bool EvalEmitter::emitRetVoid(SourceInfo Info) { bool EvalEmitter::emitRetValue(SourceInfo Info) { const auto &Ptr = S.Stk.pop<Pointer>(); - if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info)) + if (!EvalResult.checkDynamicAllocations(S, Ctx, Ptr, Info)) return false; if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr)) return false; + if (!EvalResult.checkLValueFields(S, Ptr, Info, ConstexprKind)) + return false; if (std::optional<APValue> APV = Ptr.toRValue(Ctx, EvalResult.getSourceType())) { diff --git a/clang/lib/AST/ByteCode/EvalEmitter.h b/clang/lib/AST/ByteCode/EvalEmitter.h index 6fd50da8cad76..b24aff480215b 100644 --- a/clang/lib/AST/ByteCode/EvalEmitter.h +++ b/clang/lib/AST/ByteCode/EvalEmitter.h @@ -33,11 +33,16 @@ class EvalEmitter : public SourceMapper { using LabelTy = uint32_t; using AddrTy = uintptr_t; using Local = Scope::Local; - using PtrCallback = llvm::function_ref<bool(const Pointer &)>; + using PtrCallback = llvm::function_ref<bool(InterpState &, const Pointer &)>; - EvaluationResult interpretExpr(const Expr *E, - bool ConvertResultToRValue = false, + EvaluationResult interpretExpr(const Expr *E) { + return interpretExpr(E, /*ConvertResultToRValue=*/E->isGLValue(), + /*DestroyToplevelScope=*/false); + } + + EvaluationResult interpretExpr(const Expr *E, bool ConvertResultToRValue, bool DestroyToplevelScope = false); + EvaluationResult interpretDecl(const VarDecl *VD, const Expr *Init, bool CheckFullyInitialized); EvaluationResult interpretDestructor(const VarDecl *VD, const APValue &Value); @@ -51,8 +56,13 @@ class EvalEmitter : public SourceMapper { /// Clean up all resources. void cleanup(); + bool constantFolding() const { + return S.EvalMode == EvaluationMode::ConstantFold; + } + protected: - EvalEmitter(Context &Ctx, Program &P, State &Parent, InterpStack &Stk); + EvalEmitter(Context &Ctx, Program &P, State &Parent, InterpStack &Stk, + ConstantExprKind ConstexprKind = ConstantExprKind::Normal); virtual ~EvalEmitter(); @@ -109,6 +119,7 @@ class EvalEmitter : public SourceMapper { InterpState S; /// Location to write the result to. EvaluationResult EvalResult; + ConstantExprKind ConstexprKind = ConstantExprKind::Normal; /// Whether the result should be converted to an RValue. bool ConvertResultToRValue = false; /// Whether we should check if the result has been fully diff --git a/clang/lib/AST/ByteCode/EvaluationResult.cpp b/clang/lib/AST/ByteCode/EvaluationResult.cpp index 24f242758324e..cb0cffdce442b 100644 --- a/clang/lib/AST/ByteCode/EvaluationResult.cpp +++ b/clang/lib/AST/ByteCode/EvaluationResult.cpp @@ -7,16 +7,36 @@ //===----------------------------------------------------------------------===// #include "EvaluationResult.h" +#include "../ExprConstShared.h" #include "InterpState.h" #include "Pointer.h" #include "Record.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/ExprObjC.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallPtrSet.h" #include <iterator> namespace clang { namespace interp { +QualType EvaluationResult::getStorageType() const { + if (const auto *E = Source.dyn_cast<const Expr *>()) { + if (E->isPRValue()) + return E->getType(); + + return Ctx.getASTContext().getLValueReferenceType(E->getType()); + } + + if (const auto *D = + dyn_cast_if_present<ValueDecl>(Source.dyn_cast<const Decl *>())) + return D->getType(); + return QualType(); +} + static void DiagnoseUninitializedSubobject(InterpState &S, SourceLocation Loc, const FieldDecl *SubObjDecl) { assert(SubObjDecl && "Subobject declaration does not exist"); @@ -198,11 +218,14 @@ static void collectBlocks(PtrView Ptr, llvm::SetVector<const Block *> &Blocks) { } } -bool EvaluationResult::checkReturnValue(InterpState &S, const Context &Ctx, - const Pointer &Ptr, - const SourceInfo &Info) { +bool EvaluationResult::checkDynamicAllocations(InterpState &S, + const Context &Ctx, + const Pointer &Ptr, + SourceInfo Info) { + if (!Ptr.isBlockPointer()) return true; + // Collect all blocks that this pointer (transitively) points to and // return false if any of them is a dynamic block. llvm::SetVector<const Block *> Blocks; @@ -226,5 +249,338 @@ bool EvaluationResult::checkReturnValue(InterpState &S, const Context &Ctx, return true; } +static bool isGlobalLValue(const Pointer &Ptr) { + if (Ptr.isBlockPointer() && Ptr.block()->isDynamic()) + return true; + if (Ptr.isTypeidPointer()) + return true; + + const Descriptor *Desc = Ptr.getDeclDesc(); + return ::isGlobalLValue(Desc->asValueDecl(), Desc->asExpr()); +} + +/// Check if the given function pointer can be returned from an evaluation. +static bool checkFunctionPtr(InterpState &S, const Pointer &Ptr, + QualType PtrType, SourceInfo Info, + ConstantExprKind ConstexprKind) { + assert(Ptr.isFunctionPointer()); + const FunctionPointer &FuncPtr = Ptr.asFunctionPointer(); + const FunctionDecl *FD = FuncPtr.Func->getDecl(); + // E.g. ObjC block pointers. + if (!FD) + return true; + if (FD->isImmediateFunction()) { + S.FFDiag(Info, diag::note_consteval_address_accessible) + << !PtrType->isAnyPointerType(); + S.Note(FD->getLocation(), diag::note_declared_at); + return false; + } + + // __declspec(dllimport) must be handled very carefully: + // We must never initialize an expression with the thunk in C++. + // Doing otherwise would allow the same id-expression to yield + // different addresses for the same function in different translation + // units. However, this means that we must dynamically initialize the + // expression with the contents of the import address table at runtime. + // + // The C language has no notion of ODR; furthermore, it has no notion of + // dynamic initialization. This means that we are permitted to + // perform initialization with the address of the thunk. + if (S.getLangOpts().CPlusPlus && !isForManglingOnly(ConstexprKind) && + FD->hasAttr<DLLImportAttr>()) + // FIXME: Diagnostic! + return false; + return true; +} + +static bool lvalFields(InterpState &S, const ASTContext &Ctx, + const Pointer &Ptr, QualType PtrType, SourceInfo Info, + ConstantExprKind ConstexprKind, + llvm::SmallPtrSet<const Block *, 4> &CheckedBlocks); +static bool lval(InterpState &S, const ASTContext &Ctx, const Pointer &Ptr, + QualType PtrType, SourceInfo Info, + ConstantExprKind ConstexprKind, + llvm::SmallPtrSet<const Block *, 4> &CheckedBlocks) { + if (Ptr.isFunctionPointer()) + return checkFunctionPtr(S, Ptr, PtrType, Info, ConstexprKind); + + if (!Ptr.isBlockPointer()) + return true; + + const Descriptor *DeclDesc = Ptr.block()->getDescriptor(); + const Expr *BaseE = DeclDesc->asExpr(); + const ValueDecl *BaseVD = DeclDesc->asValueDecl(); + assert(BaseE || BaseVD); + bool IsReferenceType = PtrType->isReferenceType(); + bool IsSubObj = !Ptr.isRoot() || (Ptr.inArray() && !Ptr.isArrayRoot()); + + if (!isGlobalLValue(Ptr)) { + if (S.getLangOpts().CPlusPlus11) { + S.FFDiag(Info, diag::note_constexpr_non_global, 1) + << IsReferenceType << IsSubObj + << !!DeclDesc->asValueDecl() // DeclDesc->IsTemporary + << DeclDesc->asValueDecl(); + const VarDecl *VarD = DeclDesc->asVarDecl(); + if (VarD && VarD->isConstexpr()) { + // Non-static local constexpr variables have unintuitive semantics: + // constexpr int a = 1; + // constexpr const int *p = &a; + // ... is invalid because the address of 'a' is not constant. Suggest + // adding a 'static' in this case. + S.Note(VarD->getLocation(), diag::note_constexpr_not_static) + << VarD + << FixItHint::CreateInsertion(VarD->getBeginLoc(), "static "); + } else { + if (const ValueDecl *VD = DeclDesc->asValueDecl()) + S.Note(VD->getLocation(), diag::note_declared_at); + else if (const Expr *E = DeclDesc->asExpr()) + S.Note(E->getExprLoc(), diag::note_constexpr_temporary_here); + } + } else { + S.FFDiag(Info); + } + return false; + } + + if (const auto *VD = dyn_cast_if_present<VarDecl>(BaseVD)) { + // Check if this is a thread-local variable. + if (VD->getTLSKind()) { + // FIXME: Diagnostic! + return false; + } + + // A dllimport variable never acts like a constant, unless we're + // evaluating a value for use only in name mangling, and unless it's a + // static local. For the latter case, we'd still need to evaluate the + // constant expression in case we're inside a (inlined) function. + if (!isForManglingOnly(ConstexprKind) && VD->hasAttr<DLLImportAttr>() && + !VD->isStaticLocal()) + return false; + + // In CUDA/HIP device compilation, only device side variables have + // constant addresses. + if (S.getLangOpts().CUDA && S.getLangOpts().CUDAIsDevice && + Ctx.CUDAConstantEvalCtx.NoWrongSidedVars) { + if ((!VD->hasAttr<CUDADeviceAttr>() && !VD->hasAttr<CUDAConstantAttr>() && + !VD->getType()->isCUDADeviceBuiltinSurfaceType() && + !VD->getType()->isCUDADeviceBuiltinTextureType()) || + VD->hasAttr<HIPManagedAttr>()) + return false; + } + + return true; + } + + if (const auto *MTE = dyn_cast_if_present<MaterializeTemporaryExpr>(BaseE)) { + QualType TempType = Ptr.getType(); + + if (TempType.isDestructedType()) { + S.FFDiag(MTE->getExprLoc(), + diag::note_constexpr_unsupported_temporary_nontrivial_dtor) + << TempType; + return false; + } + + if (Ptr.getFieldDesc()->isPrimitive() && + Ptr.getFieldDesc()->getPrimType() == PT_Ptr) { + // Recurse! + Pointer Pointee = Ptr.deref<Pointer>(); + if (CheckedBlocks.insert(Pointee.block()).second) { + if (!lval(S, Ctx, Pointee, Pointee.getType(), + Ptr.getDeclDesc()->getLoc(), ConstexprKind, CheckedBlocks)) + return false; + } + } else if (Ptr.getRecord()) { + return lvalFields(S, Ctx, Ptr, Ptr.getType(), Info, + ConstantExprKind::Normal, CheckedBlocks); + } + } + + return true; +} + +static bool lvalFields(InterpState &S, const ASTContext &Ctx, + const Pointer &Ptr, QualType PtrType, SourceInfo Info, + ConstantExprKind ConstexprKind, + llvm::SmallPtrSet<const Block *, 4> &CheckedBlocks) { + if (!Ptr.isBlockPointer()) + return true; + + const Descriptor *FieldDesc = Ptr.getFieldDesc(); + if (const Record *R = Ptr.getRecord()) { + if (!R->hasPtrField()) + return true; + for (const Record::Field &F : R->fields()) { + if (F.Desc->isPrimitive() && F.Desc->getPrimType() == PT_Ptr) { + QualType FieldType = F.Decl->getType(); + if (!Ptr.atField(F.Offset).isLive()) + return false; + + Pointer Pointee = Ptr.atField(F.Offset).deref<Pointer>(); + if (CheckedBlocks.insert(Pointee.block()).second) { + if (!lval(S, Ctx, Pointee, FieldType, Info, ConstexprKind, + CheckedBlocks)) + return false; + } + } else { + Pointer FieldPtr = Ptr.atField(F.Offset); + if (!lvalFields(S, Ctx, FieldPtr, F.Decl->getType(), Info, + ConstexprKind, CheckedBlocks)) + return false; + } + } + + for (const Record::Base &B : R->bases()) { + Pointer BasePtr = Ptr.atField(B.Offset); + if (!lvalFields(S, Ctx, BasePtr, B.Desc->getType(), Info, ConstexprKind, + CheckedBlocks)) + return false; + } + for (const Record::Base &B : R->virtual_bases()) { + Pointer BasePtr = Ptr.atField(B.Offset); + if (!lvalFields(S, Ctx, BasePtr, B.Desc->getType(), Info, ConstexprKind, + CheckedBlocks)) + return false; + } + + return true; + } + if (FieldDesc->isPrimitiveArray()) { + if (FieldDesc->getPrimType() == PT_Ptr) { + for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { + if (!Ptr.isLive()) + return false; + Pointer Pointee = Ptr.elem<Pointer>(I); + if (CheckedBlocks.insert(Pointee.block()).second) { + if (!lval(S, Ctx, Pointee, FieldDesc->getElemQualType(), Info, + ConstexprKind, CheckedBlocks)) + return false; + } + } + } + return true; + } + if (FieldDesc->isCompositeArray()) { + if (FieldDesc->ElemRecord && !FieldDesc->ElemRecord->hasPtrField()) + return true; + + for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { + Pointer Elem = Ptr.atIndex(I).narrow(); + if (!lvalFields(S, Ctx, Elem, FieldDesc->getElemQualType(), Info, + ConstexprKind, CheckedBlocks)) + return false; + } + return true; + } + if (FieldDesc->isPrimitive() && FieldDesc->getPrimType() == PT_MemberPtr) { + MemberPointer MP = Ptr.deref<MemberPointer>(); + if (!EvaluationResult::checkMemberPointer(S, MP, Info, ConstexprKind)) + return false; + } + + return true; +} + +/// Toplevel accessor to check all lvalue fields. +bool EvaluationResult::checkLValueFields(InterpState &S, const Pointer &Ptr, + SourceInfo Info, + ConstantExprKind ConstexprKind) { + QualType SourceType = getStorageType(); + llvm::SmallPtrSet<const Block *, 4> CheckedBlocks; + + return lvalFields(S, Ctx.getASTContext(), Ptr, SourceType, Info, + ConstexprKind, CheckedBlocks); +} + +bool EvaluationResult::checkLValue(InterpState &S, const Pointer &Ptr, + SourceInfo Info, + ConstantExprKind ConstexprKind) { + if (Ptr.isZero()) + return true; + + QualType SourceType = getStorageType(); + if (Ptr.isFunctionPointer()) + return checkFunctionPtr(S, Ptr, SourceType, Info, ConstexprKind); + + bool IsReferenceType = SourceType->isReferenceType(); + if (Ptr.isTypeidPointer()) { + if (isTemplateArgument(ConstexprKind)) { + S.FFDiag(Info, diag::note_constexpr_invalid_template_arg) + << IsReferenceType << /*IsSubObj=*/false << /*InvalidBaseKind=*/0; + return false; + } + return true; + } + + if (!Ptr.isBlockPointer()) + return true; + + const Descriptor *DeclDesc = Ptr.getDeclDesc(); + const Expr *BaseE = DeclDesc->asExpr(); + const ValueDecl *BaseVD = DeclDesc->asValueDecl(); + assert(BaseE || BaseVD); + bool IsSubObj = !Ptr.isRoot() || (Ptr.inArray() && !Ptr.isArrayRoot()); + + // Additional restrictions apply in a template argument. We only enforce the + // C++20 restrictions here; additional syntactic and semantic restrictions + // are applied elsewhere. + if (isTemplateArgument(ConstexprKind)) { + int InvalidBaseKind = -1; + StringRef Ident; + if (isa_and_nonnull<StringLiteral>(BaseE)) + InvalidBaseKind = 1; + else if (isa_and_nonnull<MaterializeTemporaryExpr>(BaseE) || + isa_and_nonnull<LifetimeExtendedTemporaryDecl>(BaseVD)) + InvalidBaseKind = 2; + else if (const auto *PE = dyn_cast_if_present<PredefinedExpr>(BaseE)) { + InvalidBaseKind = 3; + Ident = PE->getIdentKindName(); + IsSubObj = true; + } + + if (InvalidBaseKind != -1) { + S.FFDiag(Info, diag::note_constexpr_invalid_template_arg) + << IsReferenceType << IsSubObj << InvalidBaseKind << Ident; + return false; + } + } + + llvm::SmallPtrSet<const Block *, 4> CheckedBlocks; + if (!lval(S, Ctx.getASTContext(), Ptr, SourceType, Info, ConstexprKind, + CheckedBlocks)) { + return false; + } + + return true; +} + +bool EvaluationResult::checkMemberPointer(InterpState &S, + const MemberPointer &MemberPtr, + SourceInfo Info, + ConstantExprKind ConstexprKind) { + const CXXMethodDecl *MD = MemberPtr.getMemberFunction(); + if (!MD) + return true; + + if (MD->isImmediateFunction()) { + S.FFDiag(Info, diag::note_consteval_address_accessible) + << /*pointer=*/false; + S.Note(MD->getLocation(), diag::note_declared_at); + return false; + } + + if (isForManglingOnly(ConstexprKind) || MD->isVirtual() || + !MD->hasAttr<DLLImportAttr>()) { + return true; + } + return false; +} + +bool EvaluationResult::checkFunctionPointer(InterpState &S, const Pointer &Ptr, + SourceInfo Info, + ConstantExprKind ConstexprKind) { + return checkFunctionPtr(S, Ptr, getStorageType(), Info, ConstexprKind); +} + } // namespace interp } // namespace clang diff --git a/clang/lib/AST/ByteCode/EvaluationResult.h b/clang/lib/AST/ByteCode/EvaluationResult.h index c296cc98ca375..a98ee1dc32c9b 100644 --- a/clang/lib/AST/ByteCode/EvaluationResult.h +++ b/clang/lib/AST/ByteCode/EvaluationResult.h @@ -17,6 +17,7 @@ namespace clang { namespace interp { class EvalEmitter; class Context; +class MemberPointer; class Pointer; class SourceInfo; class InterpState; @@ -39,9 +40,7 @@ class EvaluationResult final { using DeclTy = llvm::PointerUnion<const Decl *, const Expr *>; private: -#ifndef NDEBUG - const Context *Ctx = nullptr; -#endif + const Context &Ctx; APValue Value; ResultKind Kind = Empty; DeclTy Source = nullptr; @@ -62,31 +61,37 @@ class EvaluationResult final { Kind = Valid; } + QualType getStorageType() const; + public: -#ifndef NDEBUG - EvaluationResult(const Context *Ctx) : Ctx(Ctx) {} -#else - EvaluationResult(const Context *Ctx) {} -#endif + EvaluationResult(const Context &Ctx) : Ctx(Ctx) {} bool empty() const { return Kind == Empty; } bool isInvalid() const { return Kind == Invalid; } - - /// Returns an APValue for the evaluation result. - APValue toAPValue() const { - assert(!empty()); - assert(!isInvalid()); - return Value; - } - APValue stealAPValue() { return std::move(Value); } /// Check that all subobjects of the given pointer have been initialized. bool checkFullyInitialized(InterpState &S, const Pointer &Ptr) const; /// Check that none of the blocks the given pointer (transitively) points /// to are dynamically allocated. - bool checkReturnValue(InterpState &S, const Context &Ctx, const Pointer &Ptr, - const SourceInfo &Info); + bool checkDynamicAllocations(InterpState &S, const Context &Ctx, + const Pointer &Ptr, SourceInfo Info); + + /// Check the given pointer as an lvalue, i.e. make sure it's a global + /// lvalue and diagnose if it's not. + bool checkLValue(InterpState &S, const Pointer &Ptr, SourceInfo Info, + ConstantExprKind ConstexprKind); + /// Check all fields of the given pointer. + bool checkLValueFields(InterpState &S, const Pointer &Ptr, SourceInfo Info, + ConstantExprKind ConstexprKind); + + /// Check if the given member pointer can be returned from an evaluation. + static bool checkMemberPointer(InterpState &S, const MemberPointer &MemberPtr, + SourceInfo Info, + ConstantExprKind ConstexprKind); + /// Check if the given function pointer can be returned from an evaluation. + bool checkFunctionPointer(InterpState &S, const Pointer &Ptr, SourceInfo Info, + ConstantExprKind ConstexprKind); QualType getSourceType() const { if (const auto *D = diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 201499ea6e027..4167a433580cc 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -904,7 +904,7 @@ bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { return false; if (!CheckMutable(S, OpPC, Ptr)) return false; - if (Ptr.isConstexprUnknown()) + if (!S.inConstantContext() && isConstexprUnknown(Ptr)) return false; return true; } diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h index 619c79a1408f3..5035306a8249c 100644 --- a/clang/lib/AST/ExprConstShared.h +++ b/clang/lib/AST/ExprConstShared.h @@ -14,6 +14,7 @@ #ifndef LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H #define LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H +#include "ByteCode/State.h" #include "clang/Basic/TypeTraits.h" #include <cstdint> #include <optional> @@ -89,4 +90,46 @@ std::optional<llvm::APFloat> EvalScalarMinMaxFp(const llvm::APFloat &A, const llvm::APFloat &B, std::optional<llvm::APSInt> RoundingMode, bool IsMin); +/// Determines whether the given kind of constant expression is only ever +/// used for name mangling. If so, it's permitted to reference things that we +/// can't generate code for (in particular, dllimported functions). +inline bool isForManglingOnly(ConstantExprKind Kind) { + switch (Kind) { + case ConstantExprKind::Normal: + case ConstantExprKind::ClassTemplateArgument: + case ConstantExprKind::ImmediateInvocation: + // Note that non-type template arguments of class type are emitted as + // template parameter objects. + return false; + + case ConstantExprKind::NonClassTemplateArgument: + return true; + } + llvm_unreachable("unknown ConstantExprKind"); +} + +inline bool isTemplateArgument(ConstantExprKind Kind) { + switch (Kind) { + case ConstantExprKind::Normal: + case ConstantExprKind::ImmediateInvocation: + return false; + + case ConstantExprKind::ClassTemplateArgument: + case ConstantExprKind::NonClassTemplateArgument: + return true; + } + llvm_unreachable("unknown ConstantExprKind"); +} + +/// Should this call expression be treated as forming an opaque constant? +inline bool isOpaqueConstantCall(const CallExpr *E) { + unsigned Builtin = E->getBuiltinCallee(); + return (Builtin == Builtin::BI__builtin___CFStringMakeConstantString || + Builtin == Builtin::BI__builtin___NSStringMakeConstantString || + Builtin == Builtin::BI__builtin_ptrauth_sign_constant || + Builtin == Builtin::BI__builtin_function_start); +} + +bool isGlobalLValue(const ValueDecl *D, const Expr *E); + #endif diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index bc98c0d86bb65..fe32142e17b9a 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -145,37 +145,6 @@ namespace { return E && E->getType()->isPointerType() && tryUnwrapAllocSizeCall(E); } - /// Determines whether the given kind of constant expression is only ever - /// used for name mangling. If so, it's permitted to reference things that we - /// can't generate code for (in particular, dllimported functions). - static bool isForManglingOnly(ConstantExprKind Kind) { - switch (Kind) { - case ConstantExprKind::Normal: - case ConstantExprKind::ClassTemplateArgument: - case ConstantExprKind::ImmediateInvocation: - // Note that non-type template arguments of class type are emitted as - // template parameter objects. - return false; - - case ConstantExprKind::NonClassTemplateArgument: - return true; - } - llvm_unreachable("unknown ConstantExprKind"); - } - - static bool isTemplateArgument(ConstantExprKind Kind) { - switch (Kind) { - case ConstantExprKind::Normal: - case ConstantExprKind::ImmediateInvocation: - return false; - - case ConstantExprKind::ClassTemplateArgument: - case ConstantExprKind::NonClassTemplateArgument: - return true; - } - llvm_unreachable("unknown ConstantExprKind"); - } - /// The bound to claim that an array of unknown bound has. /// The value in MostDerivedArraySize is undefined in this case. So, set it /// to an arbitrary value that's likely to loudly break things if it's used. @@ -1922,31 +1891,30 @@ static bool EvaluateIgnoredValue(EvalInfo &Info, const Expr *E) { return true; } -/// Should this call expression be treated as forming an opaque constant? -static bool IsOpaqueConstantCall(const CallExpr *E) { - unsigned Builtin = E->getBuiltinCallee(); - return (Builtin == Builtin::BI__builtin___CFStringMakeConstantString || - Builtin == Builtin::BI__builtin___NSStringMakeConstantString || - Builtin == Builtin::BI__builtin_ptrauth_sign_constant || - Builtin == Builtin::BI__builtin_function_start); -} - static bool IsOpaqueConstantCall(const LValue &LVal) { const auto *BaseExpr = llvm::dyn_cast_if_present<CallExpr>(LVal.Base.dyn_cast<const Expr *>()); - return BaseExpr && IsOpaqueConstantCall(BaseExpr); + return BaseExpr && isOpaqueConstantCall(BaseExpr); } static bool IsGlobalLValue(APValue::LValueBase B) { + if (B.is<TypeInfoLValue>() || B.is<DynamicAllocLValue>()) + return true; + + return isGlobalLValue(B.dyn_cast<const ValueDecl *>(), + B.dyn_cast<const Expr *>()); +} + +bool isGlobalLValue(const ValueDecl *D, const Expr *E) { // C++11 [expr.const]p3 An address constant expression is a prvalue core // constant expression of pointer type that evaluates to... // ... a null pointer value, or a prvalue core constant expression of type // std::nullptr_t. - if (!B) + if (!D && !E) return true; - if (const ValueDecl *D = B.dyn_cast<const ValueDecl*>()) { + if (D) { // ... the address of an object with static storage duration, if (const VarDecl *VD = dyn_cast<VarDecl>(D)) return VD->hasGlobalStorage(); @@ -1958,10 +1926,8 @@ static bool IsGlobalLValue(APValue::LValueBase B) { return isa<FunctionDecl, MSGuidDecl, UnnamedGlobalConstantDecl>(D); } - if (B.is<TypeInfoLValue>() || B.is<DynamicAllocLValue>()) - return true; + assert(E); - const Expr *E = B.get<const Expr*>(); switch (E->getStmtClass()) { default: return false; @@ -1984,7 +1950,7 @@ static bool IsGlobalLValue(APValue::LValueBase B) { case Expr::ObjCDictionaryLiteralClass: return cast<ObjCObjectLiteral>(E)->isExpressibleAsConstantInitializer(); case Expr::CallExprClass: - return IsOpaqueConstantCall(cast<CallExpr>(E)); + return isOpaqueConstantCall(cast<CallExpr>(E)); // For GCC compatibility, &&label has static storage duration. case Expr::AddrLabelExprClass: return true; @@ -2005,6 +1971,8 @@ static bool IsGlobalLValue(APValue::LValueBase B) { // an expression might be a global lvalue. return true; } + + return false; } static const ValueDecl *GetLValueBaseDecl(const LValue &LVal) { @@ -10351,7 +10319,7 @@ static bool isOneByteCharacterType(QualType T) { bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, unsigned BuiltinOp) { - if (IsOpaqueConstantCall(E)) + if (isOpaqueConstantCall(E)) return Success(E); switch (BuiltinOp) { @@ -21216,12 +21184,8 @@ static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result) { if (!CheckLiteralType(Info, E)) return false; - if (Info.EnableNewConstInterp) { - if (!Info.Ctx.getInterpContext().evaluateAsRValue(Info, E, Result)) - return false; - return CheckConstantExpression(Info, E->getExprLoc(), E->getType(), Result, - ConstantExprKind::Normal); - } + if (Info.EnableNewConstInterp) + return Info.Ctx.getInterpContext().evaluateAsRValue(Info, E, Result); if (!::Evaluate(Result, Info, E)) return false; @@ -21422,14 +21386,8 @@ bool Expr::EvaluateAsLValue(EvalResult &Result, const ASTContext &Ctx, CheckedTemporaries CheckedTemps; if (Info.EnableNewConstInterp) { - if (!Info.Ctx.getInterpContext().evaluate(Info, this, Result.Val, - ConstantExprKind::Normal)) - return false; - - LV.setFrom(Ctx, Result.Val); - return CheckLValueConstantExpression( - Info, getExprLoc(), Ctx.getLValueReferenceType(getType()), LV, - ConstantExprKind::Normal, CheckedTemps); + return Info.Ctx.getInterpContext().evaluate(Info, this, Result.Val, + ConstantExprKind::Normal); } if (!EvaluateLValue(this, LV, Info) || !Info.discardCleanups() || @@ -21481,12 +21439,8 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx, EvalInfo Info(Ctx, Result, EM); Info.InConstantContext = true; - if (Info.EnableNewConstInterp) { - if (!Info.Ctx.getInterpContext().evaluate(Info, this, Result.Val, Kind)) - return false; - return CheckConstantExpression(Info, getExprLoc(), - getStorageType(Ctx, this), Result.Val, Kind); - } + if (Info.EnableNewConstInterp) + return Info.Ctx.getInterpContext().evaluate(Info, this, Result.Val, Kind); // The type of the object we're initializing is 'const T' for a class NTTP. QualType T = getType(); @@ -21565,39 +21519,35 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx, if (Info.EnableNewConstInterp) { auto &InterpCtx = Ctx.getInterpContext(); - if (!InterpCtx.evaluateAsInitializer(Info, VD, this, Value)) - return false; + return InterpCtx.evaluateAsInitializer(Info, VD, this, Value); + } - return CheckConstantExpression(Info, DeclLoc, DeclTy, Value, - ConstantExprKind::Normal); - } else { - LValue LVal; - LVal.set(VD); + LValue LVal; + LVal.set(VD); - { - // C++23 [intro.execution]/p5 - // A full-expression is ... an init-declarator ([dcl.decl]) or a - // mem-initializer. - // So we need to make sure temporary objects are destroyed after having - // evaluated the expression (per C++23 [class.temporary]/p4). - // - // FIXME: Otherwise this may break test/Modules/pr68702.cpp because the - // serialization code calls ParmVarDecl::getDefaultArg() which strips the - // outermost FullExpr, such as ExprWithCleanups. - FullExpressionRAII Scope(Info); - if (!EvaluateInPlace(Value, Info, LVal, this, - /*AllowNonLiteralTypes=*/true) || - EStatus.HasSideEffects) - return false; - } + { + // C++23 [intro.execution]/p5 + // A full-expression is ... an init-declarator ([dcl.decl]) or a + // mem-initializer. + // So we need to make sure temporary objects are destroyed after having + // evaluated the expression (per C++23 [class.temporary]/p4). + // + // FIXME: Otherwise this may break test/Modules/pr68702.cpp because the + // serialization code calls ParmVarDecl::getDefaultArg() which strips the + // outermost FullExpr, such as ExprWithCleanups. + FullExpressionRAII Scope(Info); + if (!EvaluateInPlace(Value, Info, LVal, this, + /*AllowNonLiteralTypes=*/true) || + EStatus.HasSideEffects) + return false; + } - // At this point, any lifetime-extended temporaries are completely - // initialized. - Info.performLifetimeExtension(); + // At this point, any lifetime-extended temporaries are completely + // initialized. + Info.performLifetimeExtension(); - if (!Info.discardCleanups()) - llvm_unreachable("Unhandled cleanup; missing full expression marker?"); - } + if (!Info.discardCleanups()) + llvm_unreachable("Unhandled cleanup; missing full expression marker?"); return CheckConstantExpression(Info, DeclLoc, DeclTy, Value, ConstantExprKind::Normal) && diff --git a/clang/test/AST/ByteCode/builtin-functions.cpp b/clang/test/AST/ByteCode/builtin-functions.cpp index 57157392f6a6e..77ba2ef3d2720 100644 --- a/clang/test/AST/ByteCode/builtin-functions.cpp +++ b/clang/test/AST/ByteCode/builtin-functions.cpp @@ -1972,9 +1972,9 @@ namespace WithinLifetime { constexpr const int &temp = 0; // both-error {{must be initialized by a constant expression}} \ // both-note {{reference to temporary is not a constant expression}} \ // both-note {{temporary created here}} \ - // ref-note {{declared here}} - static_assert(__builtin_is_within_lifetime(&temp)); // ref-error {{not an integral constant expression}} \ - // ref-note {{initializer of 'temp' is not a constant expression}} + // both-note {{declared here}} + static_assert(__builtin_is_within_lifetime(&temp)); // both-error {{not an integral constant expression}} \ + // both-note {{initializer of 'temp' is not a constant expression}} } } diff --git a/clang/test/AST/ByteCode/codegen-constexpr-unknown.cpp b/clang/test/AST/ByteCode/codegen-constexpr-unknown.cpp index 8b70f5a4251ad..3d1e31d5292ed 100644 --- a/clang/test/AST/ByteCode/codegen-constexpr-unknown.cpp +++ b/clang/test/AST/ByteCode/codegen-constexpr-unknown.cpp @@ -21,6 +21,7 @@ void rightscope() { // CHECK-NEXT: entry: // CHECK-NEXT: %p = alloca i32 // CHECK-NEXT: store i32 0, ptr %p +// CHECK-NEXT: ret void /// In the if expression below, the read from s.i should fail. diff --git a/clang/test/AST/ByteCode/references.cpp b/clang/test/AST/ByteCode/references.cpp index a4a8100f5b31e..3da3996aaca75 100644 --- a/clang/test/AST/ByteCode/references.cpp +++ b/clang/test/AST/ByteCode/references.cpp @@ -188,10 +188,9 @@ namespace ReadFromNullBlockPtr { constexpr S s = {&x}; // both-error {{must be initialized by a constant expression}} \ // both-note {{reference to temporary}} \ // both-note {{created here}} \ - // ref-note {{declared here}} \ - // expected-note {{created here}} + // ref-note {{declared here}} static_assert(s.t == &x, ""); // both-error {{not an integral constant expression}} \ - // expected-note {{read of temporary is not allowed in a constant expression outside the expression that created the temporary}} \ + // expected-note {{read of dereferenced null pointer}} \ // ref-note {{initializer of 's' is not a constant expression}} } } diff --git a/clang/test/CodeGenCXX/global-init.cpp b/clang/test/CodeGenCXX/global-init.cpp index 52039a5208223..f10f1be4ce95d 100644 --- a/clang/test/CodeGenCXX/global-init.cpp +++ b/clang/test/CodeGenCXX/global-init.cpp @@ -6,6 +6,14 @@ // RUN: | FileCheck -check-prefix CHECK-NOBUILTIN %s // RUN: %clang_cc1 %std_cxx17- -triple=x86_64-apple-darwin10 -emit-llvm -fexceptions %s -o - | FileCheck %s +// RUN: %clang_cc1 %std_cxx98-14 -triple=x86_64-apple-darwin10 -emit-llvm -fexceptions %s -o - -fexperimental-new-constant-interpreter | FileCheck %s --check-prefixes=CHECK,PRE17 +// RUN: %clang_cc1 %std_cxx98-14 -triple=x86_64-apple-darwin10 -emit-llvm %s -o - -fexperimental-new-constant-interpreter | FileCheck %s --check-prefixes=CHECK-NOEXC,PRE17 +// RUN: %clang_cc1 %std_cxx98-14 -triple=x86_64-apple-darwin10 -emit-llvm -mframe-pointer=non-leaf %s -o - -fexperimental-new-constant-interpreter \ +// RUN: | FileCheck -check-prefix CHECK-FP %s +// RUN: %clang_cc1 %std_cxx98-14 -triple=x86_64-apple-darwin10 -emit-llvm %s -o - -fno-builtin -fexperimental-new-constant-interpreter \ +// RUN: | FileCheck -check-prefix CHECK-NOBUILTIN %s +// RUN: %clang_cc1 %std_cxx17- -triple=x86_64-apple-darwin10 -emit-llvm -fexceptions %s -o - -fexperimental-new-constant-interpreter | FileCheck %s + struct A { A(); ~A(); diff --git a/clang/test/SemaCXX/PR19955.cpp b/clang/test/SemaCXX/PR19955.cpp index cbbe2fe9af164..6fa22ab846374 100644 --- a/clang/test/SemaCXX/PR19955.cpp +++ b/clang/test/SemaCXX/PR19955.cpp @@ -1,5 +1,8 @@ // RUN: %clang_cc1 -triple i686-win32 -verify -std=c++11 %s // RUN: %clang_cc1 -triple i686-mingw32 -verify -std=c++11 %s +// RUN: %clang_cc1 -triple i686-win32 -verify -std=c++11 %s -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -triple i686-mingw32 -verify -std=c++11 %s -fexperimental-new-constant-interpreter + extern int __attribute__((dllimport)) var; constexpr int *varp = &var; // expected-error {{must be initialized by a constant expression}} >From 1db252c569ee73ea0d17d70ab7517a36f126907c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Tue, 16 Jun 2026 08:46:36 +0200 Subject: [PATCH 2/2] Use PtrView in lvalFields() --- clang/lib/AST/ByteCode/EvaluationResult.cpp | 52 ++++++++++++--------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/clang/lib/AST/ByteCode/EvaluationResult.cpp b/clang/lib/AST/ByteCode/EvaluationResult.cpp index cb0cffdce442b..1d75f3589a557 100644 --- a/clang/lib/AST/ByteCode/EvaluationResult.cpp +++ b/clang/lib/AST/ByteCode/EvaluationResult.cpp @@ -293,8 +293,8 @@ static bool checkFunctionPtr(InterpState &S, const Pointer &Ptr, return true; } -static bool lvalFields(InterpState &S, const ASTContext &Ctx, - const Pointer &Ptr, QualType PtrType, SourceInfo Info, +static bool lvalFields(InterpState &S, const ASTContext &Ctx, PtrView Ptr, + QualType PtrType, SourceInfo Info, ConstantExprKind ConstexprKind, llvm::SmallPtrSet<const Block *, 4> &CheckedBlocks); static bool lval(InterpState &S, const ASTContext &Ctx, const Pointer &Ptr, @@ -391,7 +391,7 @@ static bool lval(InterpState &S, const ASTContext &Ctx, const Pointer &Ptr, return false; } } else if (Ptr.getRecord()) { - return lvalFields(S, Ctx, Ptr, Ptr.getType(), Info, + return lvalFields(S, Ctx, Ptr.view(), Ptr.getType(), Info, ConstantExprKind::Normal, CheckedBlocks); } } @@ -399,52 +399,55 @@ static bool lval(InterpState &S, const ASTContext &Ctx, const Pointer &Ptr, return true; } -static bool lvalFields(InterpState &S, const ASTContext &Ctx, - const Pointer &Ptr, QualType PtrType, SourceInfo Info, +static bool lvalFields(InterpState &S, const ASTContext &Ctx, PtrView Ptr, + QualType PtrType, SourceInfo Info, ConstantExprKind ConstexprKind, llvm::SmallPtrSet<const Block *, 4> &CheckedBlocks) { - if (!Ptr.isBlockPointer()) - return true; - const Descriptor *FieldDesc = Ptr.getFieldDesc(); if (const Record *R = Ptr.getRecord()) { if (!R->hasPtrField()) return true; + + for (const Record::Base &B : R->bases()) { + if (!B.R->hasPtrField()) + continue; + + PtrView BasePtr = Ptr.atField(B.Offset); + if (!lvalFields(S, Ctx, BasePtr, B.Desc->getType(), Info, ConstexprKind, + CheckedBlocks)) + return false; + } + for (const Record::Field &F : R->fields()) { + PtrView FieldPtr = Ptr.atField(F.Offset); + if (F.Desc->isPrimitive() && F.Desc->getPrimType() == PT_Ptr) { - QualType FieldType = F.Decl->getType(); - if (!Ptr.atField(F.Offset).isLive()) + if (!FieldPtr.isLive()) return false; - Pointer Pointee = Ptr.atField(F.Offset).deref<Pointer>(); + Pointer Pointee = FieldPtr.deref<Pointer>(); if (CheckedBlocks.insert(Pointee.block()).second) { + QualType FieldType = F.Decl->getType(); if (!lval(S, Ctx, Pointee, FieldType, Info, ConstexprKind, CheckedBlocks)) return false; } } else { - Pointer FieldPtr = Ptr.atField(F.Offset); if (!lvalFields(S, Ctx, FieldPtr, F.Decl->getType(), Info, ConstexprKind, CheckedBlocks)) return false; } } - for (const Record::Base &B : R->bases()) { - Pointer BasePtr = Ptr.atField(B.Offset); - if (!lvalFields(S, Ctx, BasePtr, B.Desc->getType(), Info, ConstexprKind, - CheckedBlocks)) - return false; - } for (const Record::Base &B : R->virtual_bases()) { - Pointer BasePtr = Ptr.atField(B.Offset); + PtrView BasePtr = Ptr.atField(B.Offset); if (!lvalFields(S, Ctx, BasePtr, B.Desc->getType(), Info, ConstexprKind, CheckedBlocks)) return false; } - return true; } + if (FieldDesc->isPrimitiveArray()) { if (FieldDesc->getPrimType() == PT_Ptr) { for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { @@ -460,12 +463,13 @@ static bool lvalFields(InterpState &S, const ASTContext &Ctx, } return true; } + if (FieldDesc->isCompositeArray()) { if (FieldDesc->ElemRecord && !FieldDesc->ElemRecord->hasPtrField()) return true; for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { - Pointer Elem = Ptr.atIndex(I).narrow(); + PtrView Elem = Ptr.atIndex(I).narrow(); if (!lvalFields(S, Ctx, Elem, FieldDesc->getElemQualType(), Info, ConstexprKind, CheckedBlocks)) return false; @@ -485,10 +489,14 @@ static bool lvalFields(InterpState &S, const ASTContext &Ctx, bool EvaluationResult::checkLValueFields(InterpState &S, const Pointer &Ptr, SourceInfo Info, ConstantExprKind ConstexprKind) { + + if (!Ptr.isBlockPointer()) + return true; + QualType SourceType = getStorageType(); llvm::SmallPtrSet<const Block *, 4> CheckedBlocks; - return lvalFields(S, Ctx.getASTContext(), Ptr, SourceType, Info, + return lvalFields(S, Ctx.getASTContext(), Ptr.view(), SourceType, Info, ConstexprKind, CheckedBlocks); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
