[clang] 2a07509 - [Clang] Add __builtin_is_within_lifetime to implement P2641R4's std::is_within_lifetime (#91895)

via cfe-commits cfe-commits at lists.llvm.org
Thu Sep 5 05:43:04 PDT 2024


Author: Mital Ashok
Date: 2024-09-05T14:42:59+02:00
New Revision: 2a07509c8d3c8b5b2c88e4f73dde0071bf506870

URL: https://github.com/llvm/llvm-project/commit/2a07509c8d3c8b5b2c88e4f73dde0071bf506870
DIFF: https://github.com/llvm/llvm-project/commit/2a07509c8d3c8b5b2c88e4f73dde0071bf506870.diff

LOG: [Clang] Add __builtin_is_within_lifetime to implement P2641R4's std::is_within_lifetime (#91895)

[P2641R4](https://wg21.link/P2641R4)

This new builtin function is declared `consteval`. Support for
`-fexperimental-new-constant-interpreter` will be added in a later
patch.

---------

Co-authored-by: cor3ntin <corentinjabot at gmail.com>

Added: 
    clang/test/SemaCXX/builtin-is-within-lifetime.cpp
    clang/test/SemaCXX/consteval-builtin.cpp

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Basic/Builtins.td
    clang/include/clang/Basic/DiagnosticASTKinds.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/AST/ByteCode/State.h
    clang/lib/AST/ExprConstant.cpp
    clang/lib/CodeGen/CGBuiltin.cpp
    clang/lib/Sema/SemaChecking.cpp
    clang/lib/Sema/SemaExpr.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index dc103aceebc362..ab3c3e6049f602 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -122,6 +122,9 @@ C++2c Feature Support
 
 - Implemented `P2747R2 constexpr placement new <https://wg21.link/P2747R2>`_.
 
+- Added the ``__builtin_is_within_lifetime`` builtin, which supports
+  `P2641R4 Checking if a union alternative is active <https://wg21.link/p2641r4>`_
+
 C++23 Feature Support
 ^^^^^^^^^^^^^^^^^^^^^
 - Removed the restriction to literal types in constexpr functions in C++23 mode.

diff  --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 9e2a590f265ac8..92118418d9d459 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 91135b18c75716..21a307d1e89878 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -167,14 +167,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">;
@@ -407,6 +407,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 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 dcb49d8a67604a..72ea5338ce615f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12186,6 +12186,10 @@ 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<
+  "%select{non-|function }0pointer 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 2cffce4bc2ae40..3248e2d8be697f 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 205cbdf52a6f72..0ad3577d4e1026 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
@@ -11684,6 +11698,9 @@ class IntExprEvaluator
 
   bool ZeroInitialization(const Expr *E) { return Success(0, E); }
 
+  friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &,
+                                                             const CallExpr *);
+
   //===--------------------------------------------------------------------===//
   //                            Visitor Methods
   //===--------------------------------------------------------------------===//
