[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
Mon Aug 12 13:48:05 PDT 2024
https://github.com/MitalAshok updated https://github.com/llvm/llvm-project/pull/91895
>From 56aed689dc5825fc5bacc6dfdff58ee0eaf71f82 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Sun, 12 May 2024 19:48:24 +0100
Subject: [PATCH 01/11] [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 f955d21169556a..e85ec5b2dca14e 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 724747ec76d732..1196b9e15c10dc 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 fb913034bd8360..6b0a04585928a5 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 53238d355ea091..1b558d70f9b487 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 bb4b116fd73ca6..39aa32526d2b1c 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 323c63e2028833..31409a4d46a65b 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 02/11] [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 11982af3fa609b..04660122565b86 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 a024f9b2a9f8c0..7f8908e45663b9 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 9e82130c936096..3f60b865449539 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 f1aa19e4409e15..f3b31d87dde9b9 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 f1e8e3618f34fe..65039a67c7de49 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 54789dde506919..1ca1d34c58b8aa 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 00000000000000..fcc6303e051e6d
--- /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);
>From 88c68cdffe8bf94d8ecca651d6162e8b601cdc38 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Tue, 14 May 2024 18:29:48 +0100
Subject: [PATCH 03/11] No longer declare constexpr builtins as constexpr
---
clang/lib/Sema/SemaDecl.cpp | 6 ++----
clang/lib/Sema/SemaDeclCXX.cpp | 18 +++++-------------
clang/test/Sema/builtin-redecl.cpp | 15 +++++----------
3 files changed, 12 insertions(+), 27 deletions(-)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 6b0a04585928a5..7ca02b5bbf6249 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2410,10 +2410,8 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type,
}
ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified;
- if (getLangOpts().CPlusPlus && Context.BuiltinInfo.isConstantEvaluated(ID)) {
- ConstexprKind = ConstexprSpecKind::Constexpr;
- if (Context.BuiltinInfo.isImmediate(ID))
- ConstexprKind = ConstexprSpecKind::Consteval;
+ if (getLangOpts().CPlusPlus && Context.BuiltinInfo.isImmediate(ID)) {
+ ConstexprKind = ConstexprSpecKind::Consteval;
}
FunctionDecl *New = FunctionDecl::Create(
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 1b558d70f9b487..53238d355ea091 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -676,19 +676,11 @@ bool Sema::MergeCXXFunctionDecl(FunctionDecl *New, FunctionDecl *Old,
// template has a constexpr specifier then all its declarations shall
// contain the constexpr specifier.
if (New->getConstexprKind() != Old->getConstexprKind()) {
- if (Old->getBuiltinID() &&
- Old->getConstexprKind() == ConstexprSpecKind::Constexpr &&
- New->getConstexprKind() == ConstexprSpecKind::Unspecified) {
- // Except allow redeclaring a builtin as non-constexpr to match C
- // redeclarations which will not be constexpr
- New->setConstexprKind(ConstexprSpecKind::Constexpr);
- } else {
- Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch)
- << New << static_cast<int>(New->getConstexprKind())
- << static_cast<int>(Old->getConstexprKind());
- Diag(Old->getLocation(), diag::note_previous_declaration);
- Invalid = true;
- }
+ Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch)
+ << New << static_cast<int>(New->getConstexprKind())
+ << static_cast<int>(Old->getConstexprKind());
+ Diag(Old->getLocation(), diag::note_previous_declaration);
+ Invalid = true;
} else if (!Old->getMostRecentDecl()->isInlined() && New->isInlined() &&
Old->isDefined(Def) &&
// If a friend function is inlined but does not have 'inline'
diff --git a/clang/test/Sema/builtin-redecl.cpp b/clang/test/Sema/builtin-redecl.cpp
index 31409a4d46a65b..323c63e2028833 100644
--- a/clang/test/Sema/builtin-redecl.cpp
+++ b/clang/test/Sema/builtin-redecl.cpp
@@ -14,18 +14,13 @@ void __builtin_va_copy(double d);
// expected-error 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
-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}}
+void *__builtin_assume_aligned(const void *, size_t, ...) noexcept;
+#else
+void *__builtin_assume_aligned(const void *, size_t, ...);
#endif
>From ec88839cb34bf2a5f57edd376e30d8c08740f855 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Tue, 14 May 2024 18:57:09 +0100
Subject: [PATCH 04/11] Prevent consteval builtins from being available when
consteval doesn't make sense (!CPlusPlus20)
---
clang/include/clang/Basic/Builtins.def | 1 +
clang/include/clang/Basic/BuiltinsBase.td | 2 +-
clang/lib/Basic/Builtins.cpp | 3 +++
clang/lib/Sema/SemaDecl.cpp | 3 ++-
4 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def
index f356f881d5ef9b..f3d76425733805 100644
--- a/clang/include/clang/Basic/Builtins.def
+++ b/clang/include/clang/Basic/Builtins.def
@@ -100,3 +100,4 @@
// M_0, ..., M_k as payload
// z -> this is a function in (possibly-versioned) namespace std
// E -> this function can be constant evaluated by Clang frontend
+// G -> this is a C++20 consteval function
diff --git a/clang/include/clang/Basic/BuiltinsBase.td b/clang/include/clang/Basic/BuiltinsBase.td
index 1196b9e15c10dc..58dee22fc0a450 100644
--- a/clang/include/clang/Basic/BuiltinsBase.td
+++ b/clang/include/clang/Basic/BuiltinsBase.td
@@ -70,7 +70,7 @@ class VScanfFormat<int I> : IndexedAttribute<"S", I>;
// Builtin can be constant evaluated
def Constexpr : Attribute<"E">;
-// Builtin is immediate and must be constant evaluated. Implies Constexpr.
+// Builtin is immediate and must be constant evaluated. Implies Constexpr, and will only be supported in C++20 mode.
def Consteval : Attribute<"EG">;
// Builtin kinds
diff --git a/clang/lib/Basic/Builtins.cpp b/clang/lib/Basic/Builtins.cpp
index b116abbe034f7a..7116e27cd95463 100644
--- a/clang/lib/Basic/Builtins.cpp
+++ b/clang/lib/Basic/Builtins.cpp
@@ -119,6 +119,9 @@ static bool builtinIsSupported(const Builtin::Info &BuiltinInfo,
/* CPlusPlus Unsupported */
if (!LangOpts.CPlusPlus && BuiltinInfo.Langs == CXX_LANG)
return false;
+ /* consteval Unsupported */
+ if (!LangOpts.CPlusPlus20 && strchr(BuiltinInfo.Attributes, 'G') != nullptr)
+ return false;
return true;
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 7ca02b5bbf6249..d651ee2f502b18 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2410,7 +2410,8 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type,
}
ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified;
- if (getLangOpts().CPlusPlus && Context.BuiltinInfo.isImmediate(ID)) {
+ if (Context.BuiltinInfo.isImmediate(ID)) {
+ assert(getLangOpts().CPlusPlus20 && "consteval builtins should only be available in C++20 mode");
ConstexprKind = ConstexprSpecKind::Consteval;
}
>From b498e90f6bd72e2b9044aef9a4c049845a68bc0d Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Tue, 14 May 2024 19:06:46 +0100
Subject: [PATCH 05/11] clang-format
---
clang/lib/Sema/SemaDecl.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 8554cb59729ff3..c24ebc3ef4ebbb 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2373,7 +2373,8 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type,
ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified;
if (Context.BuiltinInfo.isImmediate(ID)) {
- assert(getLangOpts().CPlusPlus20 && "consteval builtins should only be available in C++20 mode");
+ assert(getLangOpts().CPlusPlus20 &&
+ "consteval builtins should only be available in C++20 mode");
ConstexprKind = ConstexprSpecKind::Consteval;
}
>From bfb918623032748df99ccf948e7648de416477d0 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 20 May 2024 10:52:57 +0100
Subject: [PATCH 06/11] Add test specifically for consteval builtins
---
clang/lib/CodeGen/CGBuiltin.cpp | 3 ++
clang/test/SemaCXX/consteval-builtin.cpp | 61 ++++++++++++++++++++++++
2 files changed, 64 insertions(+)
create mode 100644 clang/test/SemaCXX/consteval-builtin.cpp
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index e251091c6ce3ee..513c6b6385838d 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -2540,6 +2540,9 @@ static RValue EmitHipStdParUnsupportedBuiltin(CodeGenFunction *CGF,
RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
const CallExpr *E,
ReturnValueSlot ReturnValue) {
+ assert(!getContext().BuiltinInfo.isImmediate(BuiltinID) &&
+ "Should not codegen for consteval builtins");
+
const FunctionDecl *FD = GD.getDecl()->getAsFunction();
// See if we can constant fold this builtin. If so, don't emit it at all.
// TODO: Extend this handling to all builtin calls that we can constant-fold.
diff --git a/clang/test/SemaCXX/consteval-builtin.cpp b/clang/test/SemaCXX/consteval-builtin.cpp
new file mode 100644
index 00000000000000..c8f89fc7c892c7
--- /dev/null
+++ b/clang/test/SemaCXX/consteval-builtin.cpp
@@ -0,0 +1,61 @@
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -Wno-unused %s -verify=cxx20-cxx26
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -Wno-unused %s -verify=cxx20,cxx20-cxx26
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -Wno-unused %s -verify=precxx20,cxx11-cxx17
+// RUN: %clang_cc1 -std=c++14 -fsyntax-only -Wno-unused %s -verify=precxx20,cxx11-cxx17
+// RUN: %clang_cc1 -std=c++11 -fsyntax-only -Wno-unused %s -verify=precxx20,cxx11-cxx17
+// RUN: %clang_cc1 -std=c++03 -fsyntax-only -Wno-unused %s -verify=precxx20
+// RUN: %clang_cc1 -std=c++98 -fsyntax-only -Wno-unused %s -verify=precxx20
+// RUN: %clang_cc1 -x c -std=c23 -fsyntax-only -Wno-unused %s -verify=c
+
+#if __cplusplus < 201103L
+#define static_assert __extension__ _Static_assert
+#define CONSTEXPR11
+#else
+#define CONSTEXPR11 constexpr
+#endif
+
+static const int i1 = 0;
+static_assert(__builtin_is_within_lifetime(&i1), "");
+// precxx20-error 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;
+#endif
>From 53ebf025473dbe48fa20a0f4fea443595b0cec3a Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 20 May 2024 11:59:36 +0100
Subject: [PATCH 07/11] Add more tests
---
.../SemaCXX/builtin-is-within-lifetime.cpp | 93 ++++++++++++++-----
1 file changed, 68 insertions(+), 25 deletions(-)
diff --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
index fcc6303e051e6d..c6190aff84abd9 100644
--- a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
+++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
@@ -1,4 +1,6 @@
-// RUN: %clang_cc1 -std=c++2c -verify %s -fcxx-exceptions -Wno-unused -triple=x86_64-linux-gnu
+// RUN: %clang_cc1 -std=c++20 -Wno-unused %s -verify=expected,cxx20
+// RUN: %clang_cc1 -std=c++23 -Wno-unused %s -verify=expected
+// RUN: %clang_cc1 -std=c++2c -Wno-unused %s -verify=expected
inline void* operator new(__SIZE_TYPE__, void* p) noexcept { return p; }
namespace std {
@@ -11,28 +13,9 @@ struct allocator {
constexpr T* allocate(__SIZE_TYPE__ n) { return static_cast<T*>(::operator new(n * sizeof(T))); }
constexpr void deallocate(T* p, __SIZE_TYPE__) { ::operator delete(p); }
};
+using nullptr_t = decltype(nullptr);
}
-constexpr void check_immediate() {
- if consteval {
- if (false) __builtin_is_within_lifetime(static_cast<int*>(nullptr));
- }
-}
-auto* not_escalated = &check_immediate;
-
-template<typename T>
-constexpr bool check_immediate2() { // #check_immediate2
- T i{};
- if (false)
- __builtin_is_within_lifetime(&i); // #check_immediate2-call
- return true;
-}
-static_assert(check_immediate2<int>());
-auto* escalated = &check_immediate2<int>;
-// expected-error 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;
@@ -101,10 +84,70 @@ consteval bool test_dynamic() {
}
static_assert(test_dynamic());
+consteval bool test_automatic() {
+ int* p;
+ {
+ int x = 0;
+ p = &x;
+ if (!__builtin_is_within_lifetime(p))
+ return false;
+ }
+ {
+ int x = 0;
+ if (__builtin_is_within_lifetime(p))
+ return false;
+ }
+ if (__builtin_is_within_lifetime(p))
+ return false;
+ {
+ int x[4];
+ p = &x[2];
+ if (!__builtin_is_within_lifetime(p))
+ return false;
+ }
+ if (__builtin_is_within_lifetime(p))
+ return false;
+ std::nullptr_t* q;
+ {
+ std::nullptr_t np = nullptr;
+ q = &np;
+ if (!__builtin_is_within_lifetime(q))
+ return false;
+ }
+ if (__builtin_is_within_lifetime(q))
+ return false;
+ return true;
+}
+static_assert(test_automatic());
+
+consteval bool test_indeterminate() {
+ int x;
+ if (!__builtin_is_within_lifetime(&x))
+ return false;
+ bool b = true;
+ unsigned char c = __builtin_bit_cast(unsigned char, b);
+ if (!__builtin_is_within_lifetime(&c))
+ return false;
+ struct {} padding;
+ unsigned char y = __builtin_bit_cast(unsigned char, padding);
+ if (!__builtin_is_within_lifetime(&y))
+ return false;
+ return true;
+}
+static_assert(test_indeterminate());
+
+consteval bool test_volatile() {
+ int x;
+ if (!__builtin_is_within_lifetime(static_cast<volatile int*>(&x)) || !__builtin_is_within_lifetime(static_cast<volatile void*>(&x)))
+ return false;
+ volatile int y;
+ if (!__builtin_is_within_lifetime(const_cast<int*>(&y)) || !__builtin_is_within_lifetime(const_cast<void*>(static_cast<volatile void*>(&y))))
+ return false;
+ return true;
+}
+static_assert(test_volatile());
+
constexpr bool self = __builtin_is_within_lifetime(&self);
-// expected-error 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() {
@@ -192,7 +235,7 @@ struct OptBool {
constexpr OptBool(bool b) : b(b) { }
constexpr auto has_value() const -> bool {
- if consteval {
+ if consteval { // cxx20-warning {{consteval if}}
return __builtin_is_within_lifetime(&b); // during constant evaluation, cannot read from c
} else {
return c != 2; // during runtime, must read from c
>From 6805d86090ea84d4c5ed4f7ab72dca9f9ba77bc7 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 20 May 2024 12:05:46 +0100
Subject: [PATCH 08/11] clang-format
---
clang/lib/AST/ExprConstant.cpp | 128 ++++++++++++++++++--------------
clang/lib/Sema/SemaChecking.cpp | 13 ++--
2 files changed, 81 insertions(+), 60 deletions(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index f3b31d87dde9b9..ffe2f172a9a5a9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1507,7 +1507,8 @@ CallStackFrame::~CallStackFrame() {
}
static bool isRead(AccessKinds AK) {
- return AK == AK_Read || AK == AK_ReadObjectRepresentation || AK == AK_IsWithinLifetime;
+ return AK == AK_Read || AK == AK_ReadObjectRepresentation ||
+ AK == AK_IsWithinLifetime;
}
static bool isModification(AccessKinds AK) {
@@ -1535,7 +1536,8 @@ static bool isAnyAccess(AccessKinds AK) {
/// Is this an access per the C++ definition?
static bool isFormalAccess(AccessKinds AK) {
- return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy && AK != AK_IsWithinLifetime;
+ return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy &&
+ AK != AK_IsWithinLifetime;
}
/// Is this kind of axcess valid on an indeterminate object value?
@@ -3655,7 +3657,8 @@ struct CompleteObject {
// In C++14 onwards, it is permitted to read a mutable member whose
// lifetime began within the evaluation.
// FIXME: Should we also allow this in C++11?
- if (!Info.getLangOpts().CPlusPlus14 && AK != AccessKinds::AK_IsWithinLifetime)
+ if (!Info.getLangOpts().CPlusPlus14 &&
+ AK != AccessKinds::AK_IsWithinLifetime)
return false;
return lifetimeStartedInEvaluation(Info, Base, /*MutableSubobject*/true);
}
@@ -4093,7 +4096,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
// started in the current evaluation.
BaseVal = Info.EvaluatingDeclValue;
if (AK == AccessKinds::AK_IsWithinLifetime)
- return CompleteObject(); // Not within lifetime
+ return CompleteObject(); // Not within lifetime
} else if (const ValueDecl *D = LVal.Base.dyn_cast<const ValueDecl *>()) {
// Allow reading from a GUID declaration.
if (auto *GD = dyn_cast<MSGuidDecl>(D)) {
@@ -11508,7 +11511,8 @@ class IntExprEvaluator
bool ZeroInitialization(const Expr *E) { return Success(0, E); }
- friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &, const CallExpr *);
+ friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &,
+ const CallExpr *);
//===--------------------------------------------------------------------===//
// Visitor Methods
@@ -17030,56 +17034,70 @@ bool Expr::tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const {
}
namespace {
- struct IsWithinLifetimeHandler {
- EvalInfo &Info;
- static constexpr AccessKinds AccessKind = AccessKinds::AK_IsWithinLifetime;
- using result_type = std::optional<bool>;
- std::optional<bool> failed() { return std::nullopt; }
- template<typename T>
- std::optional<bool> found(T &Subobj, QualType SubobjType) {
- return true;
- }
- };
+struct IsWithinLifetimeHandler {
+ EvalInfo &Info;
+ static constexpr AccessKinds AccessKind = AccessKinds::AK_IsWithinLifetime;
+ using result_type = std::optional<bool>;
+ std::optional<bool> failed() { return std::nullopt; }
+ template <typename T>
+ std::optional<bool> found(T &Subobj, QualType SubobjType) {
+ return true;
+ }
+};
- std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, const CallExpr *E) {
- EvalInfo& Info = IEE.Info;
- //assert(Info.InConstantContext && "Call to consteval builtin not in constant context?");
- assert(E->getBuiltinCallee() == Builtin::BI__builtin_is_within_lifetime);
- const Expr *Arg = E->getArg(0);
- if (Arg->isValueDependent())
- return std::nullopt;
- LValue Val;
- if (!EvaluatePointer(Arg, Val, Info))
- return std::nullopt;
+std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE,
+ const CallExpr *E) {
+ EvalInfo &Info = IEE.Info;
+ // assert(Info.InConstantContext && "Call to consteval builtin not in constant
+ // context?");
+ assert(E->getBuiltinCallee() == Builtin::BI__builtin_is_within_lifetime);
+ const Expr *Arg = E->getArg(0);
+ if (Arg->isValueDependent())
+ return std::nullopt;
+ LValue Val;
+ if (!EvaluatePointer(Arg, Val, Info))
+ return std::nullopt;
- auto Error = [&](int Diag) {
- const auto *Callee = Info.CurrentCall->getCallee();
- bool CalledFromStd = Callee && Callee->isInStdNamespace() && Callee->getIdentifier() && Callee->getIdentifier()->isStr("is_within_lifetime");
- Info.report(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin() : E->getExprLoc(), diag::err_invalid_is_within_lifetime)
- << (CalledFromStd ? "std::is_within_lifetime" : "__builtin_is_within_lifetime") << Diag;
- return std::nullopt;
- };
- // C++2c [meta.const.eval]p4:
- // During the evaluation of an expression E as a core constant expression, a call to this function is ill-formed unless p points to an object that is usable in constant expressions or whose complete object's lifetime began within E.
-
- // Make sure it points to an object
- // nullptr does not point to an object
- if (Val.isNullPointer() || Val.getLValueBase().isNull())
- return Error(0);
- QualType T = Val.getLValueBase().getType();
- if (T->isFunctionType())
- return Error(1);
- assert(T->isObjectType());
- // Hypothetical array element is not an object
- if (Val.getLValueDesignator().isOnePastTheEnd())
- return Error(2);
- assert(Val.getLValueDesignator().isValidSubobject() && "Unchecked case for valid subobject");
- // All other ill-formed values should have failed EvaluatePointer, so the object should be a pointer to an object
- // that is usable in a constant expression or whose complete lifetime began within the expression
- CompleteObject CO = findCompleteObject(Info, E, AccessKinds::AK_IsWithinLifetime, Val, T);
- if (!CO)
- return false;
- IsWithinLifetimeHandler handler{ Info };
- return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler);
- }
+ auto Error = [&](int Diag) {
+ const auto *Callee = Info.CurrentCall->getCallee();
+ bool CalledFromStd = Callee && Callee->isInStdNamespace() &&
+ Callee->getIdentifier() &&
+ Callee->getIdentifier()->isStr("is_within_lifetime");
+ Info.report(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin()
+ : E->getExprLoc(),
+ diag::err_invalid_is_within_lifetime)
+ << (CalledFromStd ? "std::is_within_lifetime"
+ : "__builtin_is_within_lifetime")
+ << Diag;
+ return std::nullopt;
+ };
+ // C++2c [meta.const.eval]p4:
+ // During the evaluation of an expression E as a core constant expression, a
+ // call to this function is ill-formed unless p points to an object that is
+ // usable in constant expressions or whose complete object's lifetime began
+ // within E.
+
+ // Make sure it points to an object
+ // nullptr does not point to an object
+ if (Val.isNullPointer() || Val.getLValueBase().isNull())
+ return Error(0);
+ QualType T = Val.getLValueBase().getType();
+ if (T->isFunctionType())
+ return Error(1);
+ assert(T->isObjectType());
+ // Hypothetical array element is not an object
+ if (Val.getLValueDesignator().isOnePastTheEnd())
+ return Error(2);
+ assert(Val.getLValueDesignator().isValidSubobject() &&
+ "Unchecked case for valid subobject");
+ // All other ill-formed values should have failed EvaluatePointer, so the
+ // object should be a pointer to an object that is usable in a constant
+ // expression or whose complete lifetime began within the expression
+ CompleteObject CO =
+ findCompleteObject(Info, E, AccessKinds::AK_IsWithinLifetime, Val, T);
+ if (!CO)
+ return false;
+ IsWithinLifetimeHandler handler{Info};
+ return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler);
}
+} // namespace
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 127175f9ceeba9..df2dd9beb75287 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2227,13 +2227,16 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
TheCall->setArg(0, Arg.get());
TheCall->setType(S.Context.BoolTy);
- // A call to this function is always ill-formed if the type is not a pointer to
- // an object type. There is no Mandates: to that effect, so we can only
+ // A call to this function is always ill-formed if the type is not a pointer
+ // to an object type. There is no Mandates: to that effect, so we can only
// issue an error if it is actually evaluated as part of a constant evaluation
- // (e.g., `false ? true : std::is_within_lifetime(static_cast<void(*)()>(nullptr));` is fine)
- // However, `std::is_within_lifetime` will only take pointer types (allow non-const qualified too)
+ // (e.g., `false ? true :
+ // std::is_within_lifetime(static_cast<void(*)()>(nullptr));` is fine)
+ // However, `std::is_within_lifetime` will only take pointer types (allow
+ // non-const qualified too)
if (!ParamTy->isPointerType()) {
- S.Diag(TheCall->getArg(0)->getExprLoc(), diag::err_builtin_is_within_lifetime_invalid_arg);
+ S.Diag(TheCall->getArg(0)->getExprLoc(),
+ diag::err_builtin_is_within_lifetime_invalid_arg);
return ExprError();
}
>From d50febdea9b7b4a6b59955765b43bb9670cdc68f Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 20 May 2024 13:14:41 +0100
Subject: [PATCH 09/11] Consolidate diagnostics text + test invalid calls
---
.../include/clang/Basic/DiagnosticASTKinds.td | 52 +++++++------------
clang/lib/Sema/SemaExpr.cpp | 5 +-
.../SemaCXX/builtin-is-within-lifetime.cpp | 22 +++++++-
clang/test/SemaCXX/consteval-builtin.cpp | 14 +++++
4 files changed, 55 insertions(+), 38 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 7f8908e45663b9..ebd8bb75ef6be1 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -166,15 +166,17 @@ def note_constexpr_heap_alloc_limit_exceeded : Note<
def note_constexpr_this : Note<
"%select{|implicit }0use of 'this' pointer is only allowed within the "
"evaluation of a call to a 'constexpr' member function">;
+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|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|read of}0">;
+def access_kind_volatile : TextSubstitution<
+ "%select{read of|read of|assignment to|increment of|decrement of|<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>}0">;
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|read of}0 %select{temporary|variable}1 whose "
+ "%sub{access_kind}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|read of}0 "
+ "%sub{access_kind_subobject}0 "
"%select{object outside its lifetime|uninitialized object}1 "
"is not allowed in a constant expression">;
def note_constexpr_use_uninit_reference : Note<
@@ -184,20 +186,16 @@ def note_constexpr_modify_const_type : Note<
"modification of object of const-qualified type %0 is not allowed "
"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>|read of}0 "
+ "%sub{access_kind_volatile}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>|read of}0 "
+ "%sub{access_kind_volatile}0 "
"volatile %select{temporary|object %2|member %2}1 is not allowed in "
"a constant expression">;
def note_constexpr_volatile_here : Note<
"volatile %select{temporary created|object declared|member declared}0 here">;
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|read of}0 "
+ "%sub{access_kind}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">;
@@ -209,46 +207,32 @@ def note_constexpr_ltor_non_constexpr : Note<
def note_constexpr_ltor_incomplete_type : Note<
"read of incomplete type %0 is not allowed in a constant expression">;
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|read of}0 "
+ "%sub{access_kind}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|read of}0 "
+ "%sub{access_kind}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|read of}0 "
+ "%sub{access_kind}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|read of}0 "
+ "%sub{access_kind_subobject}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<
"assignment would change active union member during the initialization of "
"a different member of the same union">;
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|read of|read of}0 temporary "
+ "%sub{access_kind}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|read of}0 "
+ "%sub{access_kind}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|read of}0 "
+ "%sub{access_kind}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 "
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index b691e7109f9e69..6f36ef927f58d4 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6926,7 +6926,7 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl,
// Bail out early if calling a builtin with custom type checking.
if (BuiltinID && Context.BuiltinInfo.hasCustomTypechecking(BuiltinID)) {
ExprResult E = CheckBuiltinFunctionCall(FDecl, BuiltinID, TheCall);
- if (!E.isInvalid() && Context.BuiltinInfo.isImmediate(BuiltinID))
+ if (Context.BuiltinInfo.isImmediate(BuiltinID))
E = CheckForImmediateInvocation(E, FDecl);
return E;
}
@@ -17707,7 +17707,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
index c6190aff84abd9..e37fe4e1d00839 100644
--- a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
+++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
@@ -150,9 +150,27 @@ static_assert(test_volatile());
constexpr bool self = __builtin_is_within_lifetime(&self);
constexpr int external{};
static_assert(__builtin_is_within_lifetime(&external));
-bool not_constexpr() {
- return __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 {{'__builtin_is_within_lifetime' cannot be called with a null pointer}}
+// expected-error at -2 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+ __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;
diff --git a/clang/test/SemaCXX/consteval-builtin.cpp b/clang/test/SemaCXX/consteval-builtin.cpp
index c8f89fc7c892c7..23195d01f9f974 100644
--- a/clang/test/SemaCXX/consteval-builtin.cpp
+++ b/clang/test/SemaCXX/consteval-builtin.cpp
@@ -58,4 +58,18 @@ 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
>From 43470561ea71a402b814c3758e786a0cf1504d1f Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 12 Aug 2024 21:15:14 +0100
Subject: [PATCH 10/11] Error on VLAs
---
clang/lib/Sema/SemaChecking.cpp | 10 ++++++-
.../SemaCXX/builtin-is-within-lifetime.cpp | 28 +++++++++++++++++--
2 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 0126bd200dc45a..d4831a7f53fa82 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1862,7 +1862,15 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
// 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()) {
+ 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();
diff --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
index e37fe4e1d00839..a3be78e99d6ae1 100644
--- a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
+++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
@@ -1,6 +1,6 @@
-// RUN: %clang_cc1 -std=c++20 -Wno-unused %s -verify=expected,cxx20
-// RUN: %clang_cc1 -std=c++23 -Wno-unused %s -verify=expected
-// RUN: %clang_cc1 -std=c++2c -Wno-unused %s -verify=expected
+// 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++2c -Wno-unused %s -verify=expected,sincecxx23 -Wno-vla-cxx-extension
inline void* operator new(__SIZE_TYPE__, void* p) noexcept { return p; }
namespace std {
@@ -270,3 +270,25 @@ 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
>From 82178d2f0e6bf040647af214eafd37e8ecff9980 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 12 Aug 2024 21:44:04 +0100
Subject: [PATCH 11/11] Add some more tests
---
clang/lib/Sema/SemaChecking.cpp | 5 +-
.../SemaCXX/builtin-is-within-lifetime.cpp | 51 ++++++++++++++++++-
2 files changed, 52 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index d4831a7f53fa82..1d2fa342429e2c 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1845,7 +1845,7 @@ static ExprResult BuiltinLaunder(Sema &S, CallExpr *TheCall) {
}
static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
- if (checkArgCount(S, TheCall, 1))
+ if (S.checkArgCount(TheCall, 1))
return ExprError();
ExprResult Arg = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(0));
@@ -1858,8 +1858,7 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
// 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)
+ // (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>()) {
diff --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
index a3be78e99d6ae1..eeb321b06e0ef0 100644
--- a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
+++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
@@ -195,19 +195,21 @@ static_assert(!x2.b);
static_assert(!x2.c);
struct X3 {
- bool a, b, c, d;
+ bool a, b, c, d, e;
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);
+ e = __builtin_is_within_lifetime(this);
}
constexpr X3 x3{};
static_assert(!x3.a);
static_assert(!x3.b);
static_assert(!x3.c);
static_assert(!x3.d);
+static_assert(!x3.e);
constexpr int i = 2;
static_assert(__builtin_is_within_lifetime(const_cast<int*>(&i)));
@@ -292,3 +294,50 @@ consteval bool variably_modified(int n) {
static_assert(variably_modified(1));
} // namespace vlas
+
+consteval bool arrays() {
+ int(*arrp)[2];
+ {
+ int arr[2];
+ arrp = &arr;
+ 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]);
+ }
+ return !__builtin_is_within_lifetime(arrp);
+}
+static_assert(arrays());
+
+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;
+ 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}}
+static_assert(!__builtin_is_within_lifetime(test_dangling<int[1]>())); // expected-note {{in instantiation of function template specialization}}
More information about the cfe-commits
mailing list