llvmbot wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Eli Friedman (efriedma-quic)

<details>
<summary>Changes</summary>

Usually, in constant evaluation, references which are local to the evaluation 
have to be initialized before they're accessed.  However, there's one funny 
special case: the initializer of a reference can refer to itself.  This 
generally ends up being undefined behavior if it's used in an evaluated 
context, but it isn't othewise forbidden.

In constant evaluation, this splits into two cases: global variables, and local 
variables in constexpr functions.  This patch handles both of those cases.  
(Local variables tends to trip other errors in most cases, but if you try hard 
enough, you can get an accepts-invalid.)

I'm not sure what's correct for g5(): this patch and gcc accept, but 
-fexperimental-new-constant-interpreter rejects.

Fixes #<!-- -->131330 .

---
Full diff: https://github.com/llvm/llvm-project/pull/132990.diff


2 Files Affected:

- (modified) clang/lib/AST/ExprConstant.cpp (+30-39) 
- (modified) clang/test/SemaCXX/constant-expression-p2280r4.cpp (+48-1) 


``````````diff
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 0165a0a3b0df3..71c8b9a1a5f37 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -3485,11 +3485,27 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const 
Expr *E,
 
   APValue::LValueBase Base(VD, Frame ? Frame->Index : 0, Version);
 
+  auto CheckUninitReference = [&] {
+    if (!Result->hasValue() && VD->getType()->isReferenceType()) {
+      // We can't use an uninitialized reference directly.
+      if (!AllowConstexprUnknown) {
+        if (!Info.checkingPotentialConstantExpression())
+          Info.FFDiag(E, diag::note_constexpr_use_uninit_reference);
+        return false;
+      }
+      Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base);
+    }
+    return true;
+  };
+
   // If this is a local variable, dig out its value.
   if (Frame) {
     Result = Frame->getTemporary(VD, Version);
-    if (Result)
+    if (Result) {
+      if (!CheckUninitReference())
+        return false;
       return true;
+    }
 
     if (!isa<ParmVarDecl>(VD)) {
       // Assume variables referenced within a lambda's call operator that were
@@ -3514,6 +3530,9 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const 
Expr *E,
   // in-flight value.
   if (Info.EvaluatingDecl == Base) {
     Result = Info.EvaluatingDeclValue;
+    if (!CheckUninitReference())
+      return false;
+
     return true;
   }
 
@@ -3587,11 +3606,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const 
Expr *E,
   // type so we can no longer assume we have an Init.
   // Used to be C++20 [expr.const]p5.12:
   //  ... reference has a preceding initialization and either ...
-  if (Init && !VD->evaluateValue()) {
-    if (AllowConstexprUnknown) {
-      Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base);
-      return true;
-    }
+  if (Init && !VD->evaluateValue() && !AllowConstexprUnknown) {
     Info.FFDiag(E, diag::note_constexpr_var_init_non_constant, 1) << VD;
     NoteLValueLocation(Info, Base);
     return false;
@@ -3629,17 +3644,17 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const 
Expr *E,
 
   Result = VD->getEvaluatedValue();
 
-  // C++23 [expr.const]p8
-  // ... For such an object that is not usable in constant expressions, the
-  // dynamic type of the object is constexpr-unknown. For such a reference that
-  // is not usable in constant expressions, the reference is treated as binding
-  // to an unspecified object of the referenced type whose lifetime and that of
-  // all subobjects includes the entire constant evaluation and whose dynamic
-  // type is constexpr-unknown.
-  if (AllowConstexprUnknown) {
-    if (!Result)
+  if (!Result) {
+    if (AllowConstexprUnknown) {
       Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base);
+    } else {
+      return false;
+    }
   }
+
+  if (!CheckUninitReference())
+    return false;
+
   return true;
 }
 
@@ -8931,10 +8946,6 @@ bool LValueExprEvaluator::VisitDeclRefExpr(const 
DeclRefExpr *E) {
 
 
 bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
-  // C++23 [expr.const]p8 If we have a reference type allow unknown references
-  // and pointers.
-  bool AllowConstexprUnknown =
-      Info.getLangOpts().CPlusPlus23 && VD->getType()->isReferenceType();
   // If we are within a lambda's call operator, check whether the 'VD' referred
   // to within 'E' actually represents a lambda-capture that maps to a
   // data-member/field within the closure object, and if so, evaluate to the
@@ -9001,26 +9012,6 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, 
const VarDecl *VD) {
   APValue *V;
   if (!evaluateVarDeclInit(Info, E, VD, Frame, Version, V))
     return false;
-  if (!V->hasValue()) {
-    // FIXME: Is it possible for V to be indeterminate here? If so, we should
-    // adjust the diagnostic to say that.
-    // C++23 [expr.const]p8 If we have a variable that is unknown reference
-    // or pointer it may not have a value but still be usable later on so do 
not
-    // diagnose.
-    if (!Info.checkingPotentialConstantExpression() && !AllowConstexprUnknown)
-      Info.FFDiag(E, diag::note_constexpr_use_uninit_reference);
-
-    // C++23 [expr.const]p8 If we have a variable that is unknown reference or
-    // pointer try to recover it from the frame and set the result accordingly.
-    if (VD->getType()->isReferenceType() && AllowConstexprUnknown) {
-      if (Frame) {
-        Result.set({VD, Frame->Index, Version});
-        return true;
-      }
-      return Success(VD);
-    }
-    return false;
-  }
 
   return Success(*V, E);
 }
diff --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp 
b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
index f22430a0e88a2..544372ee4492d 100644
--- a/clang/test/SemaCXX/constant-expression-p2280r4.cpp
+++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
@@ -1,5 +1,5 @@
 // RUN: %clang_cc1 -std=c++23 -verify=expected,nointerpreter %s
-// RUN: %clang_cc1 -std=c++23 -verify %s 
-fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++23 -verify=expected,interpreter %s 
-fexperimental-new-constant-interpreter
 
 using size_t = decltype(sizeof(0));
 
@@ -177,3 +177,50 @@ namespace extern_reference_used_as_unknown {
   int y;
   constinit int& g = (x,y); // expected-warning {{left operand of comma 
operator has no effect}}
 }
+
+namespace uninit_reference_used {
+  int y;
+  constexpr int &r = r; // expected-error {{must be initialized by a constant 
expression}} \
+  // nointerpreter-note {{initializer of 'r' is not a constant expression}} \
+  // nointerpreter-note {{declared here}}
+  constexpr int &rr = (rr, y);
+  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 {{declared here}} \
+    // interpreter-note {{read of uninitialized object is not allowed in a 
constant expression}}
+    return x;
+  }
+  constexpr int &gg = g(); // expected-error {{must be initialized by a 
constant expression}} \
+  // nointerpreter-note {{reference to 'x' is not a constant expression}} \
+  // interpreter-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}} \
+    // interpreter-note {{read of uninitialized object is not allowed in a 
constant expression}}
+    return x;
+  }
+  constexpr int gg2 = g2(); // expected-error {{must be initialized by a 
constant expression}} \
+  // interpreter-note {{in call to 'g2()'}}
+  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}}
+    return x;
+  }
+  constexpr int &gg3 = 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}} \
+    // interpreter-note {{read of uninitialized object is not allowed in a 
constant expression}}
+    *(uintptr_t*)x = 10;
+    return 3;
+  }
+  constexpr uintptr_t gg4 = g4(); // expected-error {{must be initialized by a 
constant expression}} \
+  // interpreter-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}} \
+    // interpreter-note {{read of uninitialized object is not allowed in a 
constant expression}}
+    return 3;
+  }
+  constexpr uintptr_t gg5 = g5(); // interpreter-error {{must be initialized 
by a constant expression}} \
+  // interpreter-note {{in call to 'g5()'}}
+
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/132990
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to