@@ -12743,6 +12760,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:
@@ -17322,3 +17344,84 @@ 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();
+  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())
+    return Error(1);
+  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(2);
+
+  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 e826c1c6fbbd23..02d8726baa4210 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 b01765b6833a17..2aab52160afa79 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1844,6 +1844,44 @@ 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);
+
+  // 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()) {
+      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()) {
+      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)
+        << 0;
+    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 +2314,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 e291ef6c97eefc..32dac4440fb82a 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -17622,7 +17622,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..62ff2681952ce5
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
@@ -0,0 +1,431 @@
+// 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>;
+template<typename T>
+inline constexpr bool is_function_v = __is_function(T);
+#ifdef INLINE_NAMESPACE
+inline namespace __1 {
+#endif
+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
+}
+#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 at -1 {{static assertion expression is not an integral constant expression}}
+//   expected-note at -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 at -1 {{static assertion expression is not an integral constant expression}}
+//   expected-note at -2 {{in call to 'test_automatic(1)'}}
+static_assert(test_automatic(2));
+// expected-error at -1 {{static assertion expression is not an integral constant expression}}
+//   expected-note at -2 {{in call to 'test_automatic(2)'}}
+static_assert(test_automatic(3));
+// expected-error at -1 {{static assertion expression is not an integral constant expression}}
+//   expected-note at -2 {{in call to 'test_automatic(3)'}}
+static_assert(test_automatic(4));
+// expected-error at -1 {{static assertion expression is not an integral constant expression}}
+//   expected-note at -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 at -1 {{constexpr variable 'self' must be initialized by a constant expression}}
+//   expected-note at -2 {{'__builtin_is_within_lifetime' cannot be called with a pointer to an object whose lifetime has not yet begun}}
+// expected-error at -3 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+//   expected-note at -4 {{initializer of 'self' is not a constant expression}}
+//   expected-note at -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 at -1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+  //   expected-note at -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 at -1 {{non-pointer argument to '__builtin_is_within_lifetime' is not allowed}}
+  // expected-error at -2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}}
+  __builtin_is_within_lifetime();
+  // expected-error at -1 {{too few arguments to function call, expected 1, have 0}}
+  // expected-error at -2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}}
+  __builtin_is_within_lifetime(1, 2);
+  // expected-error at -1 {{too many arguments to function call, expected 1, have 2}}
+  // expected-error at -2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}}
+  __builtin_is_within_lifetime(&external, &external);
+  // expected-error at -1 {{too many arguments to function call, expected 1, have 2}}
+  // expected-error at -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 at -1 {{static assertion expression is not an integral constant expression}}
+//   expected-note at -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 at -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 at -3 {{in call to 'NSDMI()'}}
+// expected-error at -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 at -7 {{in call to 'NSDMI()'}}
+
+struct X3 {
+  consteval X3() {
+    __builtin_is_within_lifetime(this); // #X3-read
+  }
+} x3;
+// expected-error at -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 at -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 at -1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+  //   expected-note at -2 {{'__builtin_is_within_lifetime' cannot be called with a one-past-the-end pointer}}
+  __builtin_is_within_lifetime(arr + 2);
+  // expected-error at -1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+  //   expected-note at -2 {{'__builtin_is_within_lifetime' cannot be called with a one-past-the-end pointer}}
+}
+
+template<typename T>
+consteval void disallow_function_types(bool b, const T* p) {
+  if (b) {
+    __builtin_is_within_lifetime(p); // expected-error {{function pointer argument to '__builtin_is_within_lifetime' is not allowed}}
+  }
+}
+void g() {
+  disallow_function_types<void ()>(false, &f);
+  // expected-note at -1 {{in instantiation of function template specialization 'disallow_function_types<void ()>' requested here}}
+}
+
+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 at -1 {{static assertion expression is not an integral constant expression}}
+//   expected-note at -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 at -1 {{static assertion expression is not an integral constant expression}}
+//   expected-note at -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 at -1 {{call to consteval function 'std::is_within_lifetime<int>' is not a constant expression}}
+  //   expected-note at -2 {{'std::is_within_lifetime' cannot be called with a null pointer}}
+  //   expected-note at -3 {{in call to 'is_within_lifetime<int>(nullptr)'}}
+  std::is_within_lifetime<void()>(&test_std_error_message);
+  // expected-error at -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 at -1 {{call to consteval function 'std::is_within_lifetime<int>' is not a constant expression}}
+  //   expected-note at -2 {{'std::is_within_lifetime' cannot be called with a one-past-the-end pointer}}
+  //   expected-note at -3 {{in call to 'is_within_lifetime<int>(&arr[2])'}}
+}
+struct XStd {
+  consteval XStd() {
+    std::is_within_lifetime(this); // #XStd-read
+  }
+} xstd;
+// expected-error at -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 at -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 at -4 {{has the builtin}}
+// precxx20-error at -3 {{does not have the builtin}}
+// c-error at -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 at -4 {{has the constexpr builtin}}
+// precxx20-error at -3 {{does not have the constexpr builtin}}
+// c-error at -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 at -1 {{use of undeclared identifier '__builtin_is_within_lifetime'}}
+// c-error at -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 at -1 {{use of undeclared identifier '__builtin_is_within_lifetime'}}
+// c-error at -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 at -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 at -5 {{in instantiation of function template specialization 'f1<int>' requested here}}
+#else
+void f1(int i) {
+  __builtin_is_within_lifetime(&i);
+  // c-error at -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 at -1 {{builtin functions must be directly called}}
+  // cxx20-cxx26-error at -2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}}
+  __builtin_is_within_lifetime();
+  // cxx20-cxx26-error at -1 {{too few arguments to function call, expected 1, have 0}}
+  // cxx20-cxx26-error at -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 at -1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+  //   cxx20-cxx26-note at -2 {{read of non-constexpr variable 'not_constexpr' is not allowed in a constant expression}}
+  //   cxx20-cxx26-note at -4 {{declared here}}
+}
+#endif


        


More information about the cfe-commits mailing list