[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