https://github.com/MitalAshok created https://github.com/llvm/llvm-project/pull/91895
None >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/2] [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/2] [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); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits