-
Notifications
You must be signed in to change notification settings - Fork 11.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Clang] Add __builtin_is_within_lifetime to implement P2641R4's std::is_within_lifetime #91895
Changes from all commits
f602189
05a2514
09a175b
70fc707
f4a3a18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1522,7 +1522,8 @@ CallStackFrame::~CallStackFrame() { | |
} | ||
|
||
static bool isRead(AccessKinds AK) { | ||
return AK == AK_Read || AK == AK_ReadObjectRepresentation; | ||
return AK == AK_Read || AK == AK_ReadObjectRepresentation || | ||
AK == AK_IsWithinLifetime; | ||
} | ||
|
||
static bool isModification(AccessKinds AK) { | ||
|
@@ -1532,6 +1533,7 @@ static bool isModification(AccessKinds AK) { | |
case AK_MemberCall: | ||
case AK_DynamicCast: | ||
case AK_TypeId: | ||
case AK_IsWithinLifetime: | ||
return false; | ||
case AK_Assign: | ||
case AK_Increment: | ||
|
@@ -1549,7 +1551,8 @@ static bool isAnyAccess(AccessKinds AK) { | |
|
||
/// Is this an access per the C++ definition? | ||
static bool isFormalAccess(AccessKinds AK) { | ||
return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy; | ||
return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy && | ||
AK != AK_IsWithinLifetime; | ||
} | ||
|
||
/// Is this kind of axcess valid on an indeterminate object value? | ||
|
@@ -1561,6 +1564,7 @@ static bool isValidIndeterminateAccess(AccessKinds AK) { | |
// These need the object's value. | ||
return false; | ||
|
||
case AK_IsWithinLifetime: | ||
case AK_ReadObjectRepresentation: | ||
case AK_Assign: | ||
case AK_Construct: | ||
|
@@ -3707,7 +3711,8 @@ struct CompleteObject { | |
// In C++14 onwards, it is permitted to read a mutable member whose | ||
// lifetime began within the evaluation. | ||
// FIXME: Should we also allow this in C++11? | ||
if (!Info.getLangOpts().CPlusPlus14) | ||
if (!Info.getLangOpts().CPlusPlus14 && | ||
AK != AccessKinds::AK_IsWithinLifetime) | ||
return false; | ||
return lifetimeStartedInEvaluation(Info, Base, /*MutableSubobject*/true); | ||
} | ||
|
@@ -3760,6 +3765,12 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, | |
if ((O->isAbsent() && !(handler.AccessKind == AK_Construct && I == N)) || | ||
(O->isIndeterminate() && | ||
!isValidIndeterminateAccess(handler.AccessKind))) { | ||
// Object has ended lifetime. | ||
// If I is non-zero, some subobject (member or array element) of a | ||
// complete object has ended its lifetime, so this is valid for | ||
// IsWithinLifetime, resulting in false. | ||
if (I != 0 && handler.AccessKind == AK_IsWithinLifetime) | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK you can't use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are already other |
||
if (!Info.checkingPotentialConstantExpression()) | ||
Info.FFDiag(E, diag::note_constexpr_access_uninit) | ||
<< handler.AccessKind << O->isIndeterminate() | ||
|
@@ -3927,6 +3938,9 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, | |
// Placement new onto an inactive union member makes it active. | ||
O->setUnion(Field, APValue()); | ||
} else { | ||
// Pointer to/into inactive union member: Not within lifetime | ||
if (handler.AccessKind == AK_IsWithinLifetime) | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
// FIXME: If O->getUnionValue() is absent, report that there's no | ||
// active union member rather than reporting the prior active union | ||
// member. We'll need to fix nullptr_t to not use APValue() as its | ||
|
@@ -11684,6 +11698,9 @@ class IntExprEvaluator | |
|
||
bool ZeroInitialization(const Expr *E) { return Success(0, E); } | ||
|
||
friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &, | ||
const CallExpr *); | ||
|
||
//===--------------------------------------------------------------------===// | ||
// Visitor Methods | ||
//===--------------------------------------------------------------------===// | ||
|
@@ -12743,6 +12760,11 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, | |
return Success(Info.InConstantContext, E); | ||
} | ||
|
||
case Builtin::BI__builtin_is_within_lifetime: | ||
if (auto result = EvaluateBuiltinIsWithinLifetime(*this, E)) | ||
return Success(*result, E); | ||
return false; | ||
|
||
case Builtin::BI__builtin_ctz: | ||
case Builtin::BI__builtin_ctzl: | ||
case Builtin::BI__builtin_ctzll: | ||
|
@@ -17332,3 +17354,84 @@ bool Expr::tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const { | |
EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantFold); | ||
return EvaluateBuiltinStrLen(this, Result, Info); | ||
} | ||
|
||
namespace { | ||
struct IsWithinLifetimeHandler { | ||
EvalInfo &Info; | ||
static constexpr AccessKinds AccessKind = AccessKinds::AK_IsWithinLifetime; | ||
using result_type = std::optional<bool>; | ||
std::optional<bool> failed() { return std::nullopt; } | ||
template <typename T> | ||
std::optional<bool> found(T &Subobj, QualType SubobjType) { | ||
return true; | ||
} | ||
}; | ||
|
||
std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, | ||
const CallExpr *E) { | ||
EvalInfo &Info = IEE.Info; | ||
// Sometimes this is called during some sorts of constant folding / early | ||
// evaluation. These are meant for non-constant expressions and are not | ||
// necessary since this consteval builtin will never be evaluated at runtime. | ||
// Just fail to evaluate when not in a constant context. | ||
if (!Info.InConstantContext) | ||
return std::nullopt; | ||
assert(E->getBuiltinCallee() == Builtin::BI__builtin_is_within_lifetime); | ||
const Expr *Arg = E->getArg(0); | ||
if (Arg->isValueDependent()) | ||
return std::nullopt; | ||
LValue Val; | ||
if (!EvaluatePointer(Arg, Val, Info)) | ||
return std::nullopt; | ||
|
||
auto Error = [&](int Diag) { | ||
bool CalledFromStd = false; | ||
const auto *Callee = Info.CurrentCall->getCallee(); | ||
if (Callee && Callee->isInStdNamespace()) { | ||
const IdentifierInfo *Identifier = Callee->getIdentifier(); | ||
CalledFromStd = Identifier && Identifier->isStr("is_within_lifetime"); | ||
} | ||
Info.CCEDiag(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin() | ||
: E->getExprLoc(), | ||
diag::err_invalid_is_within_lifetime) | ||
<< (CalledFromStd ? "std::is_within_lifetime" | ||
: "__builtin_is_within_lifetime") | ||
<< Diag; | ||
return std::nullopt; | ||
}; | ||
// C++2c [meta.const.eval]p4: | ||
// During the evaluation of an expression E as a core constant expression, a | ||
// call to this function is ill-formed unless p points to an object that is | ||
// usable in constant expressions or whose complete object's lifetime began | ||
// within E. | ||
|
||
// Make sure it points to an object | ||
// nullptr does not point to an object | ||
if (Val.isNullPointer() || Val.getLValueBase().isNull()) | ||
return Error(0); | ||
QualType T = Val.getLValueBase().getType(); | ||
assert(!T->isFunctionType() && | ||
"Pointers to functions should have been typed as function pointers " | ||
"which would have been rejected earlier"); | ||
assert(T->isObjectType()); | ||
// Hypothetical array element is not an object | ||
if (Val.getLValueDesignator().isOnePastTheEnd()) | ||
return Error(1); | ||
assert(Val.getLValueDesignator().isValidSubobject() && | ||
"Unchecked case for valid subobject"); | ||
// All other ill-formed values should have failed EvaluatePointer, so the | ||
// object should be a pointer to an object that is usable in a constant | ||
// expression or whose complete lifetime began within the expression | ||
CompleteObject CO = | ||
findCompleteObject(Info, E, AccessKinds::AK_IsWithinLifetime, Val, T); | ||
// The lifetime hasn't begun yet if we are still evaluating the | ||
// initializer ([basic.life]p(1.2)) | ||
if (Info.EvaluatingDeclValue && CO.Value == Info.EvaluatingDeclValue) | ||
return Error(2); | ||
|
||
if (!CO) | ||
return false; | ||
IsWithinLifetimeHandler handler{Info}; | ||
return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. associated with previous comment ... you are returning There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it's intentional: |
||
} | ||
} // namespace |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
read of
is already there (0)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for the messages shown in places like this:
This is the same message as
AK_Read
andAK_ReadObjectRepresentation
becauseis_within_lifetime
has basically the same restrictions as reading the value of an object (you "read" the value just to check if it is in lifetime).Open to other suggestions to make it more clear
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right. that's good enough then!