https://github.com/MitalAshok updated https://github.com/llvm/llvm-project/pull/91895
>From 56aed689dc5825fc5bacc6dfdff58ee0eaf71f82 Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Sun, 12 May 2024 19:48:24 +0100 Subject: [PATCH 1/8] [Clang] Add attribute for consteval builtins; Declare constexpr builtins as constexpr in C++ Also support redeclaring now-constexpr builtins without constexpr --- clang/include/clang/Basic/Builtins.h | 5 +++++ clang/include/clang/Basic/BuiltinsBase.td | 2 ++ clang/lib/Sema/SemaDecl.cpp | 15 +++++++++++---- clang/lib/Sema/SemaDeclCXX.cpp | 18 +++++++++++++----- clang/lib/Sema/SemaExpr.cpp | 8 ++++++-- clang/test/Sema/builtin-redecl.cpp | 15 ++++++++++----- 6 files changed, 47 insertions(+), 16 deletions(-) diff --git a/clang/include/clang/Basic/Builtins.h b/clang/include/clang/Basic/Builtins.h index f955d21169556..e85ec5b2dca14 100644 --- a/clang/include/clang/Basic/Builtins.h +++ b/clang/include/clang/Basic/Builtins.h @@ -280,6 +280,11 @@ class Context { return strchr(getRecord(ID).Attributes, 'E') != nullptr; } + /// Returns true if this is an immediate (consteval) function + bool isImmediate(unsigned ID) const { + return strchr(getRecord(ID).Attributes, 'G') != nullptr; + } + private: const Info &getRecord(unsigned ID) const; diff --git a/clang/include/clang/Basic/BuiltinsBase.td b/clang/include/clang/Basic/BuiltinsBase.td index 724747ec76d73..1196b9e15c10d 100644 --- a/clang/include/clang/Basic/BuiltinsBase.td +++ b/clang/include/clang/Basic/BuiltinsBase.td @@ -70,6 +70,8 @@ class VScanfFormat<int I> : IndexedAttribute<"S", I>; // Builtin can be constant evaluated def Constexpr : Attribute<"E">; +// Builtin is immediate and must be constant evaluated. Implies Constexpr. +def Consteval : Attribute<"EG">; // Builtin kinds // ============= diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index fb913034bd836..6b0a04585928a 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2409,10 +2409,17 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type, Parent = CLinkageDecl; } - FunctionDecl *New = FunctionDecl::Create(Context, Parent, Loc, Loc, II, Type, - /*TInfo=*/nullptr, SC_Extern, - getCurFPFeatures().isFPConstrained(), - false, Type->isFunctionProtoType()); + ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified; + if (getLangOpts().CPlusPlus && Context.BuiltinInfo.isConstantEvaluated(ID)) { + ConstexprKind = ConstexprSpecKind::Constexpr; + if (Context.BuiltinInfo.isImmediate(ID)) + ConstexprKind = ConstexprSpecKind::Consteval; + } + + FunctionDecl *New = FunctionDecl::Create( + Context, Parent, Loc, Loc, II, Type, /*TInfo=*/nullptr, SC_Extern, + getCurFPFeatures().isFPConstrained(), /*isInlineSpecified=*/false, + Type->isFunctionProtoType(), ConstexprKind); New->setImplicit(); New->addAttr(BuiltinAttr::CreateImplicit(Context, ID)); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 53238d355ea09..1b558d70f9b48 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -676,11 +676,19 @@ bool Sema::MergeCXXFunctionDecl(FunctionDecl *New, FunctionDecl *Old, // template has a constexpr specifier then all its declarations shall // contain the constexpr specifier. if (New->getConstexprKind() != Old->getConstexprKind()) { - Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch) - << New << static_cast<int>(New->getConstexprKind()) - << static_cast<int>(Old->getConstexprKind()); - Diag(Old->getLocation(), diag::note_previous_declaration); - Invalid = true; + if (Old->getBuiltinID() && + Old->getConstexprKind() == ConstexprSpecKind::Constexpr && + New->getConstexprKind() == ConstexprSpecKind::Unspecified) { + // Except allow redeclaring a builtin as non-constexpr to match C + // redeclarations which will not be constexpr + New->setConstexprKind(ConstexprSpecKind::Constexpr); + } else { + Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch) + << New << static_cast<int>(New->getConstexprKind()) + << static_cast<int>(Old->getConstexprKind()); + Diag(Old->getLocation(), diag::note_previous_declaration); + Invalid = true; + } } else if (!Old->getMostRecentDecl()->isInlined() && New->isInlined() && Old->isDefined(Def) && // If a friend function is inlined but does not have 'inline' diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index bb4b116fd73ca..39aa32526d2b1 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -7095,8 +7095,12 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl, } // Bail out early if calling a builtin with custom type checking. - if (BuiltinID && Context.BuiltinInfo.hasCustomTypechecking(BuiltinID)) - return CheckBuiltinFunctionCall(FDecl, BuiltinID, TheCall); + if (BuiltinID && Context.BuiltinInfo.hasCustomTypechecking(BuiltinID)) { + ExprResult E = CheckBuiltinFunctionCall(FDecl, BuiltinID, TheCall); + if (!E.isInvalid() && Context.BuiltinInfo.isImmediate(BuiltinID)) + E = CheckForImmediateInvocation(E, FDecl); + return E; + } if (getLangOpts().CUDA) { if (Config) { diff --git a/clang/test/Sema/builtin-redecl.cpp b/clang/test/Sema/builtin-redecl.cpp index 323c63e202883..31409a4d46a65 100644 --- a/clang/test/Sema/builtin-redecl.cpp +++ b/clang/test/Sema/builtin-redecl.cpp @@ -14,13 +14,18 @@ void __builtin_va_copy(double d); // expected-error@+2 {{cannot redeclare builtin function '__builtin_va_end'}} // expected-note@+1 {{'__builtin_va_end' is a builtin with type}} void __builtin_va_end(__builtin_va_list); -// RUN: %clang_cc1 %s -fsyntax-only -verify -// RUN: %clang_cc1 %s -fsyntax-only -verify -x c void __va_start(__builtin_va_list*, ...); + void *__builtin_assume_aligned(const void *, size_t, ...); #ifdef __cplusplus -void *__builtin_assume_aligned(const void *, size_t, ...) noexcept; -#else -void *__builtin_assume_aligned(const void *, size_t, ...); +constexpr void *__builtin_assume_aligned(const void *, size_t, ...); + void *__builtin_assume_aligned(const void *, size_t, ...) noexcept; +constexpr void *__builtin_assume_aligned(const void *, size_t, ...) noexcept; + void *__builtin_assume_aligned(const void *, size_t, ...) throw(); +constexpr void *__builtin_assume_aligned(const void *, size_t, ...) throw(); + +// expected-error@+1 {{constexpr declaration of '__builtin_calloc' follows non-constexpr declaration}} +constexpr void *__builtin_calloc(size_t, size_t); +// expected-note@-1 {{previous declaration is here}} #endif >From 253f58f0e34bc6dedbbfe17f68cfe9baa3a9146f Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Sun, 12 May 2024 18:42:49 +0100 Subject: [PATCH 2/8] [Clang] Add __builtin_is_within_lifetime to implement P2641R4's std::is_within_lifetime --- clang/include/clang/Basic/Builtins.td | 6 + .../include/clang/Basic/DiagnosticASTKinds.td | 28 ++- .../clang/Basic/DiagnosticSemaKinds.td | 3 + clang/lib/AST/ExprConstant.cpp | 89 +++++++- clang/lib/AST/Interp/State.h | 1 + clang/lib/Sema/SemaChecking.cpp | 26 +++ .../SemaCXX/builtin-is-within-lifetime.cpp | 211 ++++++++++++++++++ 7 files changed, 345 insertions(+), 19 deletions(-) create mode 100644 clang/test/SemaCXX/builtin-is-within-lifetime.cpp diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 11982af3fa609..04660122565b8 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -932,6 +932,12 @@ def IsConstantEvaluated : LangBuiltin<"CXX_LANG"> { let Prototype = "bool()"; } +def IsWithinLifetime : LangBuiltin<"CXX_LANG"> { + let Spellings = ["__builtin_is_within_lifetime"]; + let Attributes = [NoThrow, CustomTypeChecking, Consteval]; + let Prototype = "bool(void*)"; +} + // GCC exception builtins def EHReturn : Builtin { let Spellings = ["__builtin_eh_return"]; diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index a024f9b2a9f8c..7f8908e45663b 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -169,12 +169,12 @@ def note_constexpr_this : Note< def note_constexpr_lifetime_ended : Note< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 %select{temporary|variable}1 whose " + "destruction of|read of}0 %select{temporary|variable}1 whose " "%plural{8:storage duration|:lifetime}0 has ended">; def note_constexpr_access_uninit : Note< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|" - "construction of subobject of|destruction of}0 " + "construction of subobject of|destruction of|read of}0 " "%select{object outside its lifetime|uninitialized object}1 " "is not allowed in a constant expression">; def note_constexpr_use_uninit_reference : Note< @@ -185,11 +185,11 @@ def note_constexpr_modify_const_type : Note< "in a constant expression">; def note_constexpr_access_volatile_type : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "<ERROR>|<ERROR>|<ERROR>|<ERROR>}0 " + "<ERROR>|<ERROR>|<ERROR>|<ERROR>|read of}0 " "volatile-qualified type %1 is not allowed in a constant expression">; def note_constexpr_access_volatile_obj : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "<ERROR>|<ERROR>|<ERROR>|<ERROR>}0 " + "<ERROR>|<ERROR>|<ERROR>|<ERROR>|read of}0 " "volatile %select{temporary|object %2|member %2}1 is not allowed in " "a constant expression">; def note_constexpr_volatile_here : Note< @@ -197,7 +197,7 @@ def note_constexpr_volatile_here : Note< def note_constexpr_access_mutable : Note< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " + "destruction of|read of}0 " "mutable member %1 is not allowed in a constant expression">; def note_constexpr_ltor_non_const_int : Note< "read of non-const variable %0 is not allowed in a constant expression">; @@ -211,24 +211,24 @@ def note_constexpr_ltor_incomplete_type : Note< def note_constexpr_access_null : Note< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " + "destruction of|read of}0 " "dereferenced null pointer is not allowed in a constant expression">; def note_constexpr_access_past_end : Note< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " + "destruction of|read of}0 " "dereferenced one-past-the-end pointer is not allowed " "in a constant expression">; def note_constexpr_access_unsized_array : Note< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " + "destruction of|read of}0 " "element of array without known bound " "is not allowed in a constant expression">; def note_constexpr_access_inactive_union_member : Note< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|" - "construction of subobject of|destruction of}0 " + "construction of subobject of|destruction of|read of}0 " "member %1 of union with %select{active member %3|no active member}2 " "is not allowed in a constant expression">; def note_constexpr_union_member_change_during_init : Note< @@ -237,18 +237,18 @@ def note_constexpr_union_member_change_during_init : Note< def note_constexpr_access_static_temporary : Note< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|reconstruction of|" - "destruction of}0 temporary " + "destruction of|read of|read of}0 temporary " "is not allowed in a constant expression outside the expression that " "created the temporary">; def note_constexpr_access_unreadable_object : Note< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " + "destruction of|read of}0 " "object '%1' whose value is not known">; def note_constexpr_access_deleted_object : Note< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " + "destruction of|read of}0 " "heap allocated object that has been deleted">; def note_constexpr_modify_global : Note< "a constant expression cannot modify an object that is visible outside " @@ -425,6 +425,10 @@ def warn_is_constant_evaluated_always_true_constexpr : Warning< "'%0' will always evaluate to 'true' in a manifestly constant-evaluated expression">, InGroup<DiagGroup<"constant-evaluated">>; +def err_invalid_is_within_lifetime : Error< + "'%0' cannot be called with %select{a null pointer|a function pointer|a one-past-the-end pointer|a pointer to an object whose lifetime has not begun or has ended}1" +>; + // inline asm related. let CategoryName = "Inline Assembly Issue" in { def err_asm_invalid_escape : Error< diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 9e82130c93609..3f60b86544953 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -12040,6 +12040,9 @@ def err_builtin_launder_invalid_arg : Error< "%select{non-pointer|function pointer|void pointer}0 argument to " "'__builtin_launder' is not allowed">; +def err_builtin_is_within_lifetime_invalid_arg : Error< + "non-pointer argument to '__builtin_is_within_lifetime' is not allowed">; + def err_builtin_invalid_arg_type: Error < "%ordinal0 argument must be " "%select{a vector, integer or floating point type|a matrix|" diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index f1aa19e4409e1..f3b31d87dde9b 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1507,7 +1507,7 @@ CallStackFrame::~CallStackFrame() { } static bool isRead(AccessKinds AK) { - return AK == AK_Read || AK == AK_ReadObjectRepresentation; + return AK == AK_Read || AK == AK_ReadObjectRepresentation || AK == AK_IsWithinLifetime; } static bool isModification(AccessKinds AK) { @@ -1517,6 +1517,7 @@ static bool isModification(AccessKinds AK) { case AK_MemberCall: case AK_DynamicCast: case AK_TypeId: + case AK_IsWithinLifetime: return false; case AK_Assign: case AK_Increment: @@ -1534,7 +1535,7 @@ static bool isAnyAccess(AccessKinds AK) { /// Is this an access per the C++ definition? static bool isFormalAccess(AccessKinds AK) { - return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy; + return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy && AK != AK_IsWithinLifetime; } /// Is this kind of axcess valid on an indeterminate object value? @@ -1546,6 +1547,7 @@ static bool isValidIndeterminateAccess(AccessKinds AK) { // These need the object's value. return false; + case AK_IsWithinLifetime: case AK_ReadObjectRepresentation: case AK_Assign: case AK_Construct: @@ -3653,7 +3655,7 @@ struct CompleteObject { // In C++14 onwards, it is permitted to read a mutable member whose // lifetime began within the evaluation. // FIXME: Should we also allow this in C++11? - if (!Info.getLangOpts().CPlusPlus14) + if (!Info.getLangOpts().CPlusPlus14 && AK != AccessKinds::AK_IsWithinLifetime) return false; return lifetimeStartedInEvaluation(Info, Base, /*MutableSubobject*/true); } @@ -3706,6 +3708,9 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, if ((O->isAbsent() && !(handler.AccessKind == AK_Construct && I == N)) || (O->isIndeterminate() && !isValidIndeterminateAccess(handler.AccessKind))) { + // Object has ended lifetime since pointer was formed + if (handler.AccessKind == AK_IsWithinLifetime) + return false; if (!Info.checkingPotentialConstantExpression()) Info.FFDiag(E, diag::note_constexpr_access_uninit) << handler.AccessKind << O->isIndeterminate() @@ -3852,6 +3857,9 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, // Placement new onto an inactive union member makes it active. O->setUnion(Field, APValue()); } else { + // Pointer to/into inactive union member: Not within lifetime + if (handler.AccessKind == AK_IsWithinLifetime) + return false; // FIXME: If O->getUnionValue() is absent, report that there's no // active union member rather than reporting the prior active union // member. We'll need to fix nullptr_t to not use APValue() as its @@ -4051,9 +4059,11 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, std::tie(Frame, Depth) = Info.getCallFrameAndDepth(LVal.getLValueCallIndex()); if (!Frame) { - Info.FFDiag(E, diag::note_constexpr_lifetime_ended, 1) - << AK << LVal.Base.is<const ValueDecl*>(); - NoteLValueLocation(Info, LVal.Base); + if (AK != AccessKinds::AK_IsWithinLifetime) { + Info.FFDiag(E, diag::note_constexpr_lifetime_ended, 1) + << AK << LVal.Base.is<const ValueDecl *>(); + NoteLValueLocation(Info, LVal.Base); + } return CompleteObject(); } } @@ -4082,6 +4092,8 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, // This is the object whose initializer we're evaluating, so its lifetime // started in the current evaluation. BaseVal = Info.EvaluatingDeclValue; + if (AK == AccessKinds::AK_IsWithinLifetime) + return CompleteObject(); // Not within lifetime } else if (const ValueDecl *D = LVal.Base.dyn_cast<const ValueDecl *>()) { // Allow reading from a GUID declaration. if (auto *GD = dyn_cast<MSGuidDecl>(D)) { @@ -4213,7 +4225,8 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } else if (DynamicAllocLValue DA = LVal.Base.dyn_cast<DynamicAllocLValue>()) { std::optional<DynAlloc *> Alloc = Info.lookupDynamicAlloc(DA); if (!Alloc) { - Info.FFDiag(E, diag::note_constexpr_access_deleted_object) << AK; + if (AK != AccessKinds::AK_IsWithinLifetime) + Info.FFDiag(E, diag::note_constexpr_access_deleted_object) << AK; return CompleteObject(); } return CompleteObject(LVal.Base, &(*Alloc)->Value, @@ -11495,6 +11508,8 @@ class IntExprEvaluator bool ZeroInitialization(const Expr *E) { return Success(0, E); } + friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &, const CallExpr *); + //===--------------------------------------------------------------------===// // Visitor Methods //===--------------------------------------------------------------------===// @@ -12546,6 +12561,11 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, return Success(Info.InConstantContext, E); } + case Builtin::BI__builtin_is_within_lifetime: + if (auto result = EvaluateBuiltinIsWithinLifetime(*this, E)) + return Success(*result, E); + return false; + case Builtin::BI__builtin_ctz: case Builtin::BI__builtin_ctzl: case Builtin::BI__builtin_ctzll: @@ -17008,3 +17028,58 @@ bool Expr::tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const { EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantFold); return EvaluateBuiltinStrLen(this, Result, Info); } + +namespace { + struct IsWithinLifetimeHandler { + EvalInfo &Info; + static constexpr AccessKinds AccessKind = AccessKinds::AK_IsWithinLifetime; + using result_type = std::optional<bool>; + std::optional<bool> failed() { return std::nullopt; } + template<typename T> + std::optional<bool> found(T &Subobj, QualType SubobjType) { + return true; + } + }; + + std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, const CallExpr *E) { + EvalInfo& Info = IEE.Info; + //assert(Info.InConstantContext && "Call to consteval builtin not in constant context?"); + assert(E->getBuiltinCallee() == Builtin::BI__builtin_is_within_lifetime); + const Expr *Arg = E->getArg(0); + if (Arg->isValueDependent()) + return std::nullopt; + LValue Val; + if (!EvaluatePointer(Arg, Val, Info)) + return std::nullopt; + + auto Error = [&](int Diag) { + const auto *Callee = Info.CurrentCall->getCallee(); + bool CalledFromStd = Callee && Callee->isInStdNamespace() && Callee->getIdentifier() && Callee->getIdentifier()->isStr("is_within_lifetime"); + Info.report(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin() : E->getExprLoc(), diag::err_invalid_is_within_lifetime) + << (CalledFromStd ? "std::is_within_lifetime" : "__builtin_is_within_lifetime") << Diag; + return std::nullopt; + }; + // C++2c [meta.const.eval]p4: + // During the evaluation of an expression E as a core constant expression, a call to this function is ill-formed unless p points to an object that is usable in constant expressions or whose complete object's lifetime began within E. + + // Make sure it points to an object + // nullptr does not point to an object + if (Val.isNullPointer() || Val.getLValueBase().isNull()) + return Error(0); + QualType T = Val.getLValueBase().getType(); + if (T->isFunctionType()) + return Error(1); + assert(T->isObjectType()); + // Hypothetical array element is not an object + if (Val.getLValueDesignator().isOnePastTheEnd()) + return Error(2); + assert(Val.getLValueDesignator().isValidSubobject() && "Unchecked case for valid subobject"); + // All other ill-formed values should have failed EvaluatePointer, so the object should be a pointer to an object + // that is usable in a constant expression or whose complete lifetime began within the expression + CompleteObject CO = findCompleteObject(Info, E, AccessKinds::AK_IsWithinLifetime, Val, T); + if (!CO) + return false; + IsWithinLifetimeHandler handler{ Info }; + return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler); + } +} diff --git a/clang/lib/AST/Interp/State.h b/clang/lib/AST/Interp/State.h index f1e8e3618f34f..65039a67c7de4 100644 --- a/clang/lib/AST/Interp/State.h +++ b/clang/lib/AST/Interp/State.h @@ -34,6 +34,7 @@ enum AccessKinds { AK_TypeId, AK_Construct, AK_Destroy, + AK_IsWithinLifetime, }; /// The order of this enum is important for diagnostics. diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 54789dde50691..1ca1d34c58b8a 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -2215,6 +2215,30 @@ static ExprResult BuiltinLaunder(Sema &S, CallExpr *TheCall) { return TheCall; } +static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) { + if (checkArgCount(S, TheCall, 1)) + return ExprError(); + + ExprResult Arg = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(0)); + if (Arg.isInvalid()) + return ExprError(); + QualType ParamTy = Arg.get()->getType(); + TheCall->setArg(0, Arg.get()); + TheCall->setType(S.Context.BoolTy); + + // A call to this function is always ill-formed if the type is not a pointer to + // an object type. There is no Mandates: to that effect, so we can only + // issue an error if it is actually evaluated as part of a constant evaluation + // (e.g., `false ? true : std::is_within_lifetime(static_cast<void(*)()>(nullptr));` is fine) + // However, `std::is_within_lifetime` will only take pointer types (allow non-const qualified too) + if (!ParamTy->isPointerType()) { + S.Diag(TheCall->getArg(0)->getExprLoc(), diag::err_builtin_is_within_lifetime_invalid_arg); + return ExprError(); + } + + return TheCall; +} + // Emit an error and return true if the current object format type is in the // list of unsupported types. static bool CheckBuiltinTargetNotInUnsupported( @@ -2641,6 +2665,8 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, } case Builtin::BI__builtin_launder: return BuiltinLaunder(*this, TheCall); + case Builtin::BI__builtin_is_within_lifetime: + return BuiltinIsWithinLifetime(*this, TheCall); case Builtin::BI__sync_fetch_and_add: case Builtin::BI__sync_fetch_and_add_1: case Builtin::BI__sync_fetch_and_add_2: diff --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp new file mode 100644 index 0000000000000..fcc6303e051e6 --- /dev/null +++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp @@ -0,0 +1,211 @@ +// RUN: %clang_cc1 -std=c++2c -verify %s -fcxx-exceptions -Wno-unused -triple=x86_64-linux-gnu + +inline void* operator new(__SIZE_TYPE__, void* p) noexcept { return p; } +namespace std { +template<typename T, typename... Args> +constexpr T* construct_at(T* p, Args&&... args) { return ::new((void*)p) T(static_cast<Args&&>(args)...); } +template<typename T> +constexpr void destroy_at(T* p) { p->~T(); } +template<typename T> +struct allocator { + constexpr T* allocate(__SIZE_TYPE__ n) { return static_cast<T*>(::operator new(n * sizeof(T))); } + constexpr void deallocate(T* p, __SIZE_TYPE__) { ::operator delete(p); } +}; +} + +constexpr void check_immediate() { + if consteval { + if (false) __builtin_is_within_lifetime(static_cast<int*>(nullptr)); + } +} +auto* not_escalated = &check_immediate; + +template<typename T> +constexpr bool check_immediate2() { // #check_immediate2 + T i{}; + if (false) + __builtin_is_within_lifetime(&i); // #check_immediate2-call + return true; +} +static_assert(check_immediate2<int>()); +auto* escalated = &check_immediate2<int>; +// expected-error@-1 {{cannot take address of immediate function 'check_immediate2<int>' outside of an immediate invocation}} +// expected-note@#check_immediate2 {{declared here}} +// expected-note@#check_immediate2-call {{'check_immediate2<int>' is an immediate function because its body contains a call to a consteval function '__builtin_is_within_lifetime' and that call is not a constant expression}} + +consteval bool test_union(int& i, char& c) { + if (__builtin_is_within_lifetime(&i) || __builtin_is_within_lifetime(&c)) + return false; + std::construct_at(&c, 1); + if (__builtin_is_within_lifetime(&i) || !__builtin_is_within_lifetime(&c)) + return false; + std::construct_at(&i, 3); + if (!__builtin_is_within_lifetime(&i) || __builtin_is_within_lifetime(&c)) + return false; + return true; +} + +static_assert([]{ + union { int i; char c; } u; + return test_union(u.i, u.c); +}()); +static_assert([]{ + union { int i; char c; }; + return test_union(i, c); +}()); +static_assert([]{ + struct { union { int i; char c; }; } u; + return test_union(u.i, u.c); +}()); +static_assert([]{ + struct { union { int i; char c; } u; } r; + return test_union(r.u.i, r.u.c); +}()); + +consteval bool test_nested() { + union { + union { int i; char c; } u; + long l; + }; + if (__builtin_is_within_lifetime(&l) || __builtin_is_within_lifetime(&u) || __builtin_is_within_lifetime(&u.i) || __builtin_is_within_lifetime(&u.c)) + return false; + std::construct_at(&l); + if (!__builtin_is_within_lifetime(&l) || __builtin_is_within_lifetime(&u) || __builtin_is_within_lifetime(&u.i) || __builtin_is_within_lifetime(&u.c)) + return false; + std::construct_at(&u); + std::construct_at(&u.i); + if (__builtin_is_within_lifetime(&l) || !__builtin_is_within_lifetime(&u) || !__builtin_is_within_lifetime(&u.i) || __builtin_is_within_lifetime(&u.c)) + return false; + std::construct_at(&u.c); + if (__builtin_is_within_lifetime(&l) || !__builtin_is_within_lifetime(&u) || __builtin_is_within_lifetime(&u.i) || !__builtin_is_within_lifetime(&u.c)) + return false; + return true; +} +static_assert(test_nested()); + +consteval bool test_dynamic() { + std::allocator<int> a; + int* p = a.allocate(1); + if (__builtin_is_within_lifetime(p)) + return false; + std::construct_at(p); + if (!__builtin_is_within_lifetime(p)) + return false; + std::destroy_at(p); + if (__builtin_is_within_lifetime(p)) + return false; + a.deallocate(p, 1); + if (__builtin_is_within_lifetime(p)) + return false; + return true; +} +static_assert(test_dynamic()); + +constexpr bool self = __builtin_is_within_lifetime(&self); +// expected-error@-1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}} +// expected-note@-2 {{initializer of 'self' is unknown}} +// expected-note@-3 {{declared here}} +constexpr int external{}; +static_assert(__builtin_is_within_lifetime(&external)); +bool not_constexpr() { + return __builtin_is_within_lifetime(&external); +} +constexpr struct { + union { + int i; + char c; + }; + mutable int mi; // #x-mi +} x1{ .c = 2 }; +static_assert(!__builtin_is_within_lifetime(&x1.i)); +static_assert(__builtin_is_within_lifetime(&x1.c)); +static_assert(__builtin_is_within_lifetime(&x1.mi)); +// expected-error@-1 {{static assertion expression is not an integral constant expression}} +// expected-note@-2 {{read of mutable member 'mi' is not allowed in a constant expression}} +// expected-note@#x-mi {{declared here}} + +constexpr struct { + bool a = __builtin_is_within_lifetime(&b); + bool b = __builtin_is_within_lifetime(&a); + bool c = __builtin_is_within_lifetime(this); +} x2; +static_assert(!x2.a); +static_assert(!x2.b); +static_assert(!x2.c); + +struct X3 { + bool a, b, c, d; + consteval X3(); +}; +extern const X3 x3; +consteval X3::X3() : a(__builtin_is_within_lifetime(&b)), b(false), c(__builtin_is_within_lifetime(&b)) { + b = __builtin_is_within_lifetime(&b); + d = __builtin_is_within_lifetime(&x3.c); +} +constexpr X3 x3{}; +static_assert(!x3.a); +static_assert(!x3.b); +static_assert(!x3.c); +static_assert(!x3.d); + +constexpr int i = 2; +static_assert(__builtin_is_within_lifetime(const_cast<int*>(&i))); +static_assert(__builtin_is_within_lifetime(const_cast<volatile int*>(&i))); +static_assert(__builtin_is_within_lifetime(static_cast<const void*>(&i))); + +constexpr int arr[2]{}; +static_assert(__builtin_is_within_lifetime(arr)); +static_assert(__builtin_is_within_lifetime(arr + 0)); +static_assert(__builtin_is_within_lifetime(arr + 1)); +void f() { + __builtin_is_within_lifetime(&i + 1); +// expected-error@-1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}} +// expected-error@-2 {{'__builtin_is_within_lifetime' cannot be called with a one-past-the-end pointer}} + __builtin_is_within_lifetime(arr + 2); +// expected-error@-1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}} +// expected-error@-2 {{'__builtin_is_within_lifetime' cannot be called with a one-past-the-end pointer}} +} + +template<typename T> +consteval bool allow_bad_types_unless_used(bool b, T* p) { + if (b) { + __builtin_is_within_lifetime(p); // #bad_type_used + } + return true; +} +void fn(); +static_assert(allow_bad_types_unless_used<void()>(false, &fn)); +void g() { + allow_bad_types_unless_used<void()>(true, &fn); +// expected-error@-1 {{call to consteval function 'allow_bad_types_unless_used<void ()>' is not a constant expression}} +// expected-error@#bad_type_used {{'__builtin_is_within_lifetime' cannot be called with a function pointer}} +} + +struct OptBool { + union { bool b; char c; }; + + // note: this assumes common implementation properties for bool and char: + // * sizeof(bool) == sizeof(char), and + // * the value representations for true and false are distinct + // from the value representation for 2 + constexpr OptBool() : c(2) { } + constexpr OptBool(bool b) : b(b) { } + + constexpr auto has_value() const -> bool { + if consteval { + return __builtin_is_within_lifetime(&b); // during constant evaluation, cannot read from c + } else { + return c != 2; // during runtime, must read from c + } + } + + constexpr auto operator*() const -> const bool& { + return b; + } +}; + +constexpr OptBool disengaged; +constexpr OptBool engaged(true); +static_assert(!disengaged.has_value()); +static_assert(engaged.has_value()); +static_assert(*engaged); >From 88c68cdffe8bf94d8ecca651d6162e8b601cdc38 Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Tue, 14 May 2024 18:29:48 +0100 Subject: [PATCH 3/8] No longer declare constexpr builtins as constexpr --- clang/lib/Sema/SemaDecl.cpp | 6 ++---- clang/lib/Sema/SemaDeclCXX.cpp | 18 +++++------------- clang/test/Sema/builtin-redecl.cpp | 15 +++++---------- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 6b0a04585928a..7ca02b5bbf624 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2410,10 +2410,8 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type, } ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified; - if (getLangOpts().CPlusPlus && Context.BuiltinInfo.isConstantEvaluated(ID)) { - ConstexprKind = ConstexprSpecKind::Constexpr; - if (Context.BuiltinInfo.isImmediate(ID)) - ConstexprKind = ConstexprSpecKind::Consteval; + if (getLangOpts().CPlusPlus && Context.BuiltinInfo.isImmediate(ID)) { + ConstexprKind = ConstexprSpecKind::Consteval; } FunctionDecl *New = FunctionDecl::Create( diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 1b558d70f9b48..53238d355ea09 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -676,19 +676,11 @@ bool Sema::MergeCXXFunctionDecl(FunctionDecl *New, FunctionDecl *Old, // template has a constexpr specifier then all its declarations shall // contain the constexpr specifier. if (New->getConstexprKind() != Old->getConstexprKind()) { - if (Old->getBuiltinID() && - Old->getConstexprKind() == ConstexprSpecKind::Constexpr && - New->getConstexprKind() == ConstexprSpecKind::Unspecified) { - // Except allow redeclaring a builtin as non-constexpr to match C - // redeclarations which will not be constexpr - New->setConstexprKind(ConstexprSpecKind::Constexpr); - } else { - Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch) - << New << static_cast<int>(New->getConstexprKind()) - << static_cast<int>(Old->getConstexprKind()); - Diag(Old->getLocation(), diag::note_previous_declaration); - Invalid = true; - } + Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch) + << New << static_cast<int>(New->getConstexprKind()) + << static_cast<int>(Old->getConstexprKind()); + Diag(Old->getLocation(), diag::note_previous_declaration); + Invalid = true; } else if (!Old->getMostRecentDecl()->isInlined() && New->isInlined() && Old->isDefined(Def) && // If a friend function is inlined but does not have 'inline' diff --git a/clang/test/Sema/builtin-redecl.cpp b/clang/test/Sema/builtin-redecl.cpp index 31409a4d46a65..323c63e202883 100644 --- a/clang/test/Sema/builtin-redecl.cpp +++ b/clang/test/Sema/builtin-redecl.cpp @@ -14,18 +14,13 @@ void __builtin_va_copy(double d); // expected-error@+2 {{cannot redeclare builtin function '__builtin_va_end'}} // expected-note@+1 {{'__builtin_va_end' is a builtin with type}} void __builtin_va_end(__builtin_va_list); +// RUN: %clang_cc1 %s -fsyntax-only -verify +// RUN: %clang_cc1 %s -fsyntax-only -verify -x c void __va_start(__builtin_va_list*, ...); - void *__builtin_assume_aligned(const void *, size_t, ...); #ifdef __cplusplus -constexpr void *__builtin_assume_aligned(const void *, size_t, ...); - void *__builtin_assume_aligned(const void *, size_t, ...) noexcept; -constexpr void *__builtin_assume_aligned(const void *, size_t, ...) noexcept; - void *__builtin_assume_aligned(const void *, size_t, ...) throw(); -constexpr void *__builtin_assume_aligned(const void *, size_t, ...) throw(); - -// expected-error@+1 {{constexpr declaration of '__builtin_calloc' follows non-constexpr declaration}} -constexpr void *__builtin_calloc(size_t, size_t); -// expected-note@-1 {{previous declaration is here}} +void *__builtin_assume_aligned(const void *, size_t, ...) noexcept; +#else +void *__builtin_assume_aligned(const void *, size_t, ...); #endif >From ec88839cb34bf2a5f57edd376e30d8c08740f855 Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Tue, 14 May 2024 18:57:09 +0100 Subject: [PATCH 4/8] Prevent consteval builtins from being available when consteval doesn't make sense (!CPlusPlus20) --- clang/include/clang/Basic/Builtins.def | 1 + clang/include/clang/Basic/BuiltinsBase.td | 2 +- clang/lib/Basic/Builtins.cpp | 3 +++ clang/lib/Sema/SemaDecl.cpp | 3 ++- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def index f356f881d5ef9..f3d7642573380 100644 --- a/clang/include/clang/Basic/Builtins.def +++ b/clang/include/clang/Basic/Builtins.def @@ -100,3 +100,4 @@ // M_0, ..., M_k as payload // z -> this is a function in (possibly-versioned) namespace std // E -> this function can be constant evaluated by Clang frontend +// G -> this is a C++20 consteval function diff --git a/clang/include/clang/Basic/BuiltinsBase.td b/clang/include/clang/Basic/BuiltinsBase.td index 1196b9e15c10d..58dee22fc0a45 100644 --- a/clang/include/clang/Basic/BuiltinsBase.td +++ b/clang/include/clang/Basic/BuiltinsBase.td @@ -70,7 +70,7 @@ class VScanfFormat<int I> : IndexedAttribute<"S", I>; // Builtin can be constant evaluated def Constexpr : Attribute<"E">; -// Builtin is immediate and must be constant evaluated. Implies Constexpr. +// Builtin is immediate and must be constant evaluated. Implies Constexpr, and will only be supported in C++20 mode. def Consteval : Attribute<"EG">; // Builtin kinds diff --git a/clang/lib/Basic/Builtins.cpp b/clang/lib/Basic/Builtins.cpp index b116abbe034f7..7116e27cd9546 100644 --- a/clang/lib/Basic/Builtins.cpp +++ b/clang/lib/Basic/Builtins.cpp @@ -119,6 +119,9 @@ static bool builtinIsSupported(const Builtin::Info &BuiltinInfo, /* CPlusPlus Unsupported */ if (!LangOpts.CPlusPlus && BuiltinInfo.Langs == CXX_LANG) return false; + /* consteval Unsupported */ + if (!LangOpts.CPlusPlus20 && strchr(BuiltinInfo.Attributes, 'G') != nullptr) + return false; return true; } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 7ca02b5bbf624..d651ee2f502b1 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2410,7 +2410,8 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type, } ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified; - if (getLangOpts().CPlusPlus && Context.BuiltinInfo.isImmediate(ID)) { + if (Context.BuiltinInfo.isImmediate(ID)) { + assert(getLangOpts().CPlusPlus20 && "consteval builtins should only be available in C++20 mode"); ConstexprKind = ConstexprSpecKind::Consteval; } >From b498e90f6bd72e2b9044aef9a4c049845a68bc0d Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Tue, 14 May 2024 19:06:46 +0100 Subject: [PATCH 5/8] clang-format --- clang/lib/Sema/SemaDecl.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 8554cb59729ff..c24ebc3ef4ebb 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2373,7 +2373,8 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type, ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified; if (Context.BuiltinInfo.isImmediate(ID)) { - assert(getLangOpts().CPlusPlus20 && "consteval builtins should only be available in C++20 mode"); + assert(getLangOpts().CPlusPlus20 && + "consteval builtins should only be available in C++20 mode"); ConstexprKind = ConstexprSpecKind::Consteval; } >From bfb918623032748df99ccf948e7648de416477d0 Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Mon, 20 May 2024 10:52:57 +0100 Subject: [PATCH 6/8] Add test specifically for consteval builtins --- clang/lib/CodeGen/CGBuiltin.cpp | 3 ++ clang/test/SemaCXX/consteval-builtin.cpp | 61 ++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 clang/test/SemaCXX/consteval-builtin.cpp diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index e251091c6ce3e..513c6b6385838 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -2540,6 +2540,9 @@ static RValue EmitHipStdParUnsupportedBuiltin(CodeGenFunction *CGF, RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, const CallExpr *E, ReturnValueSlot ReturnValue) { + assert(!getContext().BuiltinInfo.isImmediate(BuiltinID) && + "Should not codegen for consteval builtins"); + const FunctionDecl *FD = GD.getDecl()->getAsFunction(); // See if we can constant fold this builtin. If so, don't emit it at all. // TODO: Extend this handling to all builtin calls that we can constant-fold. diff --git a/clang/test/SemaCXX/consteval-builtin.cpp b/clang/test/SemaCXX/consteval-builtin.cpp new file mode 100644 index 0000000000000..c8f89fc7c892c --- /dev/null +++ b/clang/test/SemaCXX/consteval-builtin.cpp @@ -0,0 +1,61 @@ +// RUN: %clang_cc1 -std=c++23 -fsyntax-only -Wno-unused %s -verify=cxx20-cxx26 +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -Wno-unused %s -verify=cxx20,cxx20-cxx26 +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -Wno-unused %s -verify=precxx20,cxx11-cxx17 +// RUN: %clang_cc1 -std=c++14 -fsyntax-only -Wno-unused %s -verify=precxx20,cxx11-cxx17 +// RUN: %clang_cc1 -std=c++11 -fsyntax-only -Wno-unused %s -verify=precxx20,cxx11-cxx17 +// RUN: %clang_cc1 -std=c++03 -fsyntax-only -Wno-unused %s -verify=precxx20 +// RUN: %clang_cc1 -std=c++98 -fsyntax-only -Wno-unused %s -verify=precxx20 +// RUN: %clang_cc1 -x c -std=c23 -fsyntax-only -Wno-unused %s -verify=c + +#if __cplusplus < 201103L +#define static_assert __extension__ _Static_assert +#define CONSTEXPR11 +#else +#define CONSTEXPR11 constexpr +#endif + +static const int i1 = 0; +static_assert(__builtin_is_within_lifetime(&i1), ""); +// precxx20-error@-1 {{use of undeclared identifier '__builtin_is_within_lifetime'}} +// c-error@-2 {{use of undeclared identifier '__builtin_is_within_lifetime'}} + +#if !defined(__cplusplus) || __cplusplus >= 201102L +constexpr int i2 = 0; +static_assert(__builtin_is_within_lifetime(&i2), ""); +// cxx11-cxx17-error@-1 {{use of undeclared identifier '__builtin_is_within_lifetime'}} +// c-error@-2 {{use of undeclared identifier '__builtin_is_within_lifetime'}} +#endif + +#ifdef __cplusplus +template<typename T> +CONSTEXPR11 bool f1(T i) { // #f1 + return __builtin_is_within_lifetime(&i); // #f1-consteval-call +} + +bool(&fp1)(int) = f1<int>; +// cxx20-cxx26-error@-1 {{cannot take address of immediate function 'f1<int>' outside of an immediate invocation}} +// cxx20-cxx26-note@#f1 {{declared here}} +// cxx20-cxx26-note@#f1-consteval-call {{'f1<int>' is an immediate function because its body contains a call to a consteval function '__builtin_is_within_lifetime' and that call is not a constant expression}} +// precxx20-error@#f1-consteval-call {{use of undeclared identifier '__builtin_is_within_lifetime'}} +// precxx20-note@-5 {{in instantiation of function template specialization 'f1<int>' requested here}} +#else +void f1(int i) { + __builtin_is_within_lifetime(&i); +// c-error@-1 {{use of undeclared identifier '__builtin_is_within_lifetime'}} +} +#endif + +#if __cplusplus >= 202002L +constexpr void f2() { + int i = 0; + if consteval { // cxx20-warning {{consteval if}} + __builtin_is_within_lifetime(&i); + } +} +void(&fp2)() = f2; + +constexpr void f3() { + __builtin_is_within_lifetime(&i1); +} +void(&fp3)() = f3; +#endif >From 53ebf025473dbe48fa20a0f4fea443595b0cec3a Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Mon, 20 May 2024 11:59:36 +0100 Subject: [PATCH 7/8] Add more tests --- .../SemaCXX/builtin-is-within-lifetime.cpp | 93 ++++++++++++++----- 1 file changed, 68 insertions(+), 25 deletions(-) diff --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp index fcc6303e051e6..c6190aff84abd 100644 --- a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp +++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp @@ -1,4 +1,6 @@ -// RUN: %clang_cc1 -std=c++2c -verify %s -fcxx-exceptions -Wno-unused -triple=x86_64-linux-gnu +// RUN: %clang_cc1 -std=c++20 -Wno-unused %s -verify=expected,cxx20 +// RUN: %clang_cc1 -std=c++23 -Wno-unused %s -verify=expected +// RUN: %clang_cc1 -std=c++2c -Wno-unused %s -verify=expected inline void* operator new(__SIZE_TYPE__, void* p) noexcept { return p; } namespace std { @@ -11,28 +13,9 @@ struct allocator { constexpr T* allocate(__SIZE_TYPE__ n) { return static_cast<T*>(::operator new(n * sizeof(T))); } constexpr void deallocate(T* p, __SIZE_TYPE__) { ::operator delete(p); } }; +using nullptr_t = decltype(nullptr); } -constexpr void check_immediate() { - if consteval { - if (false) __builtin_is_within_lifetime(static_cast<int*>(nullptr)); - } -} -auto* not_escalated = &check_immediate; - -template<typename T> -constexpr bool check_immediate2() { // #check_immediate2 - T i{}; - if (false) - __builtin_is_within_lifetime(&i); // #check_immediate2-call - return true; -} -static_assert(check_immediate2<int>()); -auto* escalated = &check_immediate2<int>; -// expected-error@-1 {{cannot take address of immediate function 'check_immediate2<int>' outside of an immediate invocation}} -// expected-note@#check_immediate2 {{declared here}} -// expected-note@#check_immediate2-call {{'check_immediate2<int>' is an immediate function because its body contains a call to a consteval function '__builtin_is_within_lifetime' and that call is not a constant expression}} - consteval bool test_union(int& i, char& c) { if (__builtin_is_within_lifetime(&i) || __builtin_is_within_lifetime(&c)) return false; @@ -101,10 +84,70 @@ consteval bool test_dynamic() { } static_assert(test_dynamic()); +consteval bool test_automatic() { + int* p; + { + int x = 0; + p = &x; + if (!__builtin_is_within_lifetime(p)) + return false; + } + { + int x = 0; + if (__builtin_is_within_lifetime(p)) + return false; + } + if (__builtin_is_within_lifetime(p)) + return false; + { + int x[4]; + p = &x[2]; + if (!__builtin_is_within_lifetime(p)) + return false; + } + if (__builtin_is_within_lifetime(p)) + return false; + std::nullptr_t* q; + { + std::nullptr_t np = nullptr; + q = &np; + if (!__builtin_is_within_lifetime(q)) + return false; + } + if (__builtin_is_within_lifetime(q)) + return false; + return true; +} +static_assert(test_automatic()); + +consteval bool test_indeterminate() { + int x; + if (!__builtin_is_within_lifetime(&x)) + return false; + bool b = true; + unsigned char c = __builtin_bit_cast(unsigned char, b); + if (!__builtin_is_within_lifetime(&c)) + return false; + struct {} padding; + unsigned char y = __builtin_bit_cast(unsigned char, padding); + if (!__builtin_is_within_lifetime(&y)) + return false; + return true; +} +static_assert(test_indeterminate()); + +consteval bool test_volatile() { + int x; + if (!__builtin_is_within_lifetime(static_cast<volatile int*>(&x)) || !__builtin_is_within_lifetime(static_cast<volatile void*>(&x))) + return false; + volatile int y; + if (!__builtin_is_within_lifetime(const_cast<int*>(&y)) || !__builtin_is_within_lifetime(const_cast<void*>(static_cast<volatile void*>(&y)))) + return false; + return true; +} +static_assert(test_volatile()); + constexpr bool self = __builtin_is_within_lifetime(&self); -// expected-error@-1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}} -// expected-note@-2 {{initializer of 'self' is unknown}} -// expected-note@-3 {{declared here}} constexpr int external{}; static_assert(__builtin_is_within_lifetime(&external)); bool not_constexpr() { @@ -192,7 +235,7 @@ struct OptBool { constexpr OptBool(bool b) : b(b) { } constexpr auto has_value() const -> bool { - if consteval { + if consteval { // cxx20-warning {{consteval if}} return __builtin_is_within_lifetime(&b); // during constant evaluation, cannot read from c } else { return c != 2; // during runtime, must read from c >From 6805d86090ea84d4c5ed4f7ab72dca9f9ba77bc7 Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Mon, 20 May 2024 12:05:46 +0100 Subject: [PATCH 8/8] clang-format --- clang/lib/AST/ExprConstant.cpp | 128 ++++++++++++++++++-------------- clang/lib/Sema/SemaChecking.cpp | 13 ++-- 2 files changed, 81 insertions(+), 60 deletions(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index f3b31d87dde9b..ffe2f172a9a5a 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1507,7 +1507,8 @@ CallStackFrame::~CallStackFrame() { } static bool isRead(AccessKinds AK) { - return AK == AK_Read || AK == AK_ReadObjectRepresentation || AK == AK_IsWithinLifetime; + return AK == AK_Read || AK == AK_ReadObjectRepresentation || + AK == AK_IsWithinLifetime; } static bool isModification(AccessKinds AK) { @@ -1535,7 +1536,8 @@ static bool isAnyAccess(AccessKinds AK) { /// Is this an access per the C++ definition? static bool isFormalAccess(AccessKinds AK) { - return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy && AK != AK_IsWithinLifetime; + return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy && + AK != AK_IsWithinLifetime; } /// Is this kind of axcess valid on an indeterminate object value? @@ -3655,7 +3657,8 @@ struct CompleteObject { // In C++14 onwards, it is permitted to read a mutable member whose // lifetime began within the evaluation. // FIXME: Should we also allow this in C++11? - if (!Info.getLangOpts().CPlusPlus14 && AK != AccessKinds::AK_IsWithinLifetime) + if (!Info.getLangOpts().CPlusPlus14 && + AK != AccessKinds::AK_IsWithinLifetime) return false; return lifetimeStartedInEvaluation(Info, Base, /*MutableSubobject*/true); } @@ -4093,7 +4096,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, // started in the current evaluation. BaseVal = Info.EvaluatingDeclValue; if (AK == AccessKinds::AK_IsWithinLifetime) - return CompleteObject(); // Not within lifetime + return CompleteObject(); // Not within lifetime } else if (const ValueDecl *D = LVal.Base.dyn_cast<const ValueDecl *>()) { // Allow reading from a GUID declaration. if (auto *GD = dyn_cast<MSGuidDecl>(D)) { @@ -11508,7 +11511,8 @@ class IntExprEvaluator bool ZeroInitialization(const Expr *E) { return Success(0, E); } - friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &, const CallExpr *); + friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &, + const CallExpr *); //===--------------------------------------------------------------------===// // Visitor Methods @@ -17030,56 +17034,70 @@ bool Expr::tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const { } namespace { - struct IsWithinLifetimeHandler { - EvalInfo &Info; - static constexpr AccessKinds AccessKind = AccessKinds::AK_IsWithinLifetime; - using result_type = std::optional<bool>; - std::optional<bool> failed() { return std::nullopt; } - template<typename T> - std::optional<bool> found(T &Subobj, QualType SubobjType) { - return true; - } - }; +struct IsWithinLifetimeHandler { + EvalInfo &Info; + static constexpr AccessKinds AccessKind = AccessKinds::AK_IsWithinLifetime; + using result_type = std::optional<bool>; + std::optional<bool> failed() { return std::nullopt; } + template <typename T> + std::optional<bool> found(T &Subobj, QualType SubobjType) { + return true; + } +}; - std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, const CallExpr *E) { - EvalInfo& Info = IEE.Info; - //assert(Info.InConstantContext && "Call to consteval builtin not in constant context?"); - assert(E->getBuiltinCallee() == Builtin::BI__builtin_is_within_lifetime); - const Expr *Arg = E->getArg(0); - if (Arg->isValueDependent()) - return std::nullopt; - LValue Val; - if (!EvaluatePointer(Arg, Val, Info)) - return std::nullopt; +std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, + const CallExpr *E) { + EvalInfo &Info = IEE.Info; + // assert(Info.InConstantContext && "Call to consteval builtin not in constant + // context?"); + assert(E->getBuiltinCallee() == Builtin::BI__builtin_is_within_lifetime); + const Expr *Arg = E->getArg(0); + if (Arg->isValueDependent()) + return std::nullopt; + LValue Val; + if (!EvaluatePointer(Arg, Val, Info)) + return std::nullopt; - auto Error = [&](int Diag) { - const auto *Callee = Info.CurrentCall->getCallee(); - bool CalledFromStd = Callee && Callee->isInStdNamespace() && Callee->getIdentifier() && Callee->getIdentifier()->isStr("is_within_lifetime"); - Info.report(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin() : E->getExprLoc(), diag::err_invalid_is_within_lifetime) - << (CalledFromStd ? "std::is_within_lifetime" : "__builtin_is_within_lifetime") << Diag; - return std::nullopt; - }; - // C++2c [meta.const.eval]p4: - // During the evaluation of an expression E as a core constant expression, a call to this function is ill-formed unless p points to an object that is usable in constant expressions or whose complete object's lifetime began within E. - - // Make sure it points to an object - // nullptr does not point to an object - if (Val.isNullPointer() || Val.getLValueBase().isNull()) - return Error(0); - QualType T = Val.getLValueBase().getType(); - if (T->isFunctionType()) - return Error(1); - assert(T->isObjectType()); - // Hypothetical array element is not an object - if (Val.getLValueDesignator().isOnePastTheEnd()) - return Error(2); - assert(Val.getLValueDesignator().isValidSubobject() && "Unchecked case for valid subobject"); - // All other ill-formed values should have failed EvaluatePointer, so the object should be a pointer to an object - // that is usable in a constant expression or whose complete lifetime began within the expression - CompleteObject CO = findCompleteObject(Info, E, AccessKinds::AK_IsWithinLifetime, Val, T); - if (!CO) - return false; - IsWithinLifetimeHandler handler{ Info }; - return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler); - } + auto Error = [&](int Diag) { + const auto *Callee = Info.CurrentCall->getCallee(); + bool CalledFromStd = Callee && Callee->isInStdNamespace() && + Callee->getIdentifier() && + Callee->getIdentifier()->isStr("is_within_lifetime"); + Info.report(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin() + : E->getExprLoc(), + diag::err_invalid_is_within_lifetime) + << (CalledFromStd ? "std::is_within_lifetime" + : "__builtin_is_within_lifetime") + << Diag; + return std::nullopt; + }; + // C++2c [meta.const.eval]p4: + // During the evaluation of an expression E as a core constant expression, a + // call to this function is ill-formed unless p points to an object that is + // usable in constant expressions or whose complete object's lifetime began + // within E. + + // Make sure it points to an object + // nullptr does not point to an object + if (Val.isNullPointer() || Val.getLValueBase().isNull()) + return Error(0); + QualType T = Val.getLValueBase().getType(); + if (T->isFunctionType()) + return Error(1); + assert(T->isObjectType()); + // Hypothetical array element is not an object + if (Val.getLValueDesignator().isOnePastTheEnd()) + return Error(2); + assert(Val.getLValueDesignator().isValidSubobject() && + "Unchecked case for valid subobject"); + // All other ill-formed values should have failed EvaluatePointer, so the + // object should be a pointer to an object that is usable in a constant + // expression or whose complete lifetime began within the expression + CompleteObject CO = + findCompleteObject(Info, E, AccessKinds::AK_IsWithinLifetime, Val, T); + if (!CO) + return false; + IsWithinLifetimeHandler handler{Info}; + return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler); } +} // namespace diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 127175f9ceeba..df2dd9beb7528 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -2227,13 +2227,16 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) { TheCall->setArg(0, Arg.get()); TheCall->setType(S.Context.BoolTy); - // A call to this function is always ill-formed if the type is not a pointer to - // an object type. There is no Mandates: to that effect, so we can only + // A call to this function is always ill-formed if the type is not a pointer + // to an object type. There is no Mandates: to that effect, so we can only // issue an error if it is actually evaluated as part of a constant evaluation - // (e.g., `false ? true : std::is_within_lifetime(static_cast<void(*)()>(nullptr));` is fine) - // However, `std::is_within_lifetime` will only take pointer types (allow non-const qualified too) + // (e.g., `false ? true : + // std::is_within_lifetime(static_cast<void(*)()>(nullptr));` is fine) + // However, `std::is_within_lifetime` will only take pointer types (allow + // non-const qualified too) if (!ParamTy->isPointerType()) { - S.Diag(TheCall->getArg(0)->getExprLoc(), diag::err_builtin_is_within_lifetime_invalid_arg); + S.Diag(TheCall->getArg(0)->getExprLoc(), + diag::err_builtin_is_within_lifetime_invalid_arg); return ExprError(); } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits