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.yaghm...@intel.com> Date: Thu, 13 Jun 2024 14:20:50 -0700 Subject: [PATCH 01/10] [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 c4206b73b11562..6352348107a647 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 d8e33ff421c06c..f7841cb6556de7 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 7178f081d9cf35..9a427d908e4c91 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 efb391ba0922d8..684a6a242bf714 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@-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 e4d97dcb73562d..3910886a36346a 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@+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 00000000000000..be41eed88c4944 --- /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@-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@-1 {{initializer of 'trident' is not a constant expression}} >From 3c944c05fded091f9488a9786bc80c57e291b6ed Mon Sep 17 00:00:00 2001 From: Shafik Yaghmour <shafik.yaghm...@intel.com> Date: Mon, 29 Jul 2024 10:12:34 -0700 Subject: [PATCH 02/10] - 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 6352348107a647..16153c166e23c8 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 9a427d908e4c91..a7e2cc455e65d2 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 be41eed88c4944..0a32a9f0d7aca8 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@-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.yaghm...@intel.com> Date: Mon, 29 Jul 2024 10:55:46 -0700 Subject: [PATCH 03/10] 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 a7e2cc455e65d2..424df336f20e83 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.yaghm...@intel.com> Date: Mon, 29 Jul 2024 15:52:12 -0700 Subject: [PATCH 04/10] 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 424df336f20e83..2423be6a051de6 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.yaghm...@intel.com> Date: Tue, 30 Jul 2024 18:39:55 -0700 Subject: [PATCH 05/10] 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 16153c166e23c8..625d8c2e4c846b 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 f7841cb6556de7..dbab6041e9e226 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 2423be6a051de6..a78269e4b63a60 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; >From 8f9977160ab25de15c028ec068b77781d3749fb2 Mon Sep 17 00:00:00 2001 From: Shafik Yaghmour <shafik.yaghm...@intel.com> Date: Wed, 18 Dec 2024 17:32:12 -0800 Subject: [PATCH 06/10] Fixing typos in merge conflict resolution --- clang/include/clang/AST/APValue.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h index 0d58fa3ef202ec..66058acaef79d2 100644 --- a/clang/include/clang/AST/APValue.h +++ b/clang/include/clang/AST/APValue.h @@ -356,7 +356,7 @@ class APValue { APValue(LValueBase Base, const CharUnits &Offset, NoLValuePath, bool IsNullPtr = false) : Kind(None), AllowConstexprUnknown(false) { - MakeLValue(); setLValue(B, O, N, IsNullPtr); + MakeLValue(); setLValue(Base, Offset, NoLValuePath{}, IsNullPtr); } /// Creates an lvalue APValue with an lvalue path. /// \param Base The base of the lvalue. >From 1e2fdbd3e178325f5e8e0608107648413f6b5643 Mon Sep 17 00:00:00 2001 From: Shafik Yaghmour <shafik.yaghm...@intel.com> Date: Wed, 18 Dec 2024 17:51:08 -0800 Subject: [PATCH 07/10] Fix clang-format issues --- clang/include/clang/AST/APValue.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h index 66058acaef79d2..5eb2f07ff8336c 100644 --- a/clang/include/clang/AST/APValue.h +++ b/clang/include/clang/AST/APValue.h @@ -356,7 +356,8 @@ class APValue { APValue(LValueBase Base, const CharUnits &Offset, NoLValuePath, bool IsNullPtr = false) : Kind(None), AllowConstexprUnknown(false) { - MakeLValue(); setLValue(Base, Offset, NoLValuePath{}, IsNullPtr); + MakeLValue(); + setLValue(Base, Offset, NoLValuePath{}, IsNullPtr); } /// Creates an lvalue APValue with an lvalue path. /// \param Base The base of the lvalue. >From d1febe81dd74d9621d99cca1c9c954559c24eb66 Mon Sep 17 00:00:00 2001 From: Shafik Yaghmour <shafik.yaghm...@intel.com> Date: Wed, 18 Dec 2024 17:58:31 -0800 Subject: [PATCH 08/10] More clang-format fixes --- clang/include/clang/AST/APValue.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h index 5eb2f07ff8336c..8bb8012909c27c 100644 --- a/clang/include/clang/AST/APValue.h +++ b/clang/include/clang/AST/APValue.h @@ -369,7 +369,8 @@ class APValue { APValue(LValueBase B, const CharUnits &O, ArrayRef<LValuePathEntry> Path, bool OnePastTheEnd, bool IsNullPtr = false) : Kind(None), AllowConstexprUnknown(false) { - MakeLValue(); setLValue(B, O, Path, OnePastTheEnd, IsNullPtr); + MakeLValue(); + setLValue(B, O, Path, OnePastTheEnd, IsNullPtr); } APValue(LValueBase B, ConstexprUnknown, const CharUnits &O, @@ -392,7 +393,8 @@ class APValue { /// \param UninitStruct Marker. Pass an empty UninitStruct. /// \param NumBases Number of bases. /// \param NumMembers Number of members. - APValue(UninitStruct, unsigned NumBases, unsigned NumMembers) : Kind(None), AllowConstexprUnknown(false) { + APValue(UninitStruct, unsigned NumBases, unsigned NumMembers) + : Kind(None), AllowConstexprUnknown(false) { MakeStruct(NumBases, NumMembers); } /// Creates a new union APValue. >From 9dd79589acef2e4e531528e6327ce2d27e35d5fa Mon Sep 17 00:00:00 2001 From: Shafik Yaghmour <shafik.yaghm...@intel.com> Date: Thu, 19 Dec 2024 09:45:20 -0800 Subject: [PATCH 09/10] Fix for EvaluateBuiltinIsWithinLifetime when dealing wint constexpr unknown variables --- clang/lib/AST/ExprConstant.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 304fc170974db1..4431468950b0b0 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -17934,6 +17934,9 @@ std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, if (!EvaluatePointer(Arg, Val, Info)) return std::nullopt; + if (Val.allowConstexprUnknown()) + return true; + auto Error = [&](int Diag) { bool CalledFromStd = false; const auto *Callee = Info.CurrentCall->getCallee(); >From e4225df2ffe65cc09c2609759051bcdd4f831291 Mon Sep 17 00:00:00 2001 From: Shafik Yaghmour <shafik.yaghm...@intel.com> Date: Thu, 19 Dec 2024 17:09:38 -0800 Subject: [PATCH 10/10] Adding some more tests, did some minor documentation fixes for APValue --- clang/include/clang/AST/APValue.h | 16 +++++--- clang/lib/AST/ExprConstant.cpp | 2 +- .../SemaCXX/constant-expression-p2280r4.cpp | 38 +++++++++++++++++++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h index 8bb8012909c27c..6d7c214e2eb44f 100644 --- a/clang/include/clang/AST/APValue.h +++ b/clang/include/clang/AST/APValue.h @@ -366,18 +366,22 @@ class APValue { /// \param OnePastTheEnd Whether this lvalue is one-past-the-end of the /// subobject it points to. /// \param IsNullPtr Whether this lvalue is a null pointer. - APValue(LValueBase B, const CharUnits &O, ArrayRef<LValuePathEntry> Path, - bool OnePastTheEnd, bool IsNullPtr = false) + APValue(LValueBase Base, const CharUnits &Offset, + ArrayRef<LValuePathEntry> Path, bool OnePastTheEnd, + bool IsNullPtr = false) : Kind(None), AllowConstexprUnknown(false) { MakeLValue(); - setLValue(B, O, Path, OnePastTheEnd, IsNullPtr); + setLValue(Base, Offset, Path, OnePastTheEnd, IsNullPtr); } - - APValue(LValueBase B, ConstexprUnknown, const CharUnits &O, + /// Creates a constexpr unknown lvalue APValue. + /// \param Base The base of the lvalue. + /// \param Offset The offset of the lvalue. + /// \param IsNullPtr Whether this lvalue is a null pointer. + APValue(LValueBase Base, const CharUnits &Offset, ConstexprUnknown, bool IsNullPtr = false) : Kind(None), AllowConstexprUnknown(true) { MakeLValue(); - setLValue(B, O, NoLValuePath{}, IsNullPtr); + setLValue(Base, Offset, NoLValuePath{}, IsNullPtr); } /// Creates a new array APValue. diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 4431468950b0b0..a4adc15fd7dac9 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1960,7 +1960,7 @@ APValue & CallStackFrame::createConstexprUnknownAPValues(const VarDecl *Key, APValue::LValueBase Base) { APValue &Result = ConstexprUnknownAPValues[MapKeyTy(Key, Base.getVersion())]; - Result = APValue(Base, APValue::ConstexprUnknown{}, CharUnits::One()); + Result = APValue(Base, CharUnits::One(), APValue::ConstexprUnknown{}); Result.setConstexprUnknown(); return Result; diff --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp b/clang/test/SemaCXX/constant-expression-p2280r4.cpp index 0a32a9f0d7aca8..0f85c60629eed9 100644 --- a/clang/test/SemaCXX/constant-expression-p2280r4.cpp +++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp @@ -53,6 +53,26 @@ 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@-1 {{initializer of 'trident' is not a constant expression}} +namespace explicitThis { +struct C { + constexpr int b() { return 0; }; + + constexpr int f(this C &c) { + return c.b(); // ok + } + + constexpr int g() { + return f(); // ok + } +}; + +void g() { + C c; + constexpr int x = c.f(); + constexpr int y = c.g(); +} +} + namespace GH64376 { template<int V> struct Test { @@ -116,3 +136,21 @@ void g() { (void)[&]() { constexpr int x = f(); }; } } + +namespace GH118063 { +template <unsigned int N> +struct array { + constexpr auto size() const -> unsigned int { + return N; + } +}; + +constexpr auto f(array<5> const& arr) { + return array<arr.size()>{}.size(); +} + +int g() { + array<5> arr {}; + static_assert(f(arr) == 5); +} +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits