[clang] [clang] Reject using uninitialized reference in constant evaluation (PR #180923)
Yanzuo Liu via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 11 08:04:09 PST 2026
https://github.com/zwuis updated https://github.com/llvm/llvm-project/pull/180923
>From 9882e16386c3b96257c36d2530718dd49ad3174c Mon Sep 17 00:00:00 2001
From: Yanzuo Liu <zwuis at outlook.com>
Date: Wed, 11 Feb 2026 19:32:35 +0800
Subject: [PATCH 1/2] Reject using uninitialized reference in constant
evaluation
---
clang/docs/ReleaseNotes.rst | 1 +
clang/lib/AST/ByteCode/Compiler.cpp | 16 +++++++-
clang/lib/AST/ByteCode/Interp.cpp | 28 +++++++++----
clang/lib/AST/ExprConstant.cpp | 13 +++---
clang/test/AST/ByteCode/cxx23.cpp | 5 +--
.../SemaCXX/constant-expression-cxx11.cpp | 6 ++-
.../SemaCXX/constant-expression-p2280r4.cpp | 41 ++++++++++---------
7 files changed, 68 insertions(+), 42 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 59fdbc80e8bed..047f2c3fa3d97 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -263,6 +263,7 @@ Bug Fixes to C++ Support
- Fixed a crash when instantiating ``requires`` expressions involving substitution failures in C++ concepts. (#GH176402)
- Fixed a crash when a default argument is passed to an explicit object parameter. (#GH176639)
- Fixed a crash when diagnosing an invalid static member function with an explicit object parameter (#GH177741)
+- Using uninitialized reference in constant evaluation is now correctly rejected.
Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 7aa7a8d75c8e1..f0162a87d6729 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7150,8 +7150,20 @@ bool Compiler<Emitter>::VisitVectorUnaryOperator(const UnaryOperator *E) {
template <class Emitter>
bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, const Expr *E) {
- if (DiscardResult)
- return true;
+ if (DiscardResult) {
+ if (!(isa<VarDecl>(D) && D->getType()->isReferenceType()))
+ return true;
+
+ // C++26 [dcl.ref]p6
+ // ... The behavior of an evaluation of a reference that does not happen
+ // after the initialization of the reference is undefined.
+ //
+ // Visit the variable to check if it is initialized.
+ llvm::SaveAndRestore _(DiscardResult, /*NewValue=*/false);
+ if (!this->visitDeclRef(D, E))
+ return false;
+ return this->emitPopPtr(E);
+ }
if (const auto *ECD = dyn_cast<EnumConstantDecl>(D))
return this->emitConst(ECD->getInitVal(), E);
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index d6939c023f43b..c2bdccedad3a1 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -703,17 +703,19 @@ bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern,
if (Extern && S.checkingPotentialConstantExpression())
return false;
- if (const auto *VD = Desc->asVarDecl();
- VD && (VD->isConstexpr() || VD->hasGlobalStorage())) {
+ const auto *VD = Desc->asVarDecl();
- if (VD == S.EvaluatingDecl &&
- !(S.getLangOpts().CPlusPlus23 && VD->getType()->isReferenceType())) {
+ if (VD && (VD->isConstexpr() || VD->hasGlobalStorage())) {
+
+ if (VD == S.EvaluatingDecl) {
if (!S.getLangOpts().CPlusPlus14 &&
!VD->getType().isConstant(S.getASTContext())) {
// Diagnose as non-const read.
diagnoseNonConstVariable(S, OpPC, VD);
+ } else if (const SourceInfo &Loc = S.Current->getSource(OpPC);
+ VD->getType()->isReferenceType()) {
+ S.FFDiag(Loc, diag::note_constexpr_use_uninit_reference);
} else {
- const SourceInfo &Loc = S.Current->getSource(OpPC);
// Diagnose as "read of object outside its lifetime".
S.FFDiag(Loc, diag::note_constexpr_access_uninit)
<< AK << /*IsIndeterminate=*/false;
@@ -723,8 +725,12 @@ bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern,
if (VD->getAnyInitializer()) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
- S.FFDiag(Loc, diag::note_constexpr_var_init_non_constant, 1) << VD;
- S.Note(VD->getLocation(), diag::note_declared_at);
+ if (VD->getType()->isReferenceType()) {
+ S.FFDiag(Loc, diag::note_constexpr_use_uninit_reference);
+ } else {
+ S.FFDiag(Loc, diag::note_constexpr_var_init_non_constant, 1) << VD;
+ S.Note(VD->getLocation(), diag::note_declared_at);
+ }
} else {
diagnoseMissingInitializer(S, OpPC, VD);
}
@@ -732,8 +738,12 @@ bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern,
}
if (!S.checkingPotentialConstantExpression()) {
- S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_uninit)
- << AK << /*uninitialized=*/true << S.Current->getRange(OpPC);
+ const SourceInfo &Loc = S.Current->getSource(OpPC);
+ if (VD && VD->getType()->isReferenceType())
+ S.FFDiag(Loc, diag::note_constexpr_use_uninit_reference);
+ else
+ S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_uninit)
+ << AK << /*uninitialized=*/true << S.Current->getRange(OpPC);
}
return false;
}
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 44629b8bae194..ef1d106bb0a4c 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -3335,7 +3335,10 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
APValue::LValueBase Base(VD, Frame ? Frame->Index : 0, Version);
- auto CheckUninitReference = [&](bool IsLocalVariable) {
+ // C++26 [dcl.ref]p6
+ // ... The behavior of an evaluation of a reference that does not happen after
+ // the initialization of the reference is undefined.
+ auto CheckUnknownReference = [&](bool UnknownRefIsUninit) {
if (!Result || (!Result->hasValue() && VD->getType()->isReferenceType())) {
// C++23 [expr.const]p8
// ... For such an object that is not usable in constant expressions, the
@@ -3347,7 +3350,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
//
// Variables that are part of the current evaluation are not
// constexpr-unknown.
- if (!AllowConstexprUnknown || IsLocalVariable) {
+ if (!AllowConstexprUnknown || UnknownRefIsUninit) {
if (!Info.checkingPotentialConstantExpression())
Info.FFDiag(E, diag::note_constexpr_use_uninit_reference);
return false;
@@ -3361,7 +3364,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
if (Frame) {
Result = Frame->getTemporary(VD, Version);
if (Result)
- return CheckUninitReference(/*IsLocalVariable=*/true);
+ return CheckUnknownReference(/*UnknownRefIsUninit=*/true);
if (!isa<ParmVarDecl>(VD)) {
// Assume variables referenced within a lambda's call operator that were
@@ -3386,7 +3389,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
// in-flight value.
if (Info.EvaluatingDecl == Base) {
Result = Info.EvaluatingDeclValue;
- return CheckUninitReference(/*IsLocalVariable=*/false);
+ return CheckUnknownReference(/*UnknownRefIsUninit=*/true);
}
// P2280R4 struck the restriction that variable of reference type lifetime
@@ -3505,7 +3508,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
if (!Result && !AllowConstexprUnknown)
return false;
- return CheckUninitReference(/*IsLocalVariable=*/false);
+ return CheckUnknownReference(/*UnknownRefIsUninit=*/false);
}
/// Get the base index of the given base class within an APValue representing
diff --git a/clang/test/AST/ByteCode/cxx23.cpp b/clang/test/AST/ByteCode/cxx23.cpp
index e3be7887c357e..6a0a92aaa8189 100644
--- a/clang/test/AST/ByteCode/cxx23.cpp
+++ b/clang/test/AST/ByteCode/cxx23.cpp
@@ -90,11 +90,8 @@ namespace ThreadLocalStore {
void store() { a = 42; }
}
-#if __cplusplus >= 202302L
constexpr int &b = b; // all-error {{must be initialized by a constant expression}} \
- // all-note {{initializer of 'b' is not a constant expression}} \
- // all-note {{declared here}}
-#endif
+ // all-note {{use of reference outside its lifetime is not allowed in a constant expression}}
namespace StaticLambdas {
constexpr auto static_capture_constexpr() {
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index 91c4ff1cb520d..d992ec5b24ae4 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -2012,8 +2012,7 @@ namespace ConstexprConstructorRecovery {
namespace Lifetime {
void f() {
- 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 &n = n; // expected-error {{constant expression}} expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} expected-warning {{not yet bound to a value}}
constexpr int m = m; // expected-error {{constant expression}} expected-note {{read of object outside its lifetime}}
}
@@ -2074,6 +2073,9 @@ namespace Lifetime {
void rf() {
constexpr R r; // expected-error {{constant expression}} expected-note {{in call}}
}
+
+ constexpr int k5 = 0;
+ constexpr const int &ref = (ref, k5); // expected-error {{constant expression}} expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
}
namespace Bitfields {
diff --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
index 7c8e3e975f091..c6097d2ef897a 100644
--- a/clang/test/SemaCXX/constant-expression-p2280r4.cpp
+++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
@@ -161,25 +161,30 @@ int g() {
namespace GH128409 {
int &ff();
- int &x = ff(); // expected-note {{declared here}}
+ int &x = ff(); // nointerpreter-note {{declared here}}
constinit int &z = x; // expected-error {{variable does not have a constant initializer}} \
// expected-note {{required by 'constinit' specifier here}} \
- // expected-note {{initializer of 'x' is not a constant expression}}
+ // nointerpreter-note {{initializer of 'x' is not a constant expression}} \
+ // interpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}}
}
namespace GH129845 {
int &ff();
- int &x = ff(); // expected-note {{declared here}}
+ int &x = ff(); // nointerpreter-note {{declared here}}
struct A { int& x; };
constexpr A g = {x}; // expected-error {{constexpr variable 'g' must be initialized by a constant expression}} \
- // expected-note {{initializer of 'x' is not a constant expression}}
+ // nointerpreter-note {{initializer of 'x' is not a constant expression}} \
+ // interpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}}
const A* gg = &g;
}
namespace extern_reference_used_as_unknown {
- extern int &x;
+ extern int &x; // interpreter-note {{declared here}}
int y;
- constinit int& g = (x,y); // expected-warning {{left operand of comma operator has no effect}}
+ constinit int& g = (x,y); // expected-warning {{left operand of comma operator has no effect}} \
+ // interpreter-error {{variable does not have a constant initializer}} \
+ // interpreter-note {{required by 'constinit' specifier here}} \
+ // interpreter-note {{initializer of 'x' is unknown}}
}
namespace GH139452 {
@@ -206,21 +211,19 @@ int f() {
namespace uninit_reference_used {
int y;
constexpr int &r = r; // expected-error {{must be initialized by a constant expression}} \
- // expected-note {{initializer of 'r' is not a constant expression}} \
- // expected-note {{declared here}}
- constexpr int &rr = (rr, y);
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
+ constexpr int &rr = (rr, y); // expected-error {{constexpr variable 'rr' must be initialized by a constant expression}} \
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
constexpr int &g() {
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
- // interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
return x;
}
constexpr int &gg = g(); // expected-error {{must be initialized by a constant expression}} \
// expected-note {{in call to 'g()'}}
constexpr int g2() {
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
- // interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
return x;
}
constexpr int gg2 = g2(); // expected-error {{must be initialized by a constant expression}} \
@@ -228,16 +231,15 @@ namespace uninit_reference_used {
constexpr int &g3() {
int &x = (x,y); // expected-warning{{left operand of comma operator has no effect}} \
// expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}}
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
return x;
}
- constexpr int &gg3 = g3(); // nointerpreter-error {{must be initialized by a constant expression}} \
- // nointerpreter-note {{in call to 'g3()'}}
+ constexpr int &gg3 = g3(); // expected-error {{must be initialized by a constant expression}} \
+ // expected-note {{in call to 'g3()'}}
typedef decltype(sizeof(1)) uintptr_t;
constexpr uintptr_t g4() {
uintptr_t * &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
- // interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
*(uintptr_t*)x = 10;
return 3;
}
@@ -245,8 +247,7 @@ namespace uninit_reference_used {
// expected-note {{in call to 'g4()'}}
constexpr int g5() {
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
- // interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
return 3;
}
constexpr uintptr_t gg5 = g5(); // expected-error {{must be initialized by a constant expression}} \
>From 7b36d3c1f2f210cc5a523020b1c38ccc6df7299f Mon Sep 17 00:00:00 2001
From: Yanzuo Liu <zwuis at outlook.com>
Date: Wed, 11 Feb 2026 23:41:58 +0800
Subject: [PATCH 2/2] Remove backslashes
---
clang/test/SemaCXX/constant-expression-p2280r4.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
index c6097d2ef897a..c86d5ee10ac78 100644
--- a/clang/test/SemaCXX/constant-expression-p2280r4.cpp
+++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
@@ -216,14 +216,14 @@ namespace uninit_reference_used {
// expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
constexpr int &g() {
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
return x;
}
constexpr int &gg = g(); // expected-error {{must be initialized by a constant expression}} \
// expected-note {{in call to 'g()'}}
constexpr int g2() {
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
return x;
}
constexpr int gg2 = g2(); // expected-error {{must be initialized by a constant expression}} \
@@ -247,7 +247,7 @@ namespace uninit_reference_used {
// expected-note {{in call to 'g4()'}}
constexpr int g5() {
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
return 3;
}
constexpr uintptr_t gg5 = g5(); // expected-error {{must be initialized by a constant expression}} \
More information about the cfe-commits
mailing list