[clang] [Clang] Implement P2280R4 Using unknown pointers and references in constant expressions (PR #95474)
Shafik Yaghmour via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 30 18:40:50 PDT 2024
https://github.com/shafik updated https://github.com/llvm/llvm-project/pull/95474
>From 69b09ea5b0f0a1c5419c488ade29b6fedc6de773 Mon Sep 17 00:00:00 2001
From: Shafik Yaghmour <shafik.yaghmour at intel.com>
Date: Thu, 13 Jun 2024 14:20:50 -0700
Subject: [PATCH 1/5] [Clang] Implement P2280R4 Using unknown pointers and
references in constant expressions
P2280R4 allows the use of references in pointers of unknown origins in a
constant expression context but only in specific cases that could be constant
expressions.
We track whether a variable is a constexpr unknown in a constant expression by
setting a flag in either APValue or LValue and using this flag to prevent using
unknown values in places where it is not allowed.
In `evaluateVarDeclInit` we may need to create a new `APValue` to track the
unknown referene or pointer and we track that `APValue` in the
`CallStackFrame`.
Fixes: https://github.com/llvm/llvm-project/issues/63139
https://github.com/llvm/llvm-project/issues/63117
---
clang/include/clang/AST/APValue.h | 48 +++++++----
clang/lib/AST/APValue.cpp | 12 ++-
clang/lib/AST/ExprConstant.cpp | 85 +++++++++++++++++--
.../SemaCXX/constant-expression-cxx11.cpp | 16 ++--
.../SemaCXX/constant-expression-cxx2a.cpp | 3 +-
.../SemaCXX/constant-expression-p2280r4.cpp | 54 ++++++++++++
6 files changed, 183 insertions(+), 35 deletions(-)
create mode 100644 clang/test/SemaCXX/constant-expression-p2280r4.cpp
diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h
index c4206b73b1156..6352348107a64 100644
--- a/clang/include/clang/AST/APValue.h
+++ b/clang/include/clang/AST/APValue.h
@@ -249,6 +249,7 @@ class APValue {
struct NoLValuePath {};
struct UninitArray {};
struct UninitStruct {};
+ struct ConstexprUnknown {};
template <typename Impl> friend class clang::serialization::BasicReaderBase;
friend class ASTImporter;
@@ -256,6 +257,7 @@ class APValue {
private:
ValueKind Kind;
+ bool AllowConstexprUnknown = false;
struct ComplexAPSInt {
APSInt Real, Imag;
@@ -314,53 +316,69 @@ class APValue {
DataType Data;
public:
- APValue() : Kind(None) {}
- explicit APValue(APSInt I) : Kind(None) {
+ bool allowConstexprUnknown() const { return AllowConstexprUnknown; }
+
+ void setConstexprUnknown() { AllowConstexprUnknown = true; }
+
+ APValue() : Kind(None), AllowConstexprUnknown(false) {}
+ explicit APValue(APSInt I) : Kind(None), AllowConstexprUnknown(false) {
MakeInt(); setInt(std::move(I));
}
- explicit APValue(APFloat F) : Kind(None) {
+ explicit APValue(APFloat F) : Kind(None), AllowConstexprUnknown(false) {
MakeFloat(); setFloat(std::move(F));
}
- explicit APValue(APFixedPoint FX) : Kind(None) {
+ explicit APValue(APFixedPoint FX) : Kind(None), AllowConstexprUnknown(false) {
MakeFixedPoint(std::move(FX));
}
- explicit APValue(const APValue *E, unsigned N) : Kind(None) {
+ explicit APValue(const APValue *E, unsigned N)
+ : Kind(None), AllowConstexprUnknown(false) {
MakeVector(); setVector(E, N);
}
- APValue(APSInt R, APSInt I) : Kind(None) {
+ APValue(APSInt R, APSInt I) : Kind(None), AllowConstexprUnknown(false) {
MakeComplexInt(); setComplexInt(std::move(R), std::move(I));
}
- APValue(APFloat R, APFloat I) : Kind(None) {
+ APValue(APFloat R, APFloat I) : Kind(None), AllowConstexprUnknown(false) {
MakeComplexFloat(); setComplexFloat(std::move(R), std::move(I));
}
APValue(const APValue &RHS);
APValue(APValue &&RHS);
APValue(LValueBase B, const CharUnits &O, NoLValuePath N,
bool IsNullPtr = false)
- : Kind(None) {
+ : Kind(None), AllowConstexprUnknown(false) {
MakeLValue(); setLValue(B, O, N, IsNullPtr);
}
APValue(LValueBase B, const CharUnits &O, ArrayRef<LValuePathEntry> Path,
bool OnePastTheEnd, bool IsNullPtr = false)
- : Kind(None) {
+ : Kind(None), AllowConstexprUnknown(false) {
MakeLValue(); setLValue(B, O, Path, OnePastTheEnd, IsNullPtr);
}
- APValue(UninitArray, unsigned InitElts, unsigned Size) : Kind(None) {
+
+ APValue(LValueBase B, ConstexprUnknown, const CharUnits &O,
+ bool IsNullPtr = false)
+ : Kind(None), AllowConstexprUnknown(true) {
+ MakeLValue();
+ setLValue(B, O, NoLValuePath{}, IsNullPtr);
+ }
+
+ APValue(UninitArray, unsigned InitElts, unsigned Size)
+ : Kind(None), AllowConstexprUnknown(false) {
MakeArray(InitElts, Size);
}
- APValue(UninitStruct, unsigned B, unsigned M) : Kind(None) {
+ APValue(UninitStruct, unsigned B, unsigned M)
+ : Kind(None), AllowConstexprUnknown(false) {
MakeStruct(B, M);
}
explicit APValue(const FieldDecl *D, const APValue &V = APValue())
- : Kind(None) {
+ : Kind(None), AllowConstexprUnknown(false) {
MakeUnion(); setUnion(D, V);
}
APValue(const ValueDecl *Member, bool IsDerivedMember,
- ArrayRef<const CXXRecordDecl*> Path) : Kind(None) {
+ ArrayRef<const CXXRecordDecl *> Path)
+ : Kind(None), AllowConstexprUnknown(false) {
MakeMemberPointer(Member, IsDerivedMember, Path);
}
- APValue(const AddrLabelExpr* LHSExpr, const AddrLabelExpr* RHSExpr)
- : Kind(None) {
+ APValue(const AddrLabelExpr *LHSExpr, const AddrLabelExpr *RHSExpr)
+ : Kind(None), AllowConstexprUnknown(false) {
MakeAddrLabelDiff(); setAddrLabelDiff(LHSExpr, RHSExpr);
}
static APValue IndeterminateValue() {
diff --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp
index d8e33ff421c06..f7841cb6556de 100644
--- a/clang/lib/AST/APValue.cpp
+++ b/clang/lib/AST/APValue.cpp
@@ -308,7 +308,8 @@ APValue::UnionData::~UnionData () {
delete Value;
}
-APValue::APValue(const APValue &RHS) : Kind(None) {
+APValue::APValue(const APValue &RHS)
+ : Kind(None), AllowConstexprUnknown(RHS.AllowConstexprUnknown) {
switch (RHS.getKind()) {
case None:
case Indeterminate:
@@ -379,13 +380,17 @@ APValue::APValue(const APValue &RHS) : Kind(None) {
}
}
-APValue::APValue(APValue &&RHS) : Kind(RHS.Kind), Data(RHS.Data) {
+APValue::APValue(APValue &&RHS)
+ : Kind(RHS.Kind), AllowConstexprUnknown(RHS.AllowConstexprUnknown),
+ Data(RHS.Data) {
RHS.Kind = None;
}
APValue &APValue::operator=(const APValue &RHS) {
if (this != &RHS)
*this = APValue(RHS);
+
+ AllowConstexprUnknown = RHS.AllowConstexprUnknown;
return *this;
}
@@ -395,6 +400,7 @@ APValue &APValue::operator=(APValue &&RHS) {
DestroyDataAndMakeUninit();
Kind = RHS.Kind;
Data = RHS.Data;
+ AllowConstexprUnknown = RHS.AllowConstexprUnknown;
RHS.Kind = None;
}
return *this;
@@ -426,6 +432,7 @@ void APValue::DestroyDataAndMakeUninit() {
else if (Kind == AddrLabelDiff)
((AddrLabelDiffData *)(char *)&Data)->~AddrLabelDiffData();
Kind = None;
+ AllowConstexprUnknown = false;
}
bool APValue::needsCleanup() const {
@@ -468,6 +475,7 @@ bool APValue::needsCleanup() const {
void APValue::swap(APValue &RHS) {
std::swap(Kind, RHS.Kind);
std::swap(Data, RHS.Data);
+ std::swap(AllowConstexprUnknown, RHS.AllowConstexprUnknown);
}
/// Profile the value of an APInt, excluding its bit-width.
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 7178f081d9cf3..9a427d908e4c9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1609,8 +1609,11 @@ namespace {
SubobjectDesignator Designator;
bool IsNullPtr : 1;
bool InvalidBase : 1;
+ // P2280R4 track if we have an unknown reference or pointer.
+ bool AllowConstexprUnknown = false;
const APValue::LValueBase getLValueBase() const { return Base; }
+ bool allowConstexprUnknown() const { return AllowConstexprUnknown; }
CharUnits &getLValueOffset() { return Offset; }
const CharUnits &getLValueOffset() const { return Offset; }
SubobjectDesignator &getLValueDesignator() { return Designator; }
@@ -1628,6 +1631,8 @@ namespace {
V = APValue(Base, Offset, Designator.Entries,
Designator.IsOnePastTheEnd, IsNullPtr);
}
+ if (AllowConstexprUnknown)
+ V.setConstexprUnknown();
}
void setFrom(ASTContext &Ctx, const APValue &V) {
assert(V.isLValue() && "Setting LValue from a non-LValue?");
@@ -1636,6 +1641,7 @@ namespace {
InvalidBase = false;
Designator = SubobjectDesignator(Ctx, V);
IsNullPtr = V.isNullPointer();
+ AllowConstexprUnknown = V.allowConstexprUnknown();
}
void set(APValue::LValueBase B, bool BInvalid = false) {
@@ -1653,6 +1659,7 @@ namespace {
InvalidBase = BInvalid;
Designator = SubobjectDesignator(getType(B));
IsNullPtr = false;
+ AllowConstexprUnknown = false;
}
void setNull(ASTContext &Ctx, QualType PointerTy) {
@@ -1662,6 +1669,7 @@ namespace {
InvalidBase = false;
Designator = SubobjectDesignator(PointerTy->getPointeeType());
IsNullPtr = true;
+ AllowConstexprUnknown = false;
}
void setInvalid(APValue::LValueBase B, unsigned I = 0) {
@@ -3300,6 +3308,11 @@ static bool HandleLValueComplexElement(EvalInfo &Info, const Expr *E,
static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
const VarDecl *VD, CallStackFrame *Frame,
unsigned Version, APValue *&Result) {
+ // P2280R4 If we have a reference type and we are in C++23 allow unknown
+ // references and pointers.
+ bool AllowConstexprUnknown =
+ Info.getLangOpts().CPlusPlus23 && VD->getType()->isReferenceType();
+
APValue::LValueBase Base(VD, Frame ? Frame->Index : 0, Version);
// If this is a local variable, dig out its value.
@@ -3334,7 +3347,9 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
return true;
}
- if (isa<ParmVarDecl>(VD)) {
+ // P2280R4 struck the restriction that variable of referene type lifetime
+ // should begin within the evaluation of E
+ if (isa<ParmVarDecl>(VD) && !AllowConstexprUnknown) {
// Assume parameters of a potential constant expression are usable in
// constant expressions.
if (!Info.checkingPotentialConstantExpression() ||
@@ -3358,7 +3373,9 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
// FIXME: We should eventually check whether the variable has a reachable
// initializing declaration.
const Expr *Init = VD->getAnyInitializer(VD);
- if (!Init) {
+ // P2280R4 struck the restriction that variable of referene type should have
+ // a preceding initialization.
+ if (!Init && !AllowConstexprUnknown) {
// Don't diagnose during potential constant expression checking; an
// initializer might be added later.
if (!Info.checkingPotentialConstantExpression()) {
@@ -3369,7 +3386,9 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
return false;
}
- if (Init->isValueDependent()) {
+ // P2280R4 struck the initialization requirement for variables of reference
+ // type so we can no longer assume we have an Init.
+ if (Init && Init->isValueDependent()) {
// The DeclRefExpr is not value-dependent, but the variable it refers to
// has a value-dependent initializer. This should only happen in
// constant-folding cases, where the variable is not actually of a suitable
@@ -3388,7 +3407,9 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
// Check that we can fold the initializer. In C++, we will have already done
// this in the cases where it matters for conformance.
- if (!VD->evaluateValue()) {
+ // P2280R4 struck the initialization requirement for variables of reference
+ // type so we can no longer assume we have an Init.
+ if (Init && !VD->evaluateValue()) {
Info.FFDiag(E, diag::note_constexpr_var_init_non_constant, 1) << VD;
NoteLValueLocation(Info, Base);
return false;
@@ -3420,6 +3441,15 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
}
Result = VD->getEvaluatedValue();
+
+ // P2280R4 If we don't have a value because this is a reference that was not
+ // initialized or whose lifetime began within E then create a value with as
+ // a ConstexprUnknown status.
+ if (AllowConstexprUnknown) {
+ if (!Result) {
+ Result = new APValue(Base, APValue::ConstexprUnknown{}, CharUnits::One());
+ }
+ }
return true;
}
@@ -3700,6 +3730,11 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
const FieldDecl *LastField = nullptr;
const FieldDecl *VolatileField = nullptr;
+ // P2280R4 If we have an unknown referene or pointer and we don't have a
+ // value then bail out.
+ if (O->allowConstexprUnknown() && !O->hasValue())
+ return false;
+
// Walk the designator's path to find the subobject.
for (unsigned I = 0, N = Sub.Entries.size(); /**/; ++I) {
// Reading an indeterminate value is undefined, but assigning over one is OK.
@@ -5732,6 +5767,12 @@ struct CheckDynamicTypeHandler {
/// dynamic type.
static bool checkDynamicType(EvalInfo &Info, const Expr *E, const LValue &This,
AccessKinds AK, bool Polymorphic) {
+ // P2280R4 We are not allowed to invoke a virtual function whose dynamic type
+ // us constexpr-unknown, so stop early and let this fail later on if we
+ // attempt to do so.
+ if (This.allowConstexprUnknown())
+ return true;
+
if (This.Designator.Invalid)
return false;
@@ -5804,7 +5845,13 @@ static std::optional<DynamicType> ComputeDynamicType(EvalInfo &Info,
// If we don't have an lvalue denoting an object of class type, there is no
// meaningful dynamic type. (We consider objects of non-class type to have no
// dynamic type.)
- if (!checkDynamicType(Info, E, This, AK, true))
+ if (!checkDynamicType(Info, E, This, AK,
+ (AK == AK_TypeId
+ ? (E->getType()->isReferenceType() ? true : false)
+ : true)))
+ return std::nullopt;
+
+ if (This.Designator.Invalid)
return std::nullopt;
// Refuse to compute a dynamic type in the presence of virtual bases. This
@@ -8539,7 +8586,8 @@ static bool HandleLambdaCapture(EvalInfo &Info, const Expr *E, LValue &Result,
const ParmVarDecl *Self = MD->getParamDecl(0);
if (Self->getType()->isReferenceType()) {
APValue *RefValue = Info.getParamSlot(Info.CurrentCall->Arguments, Self);
- Result.setFrom(Info.Ctx, *RefValue);
+ if (!RefValue->allowConstexprUnknown() || RefValue->hasValue())
+ Result.setFrom(Info.Ctx, *RefValue);
} else {
const ParmVarDecl *VD = Info.CurrentCall->Arguments.getOrigParam(Self);
CallStackFrame *Frame =
@@ -8595,7 +8643,10 @@ bool LValueExprEvaluator::VisitDeclRefExpr(const DeclRefExpr *E) {
bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
-
+ // P2280R4 if we are in C++23 track if we have an unknown reference or
+ // pointer.
+ bool AllowConstexprUnknown =
+ Info.getLangOpts().CPlusPlus23 && VD->getType()->isReferenceType();
// If we are within a lambda's call operator, check whether the 'VD' referred
// to within 'E' actually represents a lambda-capture that maps to a
// data-member/field within the closure object, and if so, evaluate to the
@@ -8665,10 +8716,23 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
if (!V->hasValue()) {
// FIXME: Is it possible for V to be indeterminate here? If so, we should
// adjust the diagnostic to say that.
- if (!Info.checkingPotentialConstantExpression())
+ // P2280R4 If we are have a variable that is unknown reference or pointer
+ // it may not have a value but still be usable later on so do not diagnose.
+ if (!Info.checkingPotentialConstantExpression() && !AllowConstexprUnknown)
Info.FFDiag(E, diag::note_constexpr_use_uninit_reference);
+
+ // P2280R4 If we are have a variable that is unknown reference or pointer
+ // try to recover it from the frame and set the result accordingly.
+ if (VD->getType()->isReferenceType() && AllowConstexprUnknown) {
+ if (Frame) {
+ Result.set({VD, Frame->Index, Version});
+ return true;
+ }
+ return Success(VD);
+ }
return false;
}
+
return Success(*V, E);
}
@@ -11486,7 +11550,10 @@ class IntExprEvaluator
}
bool Success(const APValue &V, const Expr *E) {
- if (V.isLValue() || V.isAddrLabelDiff() || V.isIndeterminate()) {
+ // P2280R4 if we have an unknown reference or pointer allow further
+ // evaluation of the value.
+ if (V.isLValue() || V.isAddrLabelDiff() || V.isIndeterminate() ||
+ V.allowConstexprUnknown()) {
Result = V;
return true;
}
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index efb391ba0922d..684a6a242bf71 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -1419,9 +1419,9 @@ namespace ConvertedConstantExpr {
// useless note and instead just point to the non-constant subexpression.
enum class E {
em = m,
- en = n, // expected-error {{not a constant expression}} expected-note {{initializer of 'n' is unknown}}
+ en = n, // cxx23-note {{initializer of 'n' is not a constant expression}} expected-error {{enumerator value is not a constant expression}} cxx11_20-note {{initializer of 'n' is unknown}}
eo = (m + // expected-error {{not a constant expression}}
- n // expected-note {{initializer of 'n' is unknown}}
+ n // cxx23-note {{initializer of 'n' is not a constant expression}} cxx11_20-note {{initializer of 'n' is unknown}}
),
eq = reinterpret_cast<long>((int*)0) // expected-error {{not a constant expression}} expected-note {{reinterpret_cast}}
};
@@ -1961,7 +1961,8 @@ namespace ConstexprConstructorRecovery {
namespace Lifetime {
void f() {
- constexpr int &n = n; // expected-error {{constant expression}} expected-note {{use of reference outside its lifetime}} expected-warning {{not yet bound to a value}}
+ constexpr int &n = n; // expected-error {{constant expression}} cxx23-note {{reference to 'n' is not a constant expression}} cxx23-note {{address of non-static constexpr variable 'n' may differ}} expected-warning {{not yet bound to a value}}
+ // cxx11_20-note at -1 {{use of reference outside its lifetime is not allowed in a constant expression}}
constexpr int m = m; // expected-error {{constant expression}} expected-note {{read of object outside its lifetime}}
}
@@ -2381,15 +2382,15 @@ namespace array_size {
template<typename T> void f1(T t) {
constexpr int k = t.size();
}
- template<typename T> void f2(const T &t) { // expected-note 2{{declared here}}
- constexpr int k = t.size(); // expected-error 2{{constant}} expected-note 2{{function parameter 't' with unknown value cannot be used in a constant expression}}
+ template<typename T> void f2(const T &t) { // cxx11_20-note 2{{declared here}}
+ constexpr int k = t.size(); // cxx11_20-error 2{{constexpr variable 'k' must be initialized by a constant expression}} cxx11_20-note 2{{function parameter 't' with unknown value cannot be used in a constant expression}}
}
template<typename T> void f3(const T &t) {
constexpr int k = T::size();
}
void g(array<3> a) {
f1(a);
- f2(a); // expected-note {{instantiation of}}
+ f2(a); // cxx11_20-note {{in instantiation of function template}}
f3(a);
}
@@ -2398,8 +2399,9 @@ namespace array_size {
};
void h(array_nonstatic<3> a) {
f1(a);
- f2(a); // expected-note {{instantiation of}}
+ f2(a); // cxx11_20-note {{instantiation of}}
}
+ //static_assert(f2(array_size::array<3>{}));
}
namespace flexible_array {
diff --git a/clang/test/SemaCXX/constant-expression-cxx2a.cpp b/clang/test/SemaCXX/constant-expression-cxx2a.cpp
index e4d97dcb73562..3910886a36346 100644
--- a/clang/test/SemaCXX/constant-expression-cxx2a.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx2a.cpp
@@ -308,8 +308,7 @@ namespace TypeId {
static_assert(&B2().ti1 == &typeid(B));
static_assert(&B2().ti2 == &typeid(B2));
extern B2 extern_b2;
- // expected-note at +1 {{typeid applied to object 'extern_b2' whose dynamic type is not constant}}
- static_assert(&typeid(extern_b2) == &typeid(B2)); // expected-error {{constant expression}}
+ static_assert(&typeid(extern_b2) == &typeid(B2));
constexpr B2 b2;
constexpr const B &b1 = b2;
diff --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
new file mode 100644
index 0000000000000..be41eed88c494
--- /dev/null
+++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
@@ -0,0 +1,54 @@
+// RUN: %clang_cc1 -std=c++23 -verify %s
+
+using size_t = decltype(sizeof(0));
+
+namespace std {
+struct type_info {
+ const char* name() const noexcept(true);
+};
+}
+
+template <typename T, size_t N>
+constexpr size_t array_size(T (&)[N]) {
+ return N;
+}
+
+void use_array(int const (&gold_medal_mel)[2]) {
+ constexpr auto gold = array_size(gold_medal_mel); // ok
+}
+
+constexpr auto olympic_mile() {
+ const int ledecky = 1500;
+ return []{ return ledecky; };
+}
+static_assert(olympic_mile()() == 1500); // ok
+
+struct Swim {
+ constexpr int phelps() { return 28; }
+ virtual constexpr int lochte() { return 12; }
+ int coughlin = 12;
+};
+
+constexpr int how_many(Swim& swam) {
+ Swim* p = &swam;
+ return (p + 1 - 1)->phelps();
+}
+
+void splash(Swim& swam) {
+ static_assert(swam.phelps() == 28); // ok
+ static_assert((&swam)->phelps() == 28); // ok
+ Swim* pswam = &swam; // expected-note {{declared here}}
+ static_assert(pswam->phelps() == 28); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{read of non-constexpr variable 'pswam' is not allowed in a constant expression}}
+ static_assert(how_many(swam) == 28); // ok
+ static_assert(Swim().lochte() == 12); // ok
+ static_assert(swam.lochte() == 12); // expected-error {{static assertion expression is not an integral constant expression}}
+ static_assert(swam.coughlin == 12); // expected-error {{static assertion expression is not an integral constant expression}}
+}
+
+extern Swim dc;
+extern Swim& trident; // expected-note {{declared here}}
+
+constexpr auto& sandeno = typeid(dc); // ok: can only be typeid(Swim)
+constexpr auto& gallagher = typeid(trident); // expected-error {{constexpr variable 'gallagher' must be initialized by a constant expression}}
+ // expected-note at -1 {{initializer of 'trident' is not a constant expression}}
>From 3c944c05fded091f9488a9786bc80c57e291b6ed Mon Sep 17 00:00:00 2001
From: Shafik Yaghmour <shafik.yaghmour at intel.com>
Date: Mon, 29 Jul 2024 10:12:34 -0700
Subject: [PATCH 2/5] - Add memory management for APValues we create for
unknown references and pointers - Fix handling for comparing unknown pointers
- Add more tests
---
clang/include/clang/AST/APValue.h | 2 +-
clang/lib/AST/ExprConstant.cpp | 27 +++++++-
.../SemaCXX/constant-expression-p2280r4.cpp | 64 +++++++++++++++++++
3 files changed, 91 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h
index 6352348107a64..16153c166e23c 100644
--- a/clang/include/clang/AST/APValue.h
+++ b/clang/include/clang/AST/APValue.h
@@ -257,7 +257,7 @@ class APValue {
private:
ValueKind Kind;
- bool AllowConstexprUnknown = false;
+ bool AllowConstexprUnknown;
struct ComplexAPSInt {
APSInt Real, Imag;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 9a427d908e4c9..a7e2cc455e65d 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -555,6 +555,7 @@ namespace {
typedef std::map<MapKeyTy, APValue> MapTy;
/// Temporaries - Temporary lvalues materialized within this stack frame.
MapTy Temporaries;
+ MapTy ConstexprUnknownAPValues;
/// CallRange - The source range of the call expression for this call.
SourceRange CallRange;
@@ -629,6 +630,9 @@ namespace {
APValue &createTemporary(const KeyT *Key, QualType T,
ScopeKind Scope, LValue &LV);
+ APValue &createConstexprUnknownAPValues(const VarDecl *Key,
+ APValue::LValueBase Base);
+
/// Allocate storage for a parameter of a function call made in this frame.
APValue &createParam(CallRef Args, const ParmVarDecl *PVD, LValue &LV);
@@ -1925,6 +1929,16 @@ APValue &CallStackFrame::createTemporary(const KeyT *Key, QualType T,
return createLocal(Base, Key, T, Scope);
}
+APValue &
+CallStackFrame::createConstexprUnknownAPValues(const VarDecl *Key,
+ APValue::LValueBase Base) {
+ APValue &Result = ConstexprUnknownAPValues[MapKeyTy(Key, Base.getVersion())];
+ Result = APValue(Base, APValue::ConstexprUnknown{}, CharUnits::One());
+ Result.setConstexprUnknown();
+
+ return Result;
+}
+
/// Allocate storage for a parameter of a function call made in this frame.
APValue &CallStackFrame::createParam(CallRef Args, const ParmVarDecl *PVD,
LValue &LV) {
@@ -3410,6 +3424,10 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
// P2280R4 struck the initialization requirement for variables of reference
// type so we can no longer assume we have an Init.
if (Init && !VD->evaluateValue()) {
+ if (AllowConstexprUnknown) {
+ Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base);
+ return true;
+ }
Info.FFDiag(E, diag::note_constexpr_var_init_non_constant, 1) << VD;
NoteLValueLocation(Info, Base);
return false;
@@ -3447,7 +3465,9 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
// a ConstexprUnknown status.
if (AllowConstexprUnknown) {
if (!Result) {
- Result = new APValue(Base, APValue::ConstexprUnknown{}, CharUnits::One());
+ Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base);
+ } else {
+ Result->setConstexprUnknown();
}
}
return true;
@@ -13633,6 +13653,11 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
if (!EvaluatePointer(E->getRHS(), RHSValue, Info) || !LHSOK)
return false;
+ // If we have Unknown pointers we should fail if they are not global values.
+ if (!(IsGlobalLValue(LHSValue.getLValueBase()) && IsGlobalLValue(RHSValue.getLValueBase())) &&
+ (LHSValue.AllowConstexprUnknown || RHSValue.AllowConstexprUnknown))
+ return false;
+
// Reject differing bases from the normal codepath; we special-case
// comparisons to null.
if (!HasSameBase(LHSValue, RHSValue)) {
diff --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
index be41eed88c494..0a32a9f0d7aca 100644
--- a/clang/test/SemaCXX/constant-expression-p2280r4.cpp
+++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
@@ -52,3 +52,67 @@ extern Swim& trident; // expected-note {{declared here}}
constexpr auto& sandeno = typeid(dc); // ok: can only be typeid(Swim)
constexpr auto& gallagher = typeid(trident); // expected-error {{constexpr variable 'gallagher' must be initialized by a constant expression}}
// expected-note at -1 {{initializer of 'trident' is not a constant expression}}
+
+namespace GH64376 {
+template<int V>
+struct Test {
+ static constexpr int value = V;
+};
+
+int main() {
+ Test<124> test;
+ auto& test2 = test;
+
+ if constexpr(test2.value > 3) {
+ return 1;
+ }
+
+ return 0;
+}
+}
+
+namespace GH30060 {
+template<int V>
+struct A {
+ static constexpr int value = V;
+};
+
+template<class T>
+static void test1(T &f) {
+ A<f.value> bar;
+}
+
+void g() {
+ A<42> f;
+
+ test1(f);
+}
+}
+
+namespace GH26067 {
+struct A {
+ constexpr operator int() const { return 42; }
+};
+
+template <int>
+void f() {}
+
+void test(const A& value) {
+ f<value>();
+}
+
+int main() {
+ A a{};
+ test(a);
+}
+}
+
+namespace GH34365 {
+void g() {
+ auto f = []() { return 42; };
+ constexpr int x = f();
+ [](auto f) { constexpr int x = f(); }(f);
+ [](auto &f) { constexpr int x = f(); }(f);
+ (void)[&]() { constexpr int x = f(); };
+}
+}
>From 0c27e1ec1735487940bdaea7cb547c9b1ddc737d Mon Sep 17 00:00:00 2001
From: Shafik Yaghmour <shafik.yaghmour at intel.com>
Date: Mon, 29 Jul 2024 10:55:46 -0700
Subject: [PATCH 3/5] clang-format
---
clang/lib/AST/ExprConstant.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index a7e2cc455e65d..424df336f20e8 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -13654,9 +13654,10 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
return false;
// If we have Unknown pointers we should fail if they are not global values.
- if (!(IsGlobalLValue(LHSValue.getLValueBase()) && IsGlobalLValue(RHSValue.getLValueBase())) &&
- (LHSValue.AllowConstexprUnknown || RHSValue.AllowConstexprUnknown))
- return false;
+ if (!(IsGlobalLValue(LHSValue.getLValueBase()) &&
+ IsGlobalLValue(RHSValue.getLValueBase())) &&
+ (LHSValue.AllowConstexprUnknown || RHSValue.AllowConstexprUnknown))
+ return false;
// Reject differing bases from the normal codepath; we special-case
// comparisons to null.
>From 88c701fc6577794f60cb6b6841a27aa95cdd9f1c Mon Sep 17 00:00:00 2001
From: Shafik Yaghmour <shafik.yaghmour at intel.com>
Date: Mon, 29 Jul 2024 15:52:12 -0700
Subject: [PATCH 4/5] Fix CheckedHandleSizeof to handle references, before
unknown references and pointers it did not have to handle this case before.
---
clang/lib/AST/ExprConstant.cpp | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 424df336f20e8..2423be6a051de 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -12273,6 +12273,10 @@ static bool determineEndOffset(EvalInfo &Info, SourceLocation ExprLoc,
auto CheckedHandleSizeof = [&](QualType Ty, CharUnits &Result) {
if (Ty.isNull() || Ty->isIncompleteType() || Ty->isFunctionType())
return false;
+
+ if (Ty->isReferenceType())
+ Ty = Ty.getNonReferenceType();
+
return HandleSizeof(Info, ExprLoc, Ty, Result);
};
>From 16c44b48c9a5c41a2de3976b4e23c13e62918da7 Mon Sep 17 00:00:00 2001
From: Shafik Yaghmour <shafik.yaghmour at intel.com>
Date: Tue, 30 Jul 2024 18:39:55 -0700
Subject: [PATCH 5/5] Address comments by making AllowConstexprUnknown to be a
bit-field and updating comments to refer to the standard when possible
---
clang/include/clang/AST/APValue.h | 2 +-
clang/lib/AST/APValue.cpp | 5 ++++-
clang/lib/AST/ExprConstant.cpp | 33 +++++++++++++++++--------------
3 files changed, 23 insertions(+), 17 deletions(-)
diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h
index 16153c166e23c..625d8c2e4c846 100644
--- a/clang/include/clang/AST/APValue.h
+++ b/clang/include/clang/AST/APValue.h
@@ -257,7 +257,7 @@ class APValue {
private:
ValueKind Kind;
- bool AllowConstexprUnknown;
+ bool AllowConstexprUnknown : 1;
struct ComplexAPSInt {
APSInt Real, Imag;
diff --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp
index f7841cb6556de..dbab6041e9e22 100644
--- a/clang/lib/AST/APValue.cpp
+++ b/clang/lib/AST/APValue.cpp
@@ -475,7 +475,10 @@ bool APValue::needsCleanup() const {
void APValue::swap(APValue &RHS) {
std::swap(Kind, RHS.Kind);
std::swap(Data, RHS.Data);
- std::swap(AllowConstexprUnknown, RHS.AllowConstexprUnknown);
+ // We can't use std::swap w/ bit-fields
+ bool tmp = AllowConstexprUnknown;
+ AllowConstexprUnknown = RHS.AllowConstexprUnknown;
+ RHS.AllowConstexprUnknown = tmp;
}
/// Profile the value of an APInt, excluding its bit-width.
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 2423be6a051de..a78269e4b63a6 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -3322,8 +3322,8 @@ static bool HandleLValueComplexElement(EvalInfo &Info, const Expr *E,
static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
const VarDecl *VD, CallStackFrame *Frame,
unsigned Version, APValue *&Result) {
- // P2280R4 If we have a reference type and we are in C++23 allow unknown
- // references and pointers.
+ // C++23 [expr.const]p8 If we have a reference type allow unknown references
+ // and pointers.
bool AllowConstexprUnknown =
Info.getLangOpts().CPlusPlus23 && VD->getType()->isReferenceType();
@@ -3361,8 +3361,9 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
return true;
}
- // P2280R4 struck the restriction that variable of referene type lifetime
+ // P2280R4 struck the restriction that variable of reference type lifetime
// should begin within the evaluation of E
+ // Used to be C++20 [expr.const]p5.12.2:
if (isa<ParmVarDecl>(VD) && !AllowConstexprUnknown) {
// Assume parameters of a potential constant expression are usable in
// constant expressions.
@@ -3387,8 +3388,9 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
// FIXME: We should eventually check whether the variable has a reachable
// initializing declaration.
const Expr *Init = VD->getAnyInitializer(VD);
- // P2280R4 struck the restriction that variable of referene type should have
+ // P2280R4 struck the restriction that variable of reference type should have
// a preceding initialization.
+ // Used to be C++20 [expr.const]p5.12:
if (!Init && !AllowConstexprUnknown) {
// Don't diagnose during potential constant expression checking; an
// initializer might be added later.
@@ -3750,8 +3752,8 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
const FieldDecl *LastField = nullptr;
const FieldDecl *VolatileField = nullptr;
- // P2280R4 If we have an unknown referene or pointer and we don't have a
- // value then bail out.
+ // C++23 [expr.const]p8 If we have an unknown reference or pointers and it
+ // does not have a value then bail out.
if (O->allowConstexprUnknown() && !O->hasValue())
return false;
@@ -5788,7 +5790,7 @@ struct CheckDynamicTypeHandler {
static bool checkDynamicType(EvalInfo &Info, const Expr *E, const LValue &This,
AccessKinds AK, bool Polymorphic) {
// P2280R4 We are not allowed to invoke a virtual function whose dynamic type
- // us constexpr-unknown, so stop early and let this fail later on if we
+ // is constexpr-unknown, so stop early and let this fail later on if we
// attempt to do so.
if (This.allowConstexprUnknown())
return true;
@@ -8663,8 +8665,8 @@ bool LValueExprEvaluator::VisitDeclRefExpr(const DeclRefExpr *E) {
bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
- // P2280R4 if we are in C++23 track if we have an unknown reference or
- // pointer.
+ // C++23 [expr.const]p8 If we have a reference type allow unknown references
+ // and pointers.
bool AllowConstexprUnknown =
Info.getLangOpts().CPlusPlus23 && VD->getType()->isReferenceType();
// If we are within a lambda's call operator, check whether the 'VD' referred
@@ -8736,13 +8738,14 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
if (!V->hasValue()) {
// FIXME: Is it possible for V to be indeterminate here? If so, we should
// adjust the diagnostic to say that.
- // P2280R4 If we are have a variable that is unknown reference or pointer
- // it may not have a value but still be usable later on so do not diagnose.
+ // C++23 [expr.const]p8 If we have a variable that is unknown reference
+ // or pointer it may not have a value but still be usable later on so do not
+ // diagnose.
if (!Info.checkingPotentialConstantExpression() && !AllowConstexprUnknown)
Info.FFDiag(E, diag::note_constexpr_use_uninit_reference);
- // P2280R4 If we are have a variable that is unknown reference or pointer
- // try to recover it from the frame and set the result accordingly.
+ // C++23 [expr.const]p8 If we have a variable that is unknown reference or
+ // pointer try to recover it from the frame and set the result accordingly.
if (VD->getType()->isReferenceType() && AllowConstexprUnknown) {
if (Frame) {
Result.set({VD, Frame->Index, Version});
@@ -11570,8 +11573,8 @@ class IntExprEvaluator
}
bool Success(const APValue &V, const Expr *E) {
- // P2280R4 if we have an unknown reference or pointer allow further
- // evaluation of the value.
+ // C++23 [expr.const]p8 If we have a variable that is unknown reference or
+ // pointer allow further evaluation of the value.
if (V.isLValue() || V.isAddrLabelDiff() || V.isIndeterminate() ||
V.allowConstexprUnknown()) {
Result = V;
More information about the cfe-commits
mailing list