[clang] [clang] Reject using uninitialized reference in constant evaluation (PR #180923)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 11 03:49:26 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Yanzuo Liu (zwuis)
<details>
<summary>Changes</summary>
Evaluating uninitialized reference is undefined behaviour. So rejecting it in constant evaluation. Note that discarded-value expression IS evaluated.
There are diagnostic regressions for bytecode interpreter because [P2280R4](https://wg21.link/p2280r4) is not implemented.
Fixes #<!-- -->157082
AI usage: Changes in Compiler.cpp is generated by AI and modified by me afterwards.
Assisted-by: GPT-5.2
---
Full diff: https://github.com/llvm/llvm-project/pull/180923.diff
7 Files Affected:
- (modified) clang/docs/ReleaseNotes.rst (+1)
- (modified) clang/lib/AST/ByteCode/Compiler.cpp (+14-2)
- (modified) clang/lib/AST/ByteCode/Interp.cpp (+19-9)
- (modified) clang/lib/AST/ExprConstant.cpp (+8-5)
- (modified) clang/test/AST/ByteCode/cxx23.cpp (+1-4)
- (modified) clang/test/SemaCXX/constant-expression-cxx11.cpp (+4-2)
- (modified) clang/test/SemaCXX/constant-expression-p2280r4.cpp (+21-20)
``````````diff
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}} \
``````````
</details>
https://github.com/llvm/llvm-project/pull/180923
More information about the cfe-commits
mailing list