[clang] [Clang] Diagnose forming references to nullptr (PR #143667)
Corentin Jabot via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 25 03:05:25 PDT 2025
https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/143667
>From 39b9a098f086958e4f71cce3126b27168c74e0e2 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 9 Jun 2025 17:22:06 +0200
Subject: [PATCH 1/6] [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 bf9208763b1ab..b673cdab8cbe5 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:
@@ -4425,7 +4427,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
@@ -4502,7 +4504,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 &&
@@ -4556,7 +4558,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");
}
@@ -5242,7 +5244,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 at -1 {{}} expected-note at -1 {{in call}}
}
+
+namespace GH48665 {
+constexpr bool foo(int *i) {
+ int &j = *i;
+ // expected-note at -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 at -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 at -1 {{static assertion expression is not an integral constant expression}}
+}
>From 5a3c5f059ee2be6cdbd3d52673cefa13f14f1a81 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 12 Jun 2025 14:25:19 +0200
Subject: [PATCH 2/6] 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 b673cdab8cbe5..ba475c5e6aea6 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:
@@ -4419,6 +4419,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>(
@@ -4427,7 +4430,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
@@ -4491,7 +4494,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);
@@ -4504,7 +4509,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 &&
@@ -4558,7 +4563,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");
}
@@ -5224,6 +5229,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;
@@ -5244,18 +5267,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 at -1 {{read of dereferenced null pointer is not allowed in a constant expression}}
+ // expected-note at -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 at -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 at -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 at -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 at -1 {{static assertion expression is not an integral constant expression}}
+
}
>From 4cd55ea29219300d97c0edb281e5c3ebe62f7f8a Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 13 Jun 2025 17:31:45 +0200
Subject: [PATCH 3/6] 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 ba475c5e6aea6..a3bc245733e9b 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -4419,9 +4419,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>(
@@ -4429,7 +4426,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
@@ -4494,6 +4492,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))
@@ -4506,10 +4506,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 &&
@@ -5229,22 +5232,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) {
>From 10241fe6d5ee32cb1601cce2c73bf3198e1009ad Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 13 Jun 2025 17:39:26 +0200
Subject: [PATCH 4/6] changelog
---
clang/docs/ReleaseNotes.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index e1fe22393eebb..de5c0ce5198d7 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -872,6 +872,7 @@ Bug Fixes to C++ Support
- Fixed a crash when constant evaluating some explicit object member assignment operators. (#GH142835)
- Fixed an access checking bug when substituting into concepts (#GH115838)
- Fix a bug where private access specifier of overloaded function not respected. (#GH107629)
+- Diagnose binding a reference to `*nullptr` during constant evaluation. (#GH48665)
Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
>From ddc97193856b448c65e07e63b5a4b370bb1a5022 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 13 Jun 2025 20:26:40 +0200
Subject: [PATCH 5/6] Oh, rST
---
clang/docs/ReleaseNotes.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index de5c0ce5198d7..2808a539639d6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -872,7 +872,7 @@ Bug Fixes to C++ Support
- Fixed a crash when constant evaluating some explicit object member assignment operators. (#GH142835)
- Fixed an access checking bug when substituting into concepts (#GH115838)
- Fix a bug where private access specifier of overloaded function not respected. (#GH107629)
-- Diagnose binding a reference to `*nullptr` during constant evaluation. (#GH48665)
+- Diagnose binding a reference to ``*nullptr`` during constant evaluation. (#GH48665)
Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
>From b36d72808958fbfd61d35f70576c38696b2be44b Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 13 Jun 2025 21:04:17 +0200
Subject: [PATCH 6/6] add rvalue tests
---
clang/test/SemaCXX/constant-expression-cxx14.cpp | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/clang/test/SemaCXX/constant-expression-cxx14.cpp b/clang/test/SemaCXX/constant-expression-cxx14.cpp
index 57a172ada8adc..80420ec40fe82 100644
--- a/clang/test/SemaCXX/constant-expression-cxx14.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx14.cpp
@@ -1332,6 +1332,14 @@ constexpr bool foo(int *i) {
static_assert(foo(nullptr), ""); // expected-note {{in call to 'foo(nullptr)'}}
// expected-error at -1 {{static assertion expression is not an integral constant expression}}
+constexpr bool foo_rvalue(int *i) {
+ int &&j = (int&&)*i;
+ // expected-note at -1 {{binding a reference to dereferenced null pointer is not allowed in a constant expression}}
+ return true;
+}
+static_assert(foo_rvalue(nullptr), ""); // expected-note {{in call to 'foo_rvalue(nullptr)'}}
+// expected-error at -1 {{static assertion expression is not an integral constant expression}}
+
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 {{binding a reference to dereferenced one-past-the-end pointer is not allowed in a constant expression}} \
More information about the cfe-commits
mailing list