https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/91895
>From f602189e20d15d7805b40f60b9383c83117b7d04 Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Tue, 20 Aug 2024 13:23:52 +0100 Subject: [PATCH 1/4] [Clang] Add __builtin_is_within_lifetime to implement P2641R4's std::is_within_lifetime Squashed all previous commits (no longer in draft) --- clang/include/clang/Basic/Builtins.td | 6 + .../include/clang/Basic/DiagnosticASTKinds.td | 12 +- .../clang/Basic/DiagnosticSemaKinds.td | 3 + clang/lib/AST/ByteCode/State.h | 1 + clang/lib/AST/ExprConstant.cpp | 108 ++++- clang/lib/CodeGen/CGBuiltin.cpp | 3 + clang/lib/Sema/SemaChecking.cpp | 36 ++ clang/lib/Sema/SemaExpr.cpp | 3 +- .../SemaCXX/builtin-is-within-lifetime.cpp | 435 ++++++++++++++++++ clang/test/SemaCXX/consteval-builtin.cpp | 93 ++++ 10 files changed, 693 insertions(+), 7 deletions(-) create mode 100644 clang/test/SemaCXX/builtin-is-within-lifetime.cpp create mode 100644 clang/test/SemaCXX/consteval-builtin.cpp diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 036366cdadf4aa..e2cbf2ac69107d 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -934,6 +934,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 f317c5ac44f32b..581640b433568d 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -169,14 +169,14 @@ def note_constexpr_this : Note< def access_kind : TextSubstitution< "%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">; def access_kind_subobject : TextSubstitution< "%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">; def access_kind_volatile : TextSubstitution< "%select{read of|read of|assignment to|increment of|decrement of|" - "<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>}0">; + "<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>}0">; def note_constexpr_lifetime_ended : Note< "%sub{access_kind}0 %select{temporary|variable}1 whose " "%plural{8:storage duration|:lifetime}0 has ended">; @@ -408,6 +408,12 @@ 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 : Note< + "'%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 yet begun}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 4b6aadd635786a..e50ea9bf20763c 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -12190,6 +12190,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/ByteCode/State.h b/clang/lib/AST/ByteCode/State.h index 44d6c037c5ad95..c163fd29222252 100644 --- a/clang/lib/AST/ByteCode/State.h +++ b/clang/lib/AST/ByteCode/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/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 5540f58b526705..9f12fcf9ad7dce 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1522,7 +1522,8 @@ 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) { @@ -1532,6 +1533,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: @@ -1549,7 +1551,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; + return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy && + AK != AK_IsWithinLifetime; } /// Is this kind of axcess valid on an indeterminate object value? @@ -1561,6 +1564,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: @@ -3707,7 +3711,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) + if (!Info.getLangOpts().CPlusPlus14 && + AK != AccessKinds::AK_IsWithinLifetime) return false; return lifetimeStartedInEvaluation(Info, Base, /*MutableSubobject*/true); } @@ -3760,6 +3765,12 @@ 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. + // If I is non-zero, some subobject (member or array element) of a + // complete object has ended its lifetime, so this is valid for + // IsWithinLifetime, resulting in false. + if (I != 0 && handler.AccessKind == AK_IsWithinLifetime) + return false; if (!Info.checkingPotentialConstantExpression()) Info.FFDiag(E, diag::note_constexpr_access_uninit) << handler.AccessKind << O->isIndeterminate() @@ -3927,6 +3938,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 @@ -11667,6 +11681,9 @@ class IntExprEvaluator bool ZeroInitialization(const Expr *E) { return Success(0, E); } + friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &, + const CallExpr *); + //===--------------------------------------------------------------------===// // Visitor Methods //===--------------------------------------------------------------------===// @@ -12722,6 +12739,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: @@ -17310,3 +17332,83 @@ 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; + // Sometimes this is called during some sorts of constant folding / early + // evaluation. These are meant for non-constant expressions and are not + // necessary since this consteval builtin will never be evaluated at runtime. + // Just fail to evaluate when not in a constant context. + if (!Info.InConstantContext) + return std::nullopt; + 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) { + bool CalledFromStd = false; + const auto *Callee = Info.CurrentCall->getCallee(); + if (Callee && Callee->isInStdNamespace()) { + const IdentifierInfo *Identifier = Callee->getIdentifier(); + CalledFromStd = Identifier && Identifier->isStr("is_within_lifetime"); + } + Info.CCEDiag(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); + // The lifetime hasn't begun yet if we are still evaluating the + // initializer ([basic.life]p(1.2)) + if (Info.EvaluatingDeclValue && CO.Value == Info.EvaluatingDeclValue) + return Error(3); + + if (!CO) + return false; + IsWithinLifetimeHandler handler{Info}; + return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler); +} +} // namespace diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index f424ddaa175400..a69d98b6794a95 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -2538,6 +2538,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/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index ee143381cf4f79..1d2fa342429e2c 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1844,6 +1844,40 @@ static ExprResult BuiltinLaunder(Sema &S, CallExpr *TheCall) { return TheCall; } +static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) { + if (S.checkArgCount(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<void()>(nullptr);` is fine) + // However, `std::is_within_lifetime` will only take pointer types (allow + // non-const qualified too) + if (const auto *PT = ParamTy->getAs<PointerType>()) { + // Disallow VLAs too since those shouldn't be able to + // be a template parameter for `std::is_within_lifetime` + if (PT->getPointeeType()->isVariableArrayType()) { + S.Diag(TheCall->getArg(0)->getExprLoc(), diag::err_vla_unsupported) + << 1 << "__builtin_is_within_lifetime"; + return ExprError(); + } + } else { + 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( @@ -2276,6 +2310,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/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index f495658fe58641..207d6524156aa2 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -17577,7 +17577,8 @@ HandleImmediateInvocations(Sema &SemaRef, (SemaRef.inTemplateInstantiation() && !ImmediateEscalating)) { SemaRef.Diag(DR->getBeginLoc(), diag::err_invalid_consteval_take_address) << ND << isa<CXXRecordDecl>(ND) << FD->isConsteval(); - SemaRef.Diag(ND->getLocation(), diag::note_declared_at); + if (!FD->getBuiltinID()) + SemaRef.Diag(ND->getLocation(), diag::note_declared_at); if (auto Context = SemaRef.InnermostDeclarationWithDelayedImmediateInvocations()) { SemaRef.Diag(Context->Loc, diag::note_invalid_consteval_initializer) 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 00000000000000..049a84bc13eac8 --- /dev/null +++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp @@ -0,0 +1,435 @@ +// RUN: %clang_cc1 -std=c++20 -Wno-unused %s -verify=expected,cxx20 -Wno-vla-cxx-extension +// RUN: %clang_cc1 -std=c++23 -Wno-unused %s -verify=expected,sincecxx23 -Wno-vla-cxx-extension +// RUN: %clang_cc1 -std=c++26 -Wno-unused %s -verify=expected,sincecxx23 -Wno-vla-cxx-extension +// RUN: %clang_cc1 -std=c++26 -DINLINE_NAMESPACE -Wno-unused %s -verify=expected,sincecxx23 -Wno-vla-cxx-extension + +inline constexpr 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); } +}; +using nullptr_t = decltype(nullptr); +template<typename T, T v> +struct integral_constant { static constexpr T value = v; }; +template<bool v> +using bool_constant = integral_constant<bool, v>; +using true_type = bool_constant<true>; +using false_type = bool_constant<false>; +#ifdef INLINE_NAMESPACE +inline namespace __1 { +#endif +template<typename T> +consteval bool is_within_lifetime(const T* p) noexcept { + return __builtin_is_within_lifetime(p); +} +#ifdef INLINE_NAMESPACE +} +#endif +} + +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(bool read_after_deallocate) { + std::allocator<int> a; + int* p = a.allocate(1); + // a.allocate starts the lifetime of an array, + // the complete object of *p has started its lifetime + 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 (read_after_deallocate) + __builtin_is_within_lifetime(p); // expected-note {{read of heap allocated object that has been deleted}} + return true; +} +static_assert(test_dynamic(false)); +static_assert(test_dynamic(true)); +// expected-error@-1 {{static assertion expression is not an integral constant expression}} +// expected-note@-2 {{in call to 'test_dynamic(true)'}} + +consteval bool test_automatic(int read_dangling) { + int* p; + { + int x = 0; + p = &x; + if (!__builtin_is_within_lifetime(p)) + return false; + } + { + int x = 0; + if (read_dangling == 1) + __builtin_is_within_lifetime(p); // expected-note {{read of object outside its lifetime is not allowed in a constant expression}} + } + if (read_dangling == 2) + __builtin_is_within_lifetime(p); // expected-note {{read of object outside its lifetime is not allowed in a constant expression}} + { + int x[4]; + p = &x[2]; + if (!__builtin_is_within_lifetime(p)) + return false; + } + if (read_dangling == 3) + __builtin_is_within_lifetime(p); // expected-note {{read of object outside its lifetime is not allowed in a constant expression}} + std::nullptr_t* q; + { + std::nullptr_t np = nullptr; + q = &np; + if (!__builtin_is_within_lifetime(q)) + return false; + } + if (read_dangling == 4) + __builtin_is_within_lifetime(q); // expected-note {{read of object outside its lifetime is not allowed in a constant expression}} + return true; +} +static_assert(test_automatic(0)); +static_assert(test_automatic(1)); +// expected-error@-1 {{static assertion expression is not an integral constant expression}} +// expected-note@-2 {{in call to 'test_automatic(1)'}} +static_assert(test_automatic(2)); +// expected-error@-1 {{static assertion expression is not an integral constant expression}} +// expected-note@-2 {{in call to 'test_automatic(2)'}} +static_assert(test_automatic(3)); +// expected-error@-1 {{static assertion expression is not an integral constant expression}} +// expected-note@-2 {{in call to 'test_automatic(3)'}} +static_assert(test_automatic(4)); +// expected-error@-1 {{static assertion expression is not an integral constant expression}} +// expected-note@-2 {{in call to 'test_automatic(4)'}} + + +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 {{constexpr variable 'self' must be initialized by a constant expression}} +// expected-note@-2 {{'__builtin_is_within_lifetime' cannot be called with a pointer to an object whose lifetime has not yet begun}} +// expected-error@-3 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}} +// expected-note@-4 {{initializer of 'self' is not a constant expression}} +// expected-note@-5 {{declared here}} +constexpr int external{}; +static_assert(__builtin_is_within_lifetime(&external)); +void not_constexpr() { + __builtin_is_within_lifetime(&external); +} +void invalid_args() { + __builtin_is_within_lifetime(static_cast<int*>(nullptr)); + // expected-error@-1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}} + // expected-note@-2 {{'__builtin_is_within_lifetime' cannot be called with a null pointer}} + + // FIXME: avoid function to pointer conversion on all consteval builtins + __builtin_is_within_lifetime(0); + // expected-error@-1 {{non-pointer argument to '__builtin_is_within_lifetime' is not allowed}} + // expected-error@-2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}} + __builtin_is_within_lifetime(); + // expected-error@-1 {{too few arguments to function call, expected 1, have 0}} + // expected-error@-2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}} + __builtin_is_within_lifetime(1, 2); + // expected-error@-1 {{too many arguments to function call, expected 1, have 2}} + // expected-error@-2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}} + __builtin_is_within_lifetime(&external, &external); + // expected-error@-1 {{too many arguments to function call, expected 1, have 2}} + // expected-error@-2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}} +} + +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 NSDMI { // #NSDMI + bool a = true; + bool b = __builtin_is_within_lifetime(&a); // #NSDMI-read +} x2; +// expected-error@-1 {{constexpr variable 'x2' must be initialized by a constant expression}} +// expected-note@#NSDMI-read {{'__builtin_is_within_lifetime' cannot be called with a pointer to an object whose lifetime has not yet begun}} +// expected-note@-3 {{in call to 'NSDMI()'}} +// expected-error@-4 {{call to immediate function 'NSDMI::NSDMI' is not a constant expression}} +// expected-note@#NSDMI {{'NSDMI' is an immediate constructor because the default initializer of 'b' contains a call to a consteval function '__builtin_is_within_lifetime' and that call is not a constant expression}} +// expected-note@#NSDMI-read {{'__builtin_is_within_lifetime' cannot be called with a pointer to an object whose lifetime has not yet begun}} +// expected-note@-7 {{in call to 'NSDMI()'}} + +struct X3 { + consteval X3() { + __builtin_is_within_lifetime(this); // #X3-read + } +} x3; +// expected-error@-1 {{call to consteval function 'X3::X3' is not a constant expression}} +// expected-note@#X3-read {{'__builtin_is_within_lifetime' cannot be called with a pointer to an object whose lifetime has not yet begun}} +// expected-note@-3 {{in call to 'X3()'}} + +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-note@-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-note@-2 {{'__builtin_is_within_lifetime' cannot be called with a one-past-the-end pointer}} +} + +// FIXME: Pending LWG4138 +template<typename T> +consteval bool allow_bad_types_unless_used(bool b, const 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, &f)); +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-note@#bad_type_used {{'__builtin_is_within_lifetime' cannot be called with a function pointer}} + // expected-note@-3 {{in call to 'allow_bad_types_unless_used<void ()>(true, &fn)'}} +} + +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 { // 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 + } + } + + 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); + +namespace vlas { + +consteval bool f(int n) { + int vla[n]; // cxx20-error {{variable of non-literal type}} + return __builtin_is_within_lifetime(static_cast<void*>(&vla)); +} +static_assert(f(1)); + +consteval bool fail(int n) { + int vla[n]; // cxx20-error {{variable of non-literal type}} + return __builtin_is_within_lifetime(&vla); // expected-error {{variable length arrays are not supported in '__builtin_is_within_lifetime'}} +} +static_assert(fail(1)); // sincecxx23-error {{static assertion expression is not an integral constant expression}} + +consteval bool variably_modified(int n) { + int(* p)[n]; + return __builtin_is_within_lifetime(&p); +} +static_assert(variably_modified(1)); + +} // namespace vlas + +consteval bool partial_arrays() { + int arr[2]; + if (!__builtin_is_within_lifetime(&arr) || !__builtin_is_within_lifetime(&arr[0]) || !__builtin_is_within_lifetime(&arr[1])) + return false; + std::destroy_at(&arr[0]); + if (!__builtin_is_within_lifetime(&arr) || __builtin_is_within_lifetime(&arr[0]) || !__builtin_is_within_lifetime(&arr[1])) + return false; + std::construct_at(&arr[0]); + if (!__builtin_is_within_lifetime(&arr) || !__builtin_is_within_lifetime(&arr[0]) || !__builtin_is_within_lifetime(&arr[1])) + return false; + return true; +} +static_assert(partial_arrays()); + +consteval bool partial_members() { + struct S { + int x; + int y; + } s; + if (!__builtin_is_within_lifetime(&s) || !__builtin_is_within_lifetime(&s.x) || !__builtin_is_within_lifetime(&s.y)) + return false; + std::destroy_at(&s.x); + if (!__builtin_is_within_lifetime(&s) || __builtin_is_within_lifetime(&s.x) || !__builtin_is_within_lifetime(&s.y)) + return false; + std::construct_at(&s.x); + if (!__builtin_is_within_lifetime(&s) || !__builtin_is_within_lifetime(&s.x) || !__builtin_is_within_lifetime(&s.y)) + return false; + return true; +} + +struct NonTrivial { + constexpr NonTrivial() {} + constexpr NonTrivial(const NonTrivial&) {} + constexpr ~NonTrivial() {} +}; + +template<typename T> +constexpr T& unmove(T&& temp) { return static_cast<T&>(temp); } + +consteval bool test_temporaries() { + static_assert(__builtin_is_within_lifetime(&unmove(0))); + static_assert(__builtin_is_within_lifetime(&unmove(NonTrivial{}))); + if (!__builtin_is_within_lifetime(&unmove(0))) + return false; + if (!__builtin_is_within_lifetime(&unmove(NonTrivial{}))) + return false; + return true; +} +static_assert(test_temporaries()); + +constexpr const int& temp = 0; +static_assert(__builtin_is_within_lifetime(&temp)); + +template<typename T> +constexpr T* test_dangling() { + T i; // expected-note 2 {{declared here}} + return &i; // expected-warning 2 {{address of stack memory associated with local variable 'i' returned}} +} +static_assert(__builtin_is_within_lifetime(test_dangling<int>())); // expected-note {{in instantiation of function template specialization}} +// expected-error@-1 {{static assertion expression is not an integral constant expression}} +// expected-note@-2 {{read of variable whose lifetime has ended}} +static_assert(__builtin_is_within_lifetime(test_dangling<int[1]>())); // expected-note {{in instantiation of function template specialization}} +// expected-error@-1 {{static assertion expression is not an integral constant expression}} +// expected-note@-2 {{read of variable whose lifetime has ended}} + +template<auto F> +concept CanCallAndPassToIsWithinLifetime = std::bool_constant<__builtin_is_within_lifetime(F())>::value; +static_assert(CanCallAndPassToIsWithinLifetime<[]{ return &i; }>); +static_assert(!CanCallAndPassToIsWithinLifetime<[]{ return static_cast<int*>(nullptr); }>); +static_assert(!CanCallAndPassToIsWithinLifetime<[]{ return static_cast<void(*)()>(&f); }>); +template<auto F> constexpr std::true_type sfinae() requires CanCallAndPassToIsWithinLifetime<F> { return {}; } +template<auto F> std::false_type sfinae() { return {}; } +static_assert(decltype(sfinae<[]{ return &i; }>())::value); +static_assert(!decltype(sfinae<[]{ return static_cast<int*>(nullptr); }>())::value); +std::true_type(* not_immediate)() = &sfinae<[]{ return &i; }>; + +void test_std_error_message() { + std::is_within_lifetime(static_cast<int*>(nullptr)); + // expected-error@-1 {{call to consteval function 'std::is_within_lifetime<int>' is not a constant expression}} + // expected-note@-2 {{'std::is_within_lifetime' cannot be called with a null pointer}} + // expected-note@-3 {{in call to 'is_within_lifetime<int>(nullptr)'}} + std::is_within_lifetime<void()>(&test_std_error_message); + // expected-error@-1 {{call to consteval function 'std::is_within_lifetime<void ()>' is not a constant expression}} + // expected-note@-2 {{'std::is_within_lifetime' cannot be called with a function pointer}} + // expected-note@-3 {{in call to 'is_within_lifetime<void ()>(&test_std_error_message)'}} + std::is_within_lifetime(arr + 2); + // expected-error@-1 {{call to consteval function 'std::is_within_lifetime<int>' is not a constant expression}} + // expected-note@-2 {{'std::is_within_lifetime' cannot be called with a one-past-the-end pointer}} + // expected-note@-3 {{in call to 'is_within_lifetime<int>(&arr[2])'}} +} +struct XStd { + consteval XStd() { + std::is_within_lifetime(this); // #XStd-read + } +} xstd; +// expected-error@-1 {{call to consteval function 'XStd::XStd' is not a constant expression}} +// expected-note@#XStd-read {{'std::is_within_lifetime' cannot be called with a pointer to an object whose lifetime has not yet begun}} +// expected-note@#XStd-read {{in call to 'is_within_lifetime<XStd>(&)'}} +// expected-note@-4 {{in call to 'XStd()'}} diff --git a/clang/test/SemaCXX/consteval-builtin.cpp b/clang/test/SemaCXX/consteval-builtin.cpp new file mode 100644 index 00000000000000..3ba95b4dbd9b50 --- /dev/null +++ b/clang/test/SemaCXX/consteval-builtin.cpp @@ -0,0 +1,93 @@ +// 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 __has_builtin(__builtin_is_within_lifetime) +#error has the builtin +#else +#error does not have the builtin +#endif +// cxx20-cxx26-error@-4 {{has the builtin}} +// precxx20-error@-3 {{does not have the builtin}} +// c-error@-4 {{does not have the builtin}} + +#if __has_constexpr_builtin(__builtin_is_within_lifetime) +#error has the constexpr builtin +#else +#error does not have the constexpr builtin +#endif +// cxx20-cxx26-error@-4 {{has the constexpr builtin}} +// precxx20-error@-3 {{does not have the constexpr builtin}} +// c-error@-4 {{does not have the constexpr builtin}} + +#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; + +constexpr void f4() { + &__builtin_is_within_lifetime; + // cxx20-cxx26-error@-1 {{builtin functions must be directly called}} + // cxx20-cxx26-error@-2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}} + __builtin_is_within_lifetime(); + // cxx20-cxx26-error@-1 {{too few arguments to function call, expected 1, have 0}} + // cxx20-cxx26-error@-2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}} + int* not_constexpr; + __builtin_is_within_lifetime(not_constexpr); + // cxx20-cxx26-error@-1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}} + // cxx20-cxx26-note@-2 {{read of non-constexpr variable 'not_constexpr' is not allowed in a constant expression}} + // cxx20-cxx26-note@-4 {{declared here}} +} +#endif >From 05a2514cdffd34e26a39792609a740be0fd39f04 Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Tue, 20 Aug 2024 13:29:19 +0100 Subject: [PATCH 2/4] Add release note --- clang/docs/ReleaseNotes.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 249249971dec7c..131f31f45ea28b 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -131,6 +131,9 @@ C++2c Feature Support - Implemented `P2893R3 Variadic Friends <https://wg21.link/P2893>`_ +- Added the ``__builtin_is_within_lifetime`` builtin, which supports + `P2641R4 Checking if a union alternative is active <https://wg21.link/p2641r4>`_ + Resolutions to C++ Defect Reports ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >From 09a175bb02a79e72f01f9b813ff47fab7c2d40d6 Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Tue, 20 Aug 2024 14:24:09 +0100 Subject: [PATCH 3/4] LWG4138: Disallow function pointers --- .../include/clang/Basic/DiagnosticASTKinds.td | 2 +- .../clang/Basic/DiagnosticSemaKinds.td | 3 ++- clang/lib/AST/ExprConstant.cpp | 8 +++--- clang/lib/Sema/SemaChecking.cpp | 18 ++++++++----- .../SemaCXX/builtin-is-within-lifetime.cpp | 26 ++++++++----------- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 581640b433568d..3902afdc3977e7 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -410,7 +410,7 @@ def warn_is_constant_evaluated_always_true_constexpr : Warning< def err_invalid_is_within_lifetime : Note< "'%0' cannot be called with " - "%select{a null pointer|a function pointer|a one-past-the-end pointer|" + "%select{a null pointer|a one-past-the-end pointer|" "a pointer to an object whose lifetime has not yet begun}1" >; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index e50ea9bf20763c..3309b7135f6470 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -12191,7 +12191,8 @@ def err_builtin_launder_invalid_arg : Error< "'__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">; + "%select{non-|function }0pointer argument to '__builtin_is_within_lifetime' " + "is not allowed">; def err_builtin_invalid_arg_type: Error < "%ordinal0 argument must be " diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 9f12fcf9ad7dce..d037be4ab26343 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -17388,12 +17388,12 @@ std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, if (Val.isNullPointer() || Val.getLValueBase().isNull()) return Error(0); QualType T = Val.getLValueBase().getType(); - if (T->isFunctionType()) - return Error(1); + assert(!T->isFunctionType() && "Pointers to functions should have been typed " + "as function pointers which are rejected"); assert(T->isObjectType()); // Hypothetical array element is not an object if (Val.getLValueDesignator().isOnePastTheEnd()) - return Error(2); + return Error(1); assert(Val.getLValueDesignator().isValidSubobject() && "Unchecked case for valid subobject"); // All other ill-formed values should have failed EvaluatePointer, so the @@ -17404,7 +17404,7 @@ std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, // The lifetime hasn't begun yet if we are still evaluating the // initializer ([basic.life]p(1.2)) if (Info.EvaluatingDeclValue && CO.Value == Info.EvaluatingDeclValue) - return Error(3); + return Error(2); if (!CO) return false; diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 1d2fa342429e2c..717fcec4cf88bc 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1855,13 +1855,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 - // issue an error if it is actually evaluated as part of a constant evaluation - // (e.g., `false ? true : std::is_within_lifetime<void()>(nullptr);` is fine) - // However, `std::is_within_lifetime` will only take pointer types (allow - // non-const qualified too) + // This function should only be called through `std::is_within_lifetime`, + // which requires a pointer type if (const auto *PT = ParamTy->getAs<PointerType>()) { + // LWG4138: Function pointer types not allowed + if (PT->getPointeeType()->isFunctionType()) { + S.Diag(TheCall->getArg(0)->getExprLoc(), + diag::err_builtin_is_within_lifetime_invalid_arg) + << 1; + return ExprError(); + } // Disallow VLAs too since those shouldn't be able to // be a template parameter for `std::is_within_lifetime` if (PT->getPointeeType()->isVariableArrayType()) { @@ -1871,7 +1874,8 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) { } } else { S.Diag(TheCall->getArg(0)->getExprLoc(), - diag::err_builtin_is_within_lifetime_invalid_arg); + diag::err_builtin_is_within_lifetime_invalid_arg) + << 0; return ExprError(); } diff --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp index 049a84bc13eac8..62ff2681952ce5 100644 --- a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp +++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp @@ -21,11 +21,13 @@ template<bool v> using bool_constant = integral_constant<bool, v>; using true_type = bool_constant<true>; using false_type = bool_constant<false>; +template<typename T> +inline constexpr bool is_function_v = __is_function(T); #ifdef INLINE_NAMESPACE inline namespace __1 { #endif -template<typename T> -consteval bool is_within_lifetime(const T* p) noexcept { +template<typename T> requires (!is_function_v<T>) // #std-constraint +consteval bool is_within_lifetime(const T* p) noexcept { // #std-definition return __builtin_is_within_lifetime(p); } #ifdef INLINE_NAMESPACE @@ -266,21 +268,15 @@ void f() { // expected-note@-2 {{'__builtin_is_within_lifetime' cannot be called with a one-past-the-end pointer}} } -// FIXME: Pending LWG4138 template<typename T> -consteval bool allow_bad_types_unless_used(bool b, const T* p) { +consteval void disallow_function_types(bool b, const T* p) { if (b) { - __builtin_is_within_lifetime(p); // #bad_type_used + __builtin_is_within_lifetime(p); // expected-error {{function pointer argument to '__builtin_is_within_lifetime' is not allowed}} } - return true; } -void fn(); -static_assert(allow_bad_types_unless_used<void()>(false, &f)); 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-note@#bad_type_used {{'__builtin_is_within_lifetime' cannot be called with a function pointer}} - // expected-note@-3 {{in call to 'allow_bad_types_unless_used<void ()>(true, &fn)'}} + disallow_function_types<void ()>(false, &f); + // expected-note@-1 {{in instantiation of function template specialization 'disallow_function_types<void ()>' requested here}} } struct OptBool { @@ -416,9 +412,9 @@ void test_std_error_message() { // expected-note@-2 {{'std::is_within_lifetime' cannot be called with a null pointer}} // expected-note@-3 {{in call to 'is_within_lifetime<int>(nullptr)'}} std::is_within_lifetime<void()>(&test_std_error_message); - // expected-error@-1 {{call to consteval function 'std::is_within_lifetime<void ()>' is not a constant expression}} - // expected-note@-2 {{'std::is_within_lifetime' cannot be called with a function pointer}} - // expected-note@-3 {{in call to 'is_within_lifetime<void ()>(&test_std_error_message)'}} + // expected-error@-1 {{no matching function for call to 'is_within_lifetime'}} + // expected-note@#std-definition {{candidate template ignored: constraints not satisfied [with T = void ()]}} + // expected-note@#std-constraint {{because '!is_function_v<void ()>' evaluated to false}} std::is_within_lifetime(arr + 2); // expected-error@-1 {{call to consteval function 'std::is_within_lifetime<int>' is not a constant expression}} // expected-note@-2 {{'std::is_within_lifetime' cannot be called with a one-past-the-end pointer}} >From 70fc7075f820ab001a221a60c3574c0bf91cafdc Mon Sep 17 00:00:00 2001 From: Mital Ashok <mi...@mitalashok.co.uk> Date: Tue, 20 Aug 2024 14:49:54 +0100 Subject: [PATCH 4/4] Reword comments --- clang/lib/AST/ExprConstant.cpp | 5 +++-- clang/lib/Sema/SemaChecking.cpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index d037be4ab26343..0a425040b89ff2 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -17388,8 +17388,9 @@ std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, if (Val.isNullPointer() || Val.getLValueBase().isNull()) return Error(0); QualType T = Val.getLValueBase().getType(); - assert(!T->isFunctionType() && "Pointers to functions should have been typed " - "as function pointers which are rejected"); + assert(!T->isFunctionType() && + "Pointers to functions should have been typed as function pointers " + "which would have been rejected earlier"); assert(T->isObjectType()); // Hypothetical array element is not an object if (Val.getLValueDesignator().isOnePastTheEnd()) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 717fcec4cf88bc..7ffbf26e66cd22 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1855,8 +1855,8 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) { TheCall->setArg(0, Arg.get()); TheCall->setType(S.Context.BoolTy); - // This function should only be called through `std::is_within_lifetime`, - // which requires a pointer type + // Only accept pointers to objects as arguments, which should have object + // pointer or void pointer types. if (const auto *PT = ParamTy->getAs<PointerType>()) { // LWG4138: Function pointer types not allowed if (PT->getPointeeType()->isFunctionType()) { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits