https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/186045
>From a4f52894d59c5d375b49c52016a803f1793ab3fb 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] 2 --- clang/lib/AST/ByteCode/ByteCodeEmitter.h | 1 + clang/lib/AST/ByteCode/Compiler.cpp | 6 + clang/lib/AST/ByteCode/Context.cpp | 7 +- clang/lib/AST/ByteCode/Disasm.cpp | 5 +- clang/lib/AST/ByteCode/EvalEmitter.cpp | 51 ++- clang/lib/AST/ByteCode/EvalEmitter.h | 17 +- clang/lib/AST/ByteCode/EvaluationResult.cpp | 361 ++++++++++++++++++ clang/lib/AST/ByteCode/EvaluationResult.h | 30 +- clang/lib/AST/ByteCode/Interp.cpp | 2 +- clang/lib/AST/ExprConstShared.h | 43 +++ clang/lib/AST/ExprConstant.cpp | 145 +++---- 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 + clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 4 +- 17 files changed, 554 insertions(+), 141 deletions(-) diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.h b/clang/lib/AST/ByteCode/ByteCodeEmitter.h index 34342e53837b9..2964e278e22c1 100644 --- a/clang/lib/AST/ByteCode/ByteCodeEmitter.h +++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.h @@ -68,6 +68,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 bfb5df69b0e3a..eb0b48636a361 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -3517,6 +3517,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; @@ -5414,6 +5418,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 ce7a95ed49c06..93e35961dcdb1 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(); 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 a54b6d2c18fa9..380698fd43b9b 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) { @@ -242,6 +243,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) { // llvm::errs()<< __PRETTY_FUNCTION__ << "Ret\n"; if (!isActive()) @@ -254,11 +267,15 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo 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; } @@ -280,6 +297,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)); @@ -287,27 +307,28 @@ 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; // If the variable of this pointer is being evaluated when returning // its value, mark it as constexpr-unknown. - APValue V = Ptr.toAPValue(Ctx.getASTContext()); if (const Descriptor *DeclDesc = Ptr.getDeclDesc(); DeclDesc && S.EvaluatingDecl && - DeclDesc->asVarDecl() == S.EvaluatingDecl && - S.getLangOpts().CPlusPlus23 && - S.EvaluatingDecl->getType()->isReferenceType()) { - V.setConstexprUnknown(true); + ((DeclDesc->asVarDecl() == S.EvaluatingDecl && + S.getLangOpts().CPlusPlus23 && + S.EvaluatingDecl->getType()->isReferenceType()) || + Ptr.getDeclDesc()->IsConstexprUnknown)) { + S.FFDiag(Info, diag::note_constexpr_var_init_non_constant, 1) + << DeclDesc->asVarDecl(); + S.Note(DeclDesc->asVarDecl()->getLocation(), diag::note_declared_at); + + return false; } + + APValue V = Ptr.toAPValue(Ctx.getASTContext()); EvalResult.takeValue(std::move(V)); } @@ -326,6 +347,8 @@ bool EvalEmitter::emitRetValue(SourceInfo 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 26bb22a71a3ce..6d66fec5d7a13 100644 --- a/clang/lib/AST/ByteCode/EvalEmitter.h +++ b/clang/lib/AST/ByteCode/EvalEmitter.h @@ -36,9 +36,14 @@ class EvalEmitter : public SourceMapper { using PtrCallback = llvm::function_ref<bool(InterpState &S, CodePtr OpPC, 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); @@ -60,8 +65,13 @@ class EvalEmitter : public SourceMapper { /// Returns the source location of the current opcode. SourceInfo getSource(CodePtr PC) const override { return CurrentSource; } + 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(); @@ -117,6 +127,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 19a2744f73f15..efbc28d6b2e21 100644 --- a/clang/lib/AST/ByteCode/EvaluationResult.cpp +++ b/clang/lib/AST/ByteCode/EvaluationResult.cpp @@ -7,16 +7,35 @@ //===----------------------------------------------------------------------===// #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.asExpr()) { + if (E->isPRValue()) + return E->getType(); + + return Ctx.getASTContext().getLValueReferenceType(E->getType()); + } + + if (const auto *D = Source.asValueDecl()) + return D->getType(); + return QualType(); +} + static void DiagnoseUninitializedSubobject(InterpState &S, SourceLocation Loc, const FieldDecl *SubObjDecl) { assert(SubObjDecl && "Subobject declaration does not exist"); @@ -249,6 +268,7 @@ bool EvaluationResult::checkDynamicAllocations(InterpState &S, 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; @@ -272,5 +292,346 @@ bool EvaluationResult::checkDynamicAllocations(InterpState &S, return true; } +static bool isGlobalLValue(const Pointer &Ptr) { + if (Ptr.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, 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, + 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.view(), Ptr.getType(), Info, + ConstantExprKind::Normal, CheckedBlocks); + } + } + + return true; +} + +static bool lvalFields(InterpState &S, const ASTContext &Ctx, PtrView Ptr, + QualType PtrType, SourceInfo Info, + ConstantExprKind ConstexprKind, + llvm::SmallPtrSet<const Block *, 4> &CheckedBlocks) { + 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) { + if (!FieldPtr.isLive()) + return false; + + 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 { + if (!lvalFields(S, Ctx, FieldPtr, F.Decl->getType(), Info, + ConstexprKind, CheckedBlocks)) + return false; + } + } + + for (const Record::Base &B : R->virtual_bases()) { + 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) { + 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) { + PtrView 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) { + + if (!Ptr.isBlockPointer()) + return true; + + QualType SourceType = getStorageType(); + llvm::SmallPtrSet<const Block *, 4> CheckedBlocks; + + return lvalFields(S, Ctx.getASTContext(), Ptr.view(), 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 1edd9bc55406a..15739cc1aef53 100644 --- a/clang/lib/AST/ByteCode/EvaluationResult.h +++ b/clang/lib/AST/ByteCode/EvaluationResult.h @@ -18,6 +18,7 @@ namespace clang { namespace interp { class EvalEmitter; class Context; +class MemberPointer; class Pointer; class SourceInfo; class InterpState; @@ -38,9 +39,7 @@ class EvaluationResult final { }; private: -#ifndef NDEBUG - const Context *Ctx = nullptr; -#endif + const Context &Ctx; APValue Value; ResultKind Kind = Empty; DeclOrExpr Source = nullptr; @@ -63,17 +62,14 @@ 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; } - /// Moves the APValue containing the evaluation result to the caller. APValue stealAPValue() { return std::move(Value); } /// Check that all subobjects of the given pointer have been initialized. @@ -83,6 +79,22 @@ class EvaluationResult final { 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 = Source.asValueDecl()) return D->getType(); diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 1f1db99973f2b..e3f7a37e5aede 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -991,7 +991,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 cdf2a5697528e..0dd59cd1de240 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/BuiltinTraits.h" #include <cstdint> #include <optional> @@ -110,4 +111,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 480d5119a5363..12e8a1e4481ad 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -149,37 +149,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. @@ -1939,31 +1908,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(); @@ -1975,10 +1943,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; @@ -2001,7 +1967,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; @@ -2022,6 +1988,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) { @@ -10614,7 +10582,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) { @@ -21730,12 +21698,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; @@ -21936,14 +21900,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() || @@ -21995,12 +21953,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(); @@ -22075,40 +22029,35 @@ bool Expr::EvaluateAsInitializer(const ASTContext &Ctx, const VarDecl *VD, if (Info.EnableNewConstInterp) { auto &InterpCtx = Ctx.getInterpContext(); - if (!InterpCtx.evaluateAsInitializer(Info, VD, this, EStatus.Val)) - return false; - - return CheckConstantExpression(Info, DeclLoc, DeclTy, EStatus.Val, - ConstantExprKind::Normal); - } else { - 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(EStatus.Val, Info, LVal, this, - /*AllowNonLiteralTypes=*/true) || - EStatus.HasSideEffects) - return false; - } + return InterpCtx.evaluateAsInitializer(Info, VD, this, EStatus.Val); + } - // At this point, any lifetime-extended temporaries are completely - // initialized. - Info.performLifetimeExtension(); + LValue LVal; + LVal.set(VD); - if (!Info.discardCleanups()) - llvm_unreachable("Unhandled cleanup; missing full expression marker?"); + { + // 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(EStatus.Val, Info, LVal, this, + /*AllowNonLiteralTypes=*/true) || + EStatus.HasSideEffects) + return false; } + // At this point, any lifetime-extended temporaries are completely + // initialized. + Info.performLifetimeExtension(); + + if (!Info.discardCleanups()) + llvm_unreachable("Unhandled cleanup; missing full expression marker?"); return CheckConstantExpression(Info, DeclLoc, DeclTy, EStatus.Val, ConstantExprKind::Normal) && CheckMemoryLeaks(Info); diff --git a/clang/test/AST/ByteCode/builtin-functions.cpp b/clang/test/AST/ByteCode/builtin-functions.cpp index b14ae91c753cb..90f6e2569e2c2 100644 --- a/clang/test/AST/ByteCode/builtin-functions.cpp +++ b/clang/test/AST/ByteCode/builtin-functions.cpp @@ -2014,9 +2014,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}} diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp index dd450a8f1b76c..e683ecdf5c15c 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp @@ -799,8 +799,8 @@ void not_constant_expression() { expected-note {{in instantiation of expansion statement requested here}} \ old-interp-note {{read of variable '[__u0]' whose value is not known}} \ old-interp-note {{declared here}} \ - new-interp-note {{temporary created here}} \ - new-interp-note {{read of temporary is not allowed in a constant expression outside the expression that created the temporary}} + new-interp-note {{declared here}} \ + new-interp-note {{initializer of '[__u0]' is not a constant expression}} g(x); } } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
