https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/143667
>From 83d2fe6d4fb870e816e6576636864f50586fe37a Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@gmail.com> Date: Mon, 9 Jun 2025 17:22:06 +0200 Subject: [PATCH 1/3] [Clang] Diagnose forming references to nullptr Per [decl.ref], > Because a null pointer value or a pointer past the end of an object does not point to an object, a reference in a well-defined program cannot refer to such things. Note this does not fixes the new bytecode interpreter. Fixes #48665 --- .../include/clang/Basic/DiagnosticASTKinds.td | 9 ++++--- clang/lib/AST/ByteCode/State.h | 1 + clang/lib/AST/ExprConstant.cpp | 26 ++++++++++++++----- .../SemaCXX/constant-expression-cxx14.cpp | 23 +++++++++++++++- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index d2cd86d05d55a..41ecda1cad960 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -174,10 +174,11 @@ 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 + : 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|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|" diff --git a/clang/lib/AST/ByteCode/State.h b/clang/lib/AST/ByteCode/State.h index 9a81fa6b7d220..649b58a4dd164 100644 --- a/clang/lib/AST/ByteCode/State.h +++ b/clang/lib/AST/ByteCode/State.h @@ -35,6 +35,7 @@ enum AccessKinds { AK_Construct, AK_Destroy, AK_IsWithinLifetime, + AK_CheckReferenceInitialization }; /// The order of this enum is important for diagnostics. diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index fa4e10e84de05..c02bf973c2552 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1529,7 +1529,7 @@ CallStackFrame::~CallStackFrame() { static bool isRead(AccessKinds AK) { return AK == AK_Read || AK == AK_ReadObjectRepresentation || - AK == AK_IsWithinLifetime; + AK == AK_IsWithinLifetime || AK == AK_CheckReferenceInitialization; } static bool isModification(AccessKinds AK) { @@ -1540,6 +1540,7 @@ static bool isModification(AccessKinds AK) { case AK_DynamicCast: case AK_TypeId: case AK_IsWithinLifetime: + case AK_CheckReferenceInitialization: return false; case AK_Assign: case AK_Increment: @@ -1558,7 +1559,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 && - AK != AK_IsWithinLifetime; + AK != AK_IsWithinLifetime && AK != AK_CheckReferenceInitialization; } /// Is this kind of axcess valid on an indeterminate object value? @@ -1571,6 +1572,7 @@ static bool isValidIndeterminateAccess(AccessKinds AK) { return false; case AK_IsWithinLifetime: + case AK_CheckReferenceInitialization: case AK_ReadObjectRepresentation: case AK_Assign: case AK_Construct: @@ -4426,7 +4428,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, // Unless we're looking at a local variable or argument in a constexpr call, // the variable we're reading must be const. - if (!Frame) { + if (!Frame && AK != clang::AK_CheckReferenceInitialization) { if (IsAccess && isa<ParmVarDecl>(VD)) { // Access of a parameter that's not associated with a frame isn't going // to work out, but we can leave it to evaluateVarDeclInit to provide a @@ -4503,7 +4505,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } else { const Expr *Base = LVal.Base.dyn_cast<const Expr*>(); - if (!Frame) { + if (!Frame && AK != clang::AK_CheckReferenceInitialization) { if (const MaterializeTemporaryExpr *MTE = dyn_cast_or_null<MaterializeTemporaryExpr>(Base)) { assert(MTE->getStorageDuration() == SD_Static && @@ -4557,7 +4559,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, NoteLValueLocation(Info, LVal.Base); return CompleteObject(); } - } else { + } else if (AK != clang::AK_CheckReferenceInitialization) { BaseVal = Frame->getTemporary(Base, LVal.Base.getVersion()); assert(BaseVal && "missing value for temporary"); } @@ -5243,7 +5245,19 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) { if (InitE->isValueDependent()) return false; - if (!EvaluateInPlace(Val, Info, Result, InitE)) { + if (VD->getType()->isReferenceType() && InitE->isGLValue()) { + if (!EvaluateLValue(InitE, Result, Info)) + return false; + CompleteObject Obj = findCompleteObject( + Info, InitE, AK_CheckReferenceInitialization, Result, InitE->getType()); + if (Result.Designator.isOnePastTheEnd()) { + Info.FFDiag(InitE, diag::note_constexpr_access_past_end) + << AK_CheckReferenceInitialization; + return false; + } + Result.moveInto(Val); + return !!Obj; + } else if (!EvaluateInPlace(Val, Info, Result, InitE)) { // Wipe out any partially-computed value, to allow tracking that this // evaluation failed. Val = APValue(); diff --git a/clang/test/SemaCXX/constant-expression-cxx14.cpp b/clang/test/SemaCXX/constant-expression-cxx14.cpp index e16a69df3830d..d8ebe92131ddc 100644 --- a/clang/test/SemaCXX/constant-expression-cxx14.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx14.cpp @@ -250,7 +250,7 @@ namespace subobject { namespace lifetime { constexpr int &&id(int &&n) { return static_cast<int&&>(n); } constexpr int &&dead() { return id(0); } // expected-note {{temporary created here}} - constexpr int bad() { int &&n = dead(); n = 1; return n; } // expected-note {{assignment to temporary whose lifetime has ended}} + constexpr int bad() { int &&n = dead(); n = 1; return n; } // expected-note {{read of temporary whose lifetime has ended}} static_assert(bad(), ""); // expected-error {{constant expression}} expected-note {{in call}} } @@ -1321,3 +1321,24 @@ constexpr bool check = different_in_loop(); // expected-error@-1 {{}} expected-note@-1 {{in call}} } + +namespace GH48665 { +constexpr bool foo(int *i) { + int &j = *i; + // expected-note@-1 {{read of dereferenced null pointer is not allowed in a constant expression}} + return true; +} + +static_assert(foo(nullptr), ""); // expected-note {{in call to 'foo(nullptr)'}} +// expected-error@-1 {{static assertion expression is not an integral constant expression}} + +int arr[3]; // expected-note 2{{declared here}} +constexpr bool f() { // cxx14_20-error {{constexpr function never produces a constant expression}} + int &r = arr[3]; // cxx14_20-note {{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} \ + // expected-warning {{array index 3 is past the end of the array}}\ + // expected-note {{initializer of 'arr' is unknown}} + return true; +} +static_assert(f(), ""); // expected-note {{in call to 'f()'}} +// expected-error@-1 {{static assertion expression is not an integral constant expression}} +} >From e467b500ff11f07c3be7cb8ad77e62ecf8776dc4 Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@gmail.com> Date: Thu, 12 Jun 2025 14:25:19 +0200 Subject: [PATCH 2/3] Apply feedback, check constructors and aggregates --- .../include/clang/Basic/DiagnosticASTKinds.td | 2 +- clang/lib/AST/ByteCode/State.h | 2 +- clang/lib/AST/ExprConstant.cpp | 83 +++++++++++++------ .../SemaCXX/constant-expression-cxx14.cpp | 37 +++++++-- 4 files changed, 90 insertions(+), 34 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 41ecda1cad960..5ec61dda5d7e0 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -178,7 +178,7 @@ 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|read of}0">; + "destruction of|read of|binding a reference to}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|" diff --git a/clang/lib/AST/ByteCode/State.h b/clang/lib/AST/ByteCode/State.h index 649b58a4dd164..8fc5b0c7e534c 100644 --- a/clang/lib/AST/ByteCode/State.h +++ b/clang/lib/AST/ByteCode/State.h @@ -35,7 +35,7 @@ enum AccessKinds { AK_Construct, AK_Destroy, AK_IsWithinLifetime, - AK_CheckReferenceInitialization + AK_ReferenceInitialization }; /// The order of this enum is important for diagnostics. diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index c02bf973c2552..edaac771cee14 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1529,7 +1529,7 @@ CallStackFrame::~CallStackFrame() { static bool isRead(AccessKinds AK) { return AK == AK_Read || AK == AK_ReadObjectRepresentation || - AK == AK_IsWithinLifetime || AK == AK_CheckReferenceInitialization; + AK == AK_IsWithinLifetime || AK == AK_ReferenceInitialization; } static bool isModification(AccessKinds AK) { @@ -1540,7 +1540,7 @@ static bool isModification(AccessKinds AK) { case AK_DynamicCast: case AK_TypeId: case AK_IsWithinLifetime: - case AK_CheckReferenceInitialization: + case AK_ReferenceInitialization: return false; case AK_Assign: case AK_Increment: @@ -1559,7 +1559,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 && - AK != AK_IsWithinLifetime && AK != AK_CheckReferenceInitialization; + AK != AK_IsWithinLifetime && AK != AK_ReferenceInitialization; } /// Is this kind of axcess valid on an indeterminate object value? @@ -1572,7 +1572,7 @@ static bool isValidIndeterminateAccess(AccessKinds AK) { return false; case AK_IsWithinLifetime: - case AK_CheckReferenceInitialization: + case AK_ReferenceInitialization: case AK_ReadObjectRepresentation: case AK_Assign: case AK_Construct: @@ -4420,6 +4420,9 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, return CompleteObject(); } + // if(AK == clang::AK_ReferenceInitialization) + // return CompleteObject(LVal.getLValueBase(), nullptr, BaseType); + bool IsConstant = BaseType.isConstant(Info.Ctx); bool ConstexprVar = false; if (const auto *VD = dyn_cast_if_present<VarDecl>( @@ -4428,7 +4431,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, // Unless we're looking at a local variable or argument in a constexpr call, // the variable we're reading must be const. - if (!Frame && AK != clang::AK_CheckReferenceInitialization) { + if (AK != clang::AK_ReferenceInitialization && !Frame) { if (IsAccess && isa<ParmVarDecl>(VD)) { // Access of a parameter that's not associated with a frame isn't going // to work out, but we can leave it to evaluateVarDeclInit to provide a @@ -4492,7 +4495,9 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } } - if (!evaluateVarDeclInit(Info, E, VD, Frame, LVal.getLValueVersion(), BaseVal)) + if (AK != clang::AK_ReferenceInitialization && + !evaluateVarDeclInit(Info, E, VD, Frame, LVal.getLValueVersion(), + BaseVal)) return CompleteObject(); } else if (DynamicAllocLValue DA = LVal.Base.dyn_cast<DynamicAllocLValue>()) { std::optional<DynAlloc *> Alloc = Info.lookupDynamicAlloc(DA); @@ -4505,7 +4510,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } else { const Expr *Base = LVal.Base.dyn_cast<const Expr*>(); - if (!Frame && AK != clang::AK_CheckReferenceInitialization) { + if (AK != clang::AK_ReferenceInitialization && !Frame) { if (const MaterializeTemporaryExpr *MTE = dyn_cast_or_null<MaterializeTemporaryExpr>(Base)) { assert(MTE->getStorageDuration() == SD_Static && @@ -4559,7 +4564,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, NoteLValueLocation(Info, LVal.Base); return CompleteObject(); } - } else if (AK != clang::AK_CheckReferenceInitialization) { + } else if (AK != clang::AK_ReferenceInitialization) { BaseVal = Frame->getTemporary(Base, LVal.Base.getVersion()); assert(BaseVal && "missing value for temporary"); } @@ -5225,6 +5230,24 @@ enum EvalStmtResult { }; } +static bool EvaluateInitForDeclOfReferenceType(EvalInfo &Info, + const ValueDecl *D, + const Expr *Init, LValue &Result, + APValue &Val) { + assert(Init->isGLValue() && D->getType()->isReferenceType()); + if (!EvaluateLValue(Init, Result, Info)) + return false; + CompleteObject Obj = findCompleteObject( + Info, Init, AK_ReferenceInitialization, Result, Init->getType()); + if (!Result.Designator.Invalid && Result.Designator.isOnePastTheEnd()) { + Info.FFDiag(Init, diag::note_constexpr_access_past_end) + << AK_ReferenceInitialization; + return false; + } + Result.moveInto(Val); + return !!Obj; +} + static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) { if (VD->isInvalidDecl()) return false; @@ -5245,18 +5268,8 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) { if (InitE->isValueDependent()) return false; - if (VD->getType()->isReferenceType() && InitE->isGLValue()) { - if (!EvaluateLValue(InitE, Result, Info)) - return false; - CompleteObject Obj = findCompleteObject( - Info, InitE, AK_CheckReferenceInitialization, Result, InitE->getType()); - if (Result.Designator.isOnePastTheEnd()) { - Info.FFDiag(InitE, diag::note_constexpr_access_past_end) - << AK_CheckReferenceInitialization; - return false; - } - Result.moveInto(Val); - return !!Obj; + if (VD->getType()->isReferenceType()) { + return EvaluateInitForDeclOfReferenceType(Info, VD, InitE, Result, Val); } else if (!EvaluateInPlace(Val, Info, Result, InitE)) { // Wipe out any partially-computed value, to allow tracking that this // evaluation failed. @@ -6897,9 +6910,18 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This, ThisOverrideRAII ThisOverride(*Info.CurrentCall, &SubobjectParent, isa<CXXDefaultInitExpr>(Init)); FullExpressionRAII InitScope(Info); - if (!EvaluateInPlace(*Value, Info, Subobject, Init) || - (FD && FD->isBitField() && - !truncateBitfieldValue(Info, Init, *Value, FD))) { + + if (FD && FD->getType()->isReferenceType()) { + LValue Result; + if (!EvaluateInitForDeclOfReferenceType(Info, FD, Init, Result, + *Value)) { + if (!Info.noteFailure()) + return false; + Success = false; + } + } else if (!EvaluateInPlace(*Value, Info, Subobject, Init) || + (FD && FD->isBitField() && + !truncateBitfieldValue(Info, Init, *Value, FD))) { // If we're checking for a potential constant expression, evaluate all // initializers even if some of them fail. if (!Info.noteFailure()) @@ -10926,9 +10948,18 @@ bool RecordExprEvaluator::VisitCXXParenListOrInitListExpr( isa<CXXDefaultInitExpr>(Init)); APValue &FieldVal = Result.getStructField(Field->getFieldIndex()); - if (!EvaluateInPlace(FieldVal, Info, Subobject, Init) || - (Field->isBitField() && !truncateBitfieldValue(Info, Init, - FieldVal, Field))) { + + if (Field->getType()->isReferenceType()) { + LValue Result; + if (!EvaluateInitForDeclOfReferenceType(Info, Field, Init, Result, + FieldVal)) { + if (!Info.noteFailure()) + return false; + Success = false; + } + } else if (!EvaluateInPlace(FieldVal, Info, Subobject, Init) || + (Field->isBitField() && + !truncateBitfieldValue(Info, Init, FieldVal, Field))) { if (!Info.noteFailure()) return false; Success = false; diff --git a/clang/test/SemaCXX/constant-expression-cxx14.cpp b/clang/test/SemaCXX/constant-expression-cxx14.cpp index d8ebe92131ddc..57a172ada8adc 100644 --- a/clang/test/SemaCXX/constant-expression-cxx14.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx14.cpp @@ -250,7 +250,7 @@ namespace subobject { namespace lifetime { constexpr int &&id(int &&n) { return static_cast<int&&>(n); } constexpr int &&dead() { return id(0); } // expected-note {{temporary created here}} - constexpr int bad() { int &&n = dead(); n = 1; return n; } // expected-note {{read of temporary whose lifetime has ended}} + constexpr int bad() { int &&n = dead(); n = 1; return n; } // expected-note {{binding a reference to temporary whose lifetime has ended}} static_assert(bad(), ""); // expected-error {{constant expression}} expected-note {{in call}} } @@ -1325,20 +1325,45 @@ constexpr bool check = different_in_loop(); namespace GH48665 { constexpr bool foo(int *i) { int &j = *i; - // expected-note@-1 {{read of dereferenced null pointer is not allowed in a constant expression}} + // expected-note@-1 {{binding a reference to dereferenced null pointer is not allowed in a constant expression}} return true; } static_assert(foo(nullptr), ""); // expected-note {{in call to 'foo(nullptr)'}} // expected-error@-1 {{static assertion expression is not an integral constant expression}} -int arr[3]; // expected-note 2{{declared here}} +int arr[3]; // expected-note {{declared here}} constexpr bool f() { // cxx14_20-error {{constexpr function never produces a constant expression}} - int &r = arr[3]; // cxx14_20-note {{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} \ - // expected-warning {{array index 3 is past the end of the array}}\ - // expected-note {{initializer of 'arr' is unknown}} + int &r = arr[3]; // cxx14_20-note {{binding a reference to dereferenced one-past-the-end pointer is not allowed in a constant expression}} \ + // expected-note {{binding a reference to dereferenced one-past-the-end pointer is not allowed in a constant expression}} \ + // expected-warning {{array index 3 is past the end of the array}} return true; } static_assert(f(), ""); // expected-note {{in call to 'f()'}} // expected-error@-1 {{static assertion expression is not an integral constant expression}} + + +struct Aggregate { + int &r; +}; +constexpr bool test_agg(int *i) { + Aggregate a{*i}; //expected-note {{binding a reference to dereferenced null pointer is not allowed in a constant expression}} + return true; +} +static_assert(test_agg(nullptr), ""); // expected-note {{in call to 'test_agg(nullptr)'}} +// expected-error@-1 {{static assertion expression is not an integral constant expression}} + +struct B { + constexpr B(int *p) : r{*p} {} // expected-note {{binding a reference to dereferenced null pointer is not allowed in a constant expression}} + int &r; +}; + +constexpr bool test_ctr(int *i) { + B b(i); // expected-note {{in call to 'B(nullptr)'}} + return true; +} + +static_assert(test_ctr(nullptr), ""); // expected-note {{in call to 'test_ctr(nullptr)'}} +// expected-error@-1 {{static assertion expression is not an integral constant expression}} + } >From fff79c4caca6a4d253dadba916055c3afcd07a31 Mon Sep 17 00:00:00 2001 From: Corentin Jabot <corentinja...@gmail.com> Date: Fri, 13 Jun 2025 17:31:45 +0200 Subject: [PATCH 3/3] cleanup, add comments --- clang/lib/AST/ExprConstant.cpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index edaac771cee14..6fb74a5d5ec6d 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -4420,9 +4420,6 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, return CompleteObject(); } - // if(AK == clang::AK_ReferenceInitialization) - // return CompleteObject(LVal.getLValueBase(), nullptr, BaseType); - bool IsConstant = BaseType.isConstant(Info.Ctx); bool ConstexprVar = false; if (const auto *VD = dyn_cast_if_present<VarDecl>( @@ -4430,7 +4427,8 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, ConstexprVar = VD->isConstexpr(); // Unless we're looking at a local variable or argument in a constexpr call, - // the variable we're reading must be const. + // the variable we're reading must be const (unless we are binding to a + // reference). if (AK != clang::AK_ReferenceInitialization && !Frame) { if (IsAccess && isa<ParmVarDecl>(VD)) { // Access of a parameter that's not associated with a frame isn't going @@ -4495,6 +4493,8 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } } + // When binding to a reference, the variable does not need to be constexpr + // or have constant initalization. if (AK != clang::AK_ReferenceInitialization && !evaluateVarDeclInit(Info, E, VD, Frame, LVal.getLValueVersion(), BaseVal)) @@ -4507,10 +4507,13 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } return CompleteObject(LVal.Base, &(*Alloc)->Value, LVal.Base.getDynamicAllocType()); - } else { + } + // When binding to a reference, the variable does not need to be + // within its lifetime. + else if (AK != clang::AK_ReferenceInitialization) { const Expr *Base = LVal.Base.dyn_cast<const Expr*>(); - if (AK != clang::AK_ReferenceInitialization && !Frame) { + if (!Frame) { if (const MaterializeTemporaryExpr *MTE = dyn_cast_or_null<MaterializeTemporaryExpr>(Base)) { assert(MTE->getStorageDuration() == SD_Static && @@ -5230,22 +5233,32 @@ enum EvalStmtResult { }; } +/// Evaluates the initializer of a reference. static bool EvaluateInitForDeclOfReferenceType(EvalInfo &Info, const ValueDecl *D, const Expr *Init, LValue &Result, APValue &Val) { assert(Init->isGLValue() && D->getType()->isReferenceType()); + // A reference is an lvalue if (!EvaluateLValue(Init, Result, Info)) return false; + // [C++26][decl.ref] + // The object designated by such a glvalue can be outside its lifetime + // Because a null pointer value or a pointer past the end of an object + // does not point to an object, a reference in a well-defined program cannot + // refer to such things; CompleteObject Obj = findCompleteObject( Info, Init, AK_ReferenceInitialization, Result, Init->getType()); + if (!Obj) + return false; if (!Result.Designator.Invalid && Result.Designator.isOnePastTheEnd()) { Info.FFDiag(Init, diag::note_constexpr_access_past_end) << AK_ReferenceInitialization; return false; } + // save the result Result.moveInto(Val); - return !!Obj; + return true; } static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits