[clang] [Clang] Add __builtin_is_within_lifetime to implement P2641R4's std::is_within_lifetime (PR #91895)
Mital Ashok via cfe-commits
cfe-commits at lists.llvm.org
Sun May 12 11:58:56 PDT 2024
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 <mital at 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 at +2 {{cannot redeclare builtin function '__builtin_va_end'}}
// expected-note at +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 at +1 {{constexpr declaration of '__builtin_calloc' follows non-constexpr declaration}}
+constexpr void *__builtin_calloc(size_t, size_t);
+// expected-note at -1 {{previous declaration is here}}
#endif
>From 253f58f0e34bc6dedbbfe17f68cfe9baa3a9146f Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at 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 at -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 at -1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+// expected-note at -2 {{initializer of 'self' is unknown}}
+// expected-note at -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 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 {
+ 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 at -1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+// expected-error 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-error at -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 at -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);
More information about the cfe-commits
mailing list