[libcxx-commits] [clang] [libcxx] [Clang] Diagnose forming references to nullptr (PR #143667)

Corentin Jabot via libcxx-commits libcxx-commits at lists.llvm.org
Wed Jul 16 01:44:43 PDT 2025


https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/143667

>From a3f85983a8ab50de52c1e4825f490120e8ecdac2 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Wed, 2 Jul 2025 15:55:41 +0200
Subject: [PATCH 1/7] Diagnose all dereferences of null pointers

---
 clang/docs/ReleaseNotes.rst                   |  3 +-
 .../include/clang/Basic/DiagnosticASTKinds.td |  9 +-
 clang/lib/AST/ByteCode/State.h                |  1 +
 clang/lib/AST/ExprConstant.cpp                | 87 +++++++++++++++----
 clang/test/AST/ByteCode/complex.cpp           |  5 +-
 clang/test/AST/ByteCode/const-eval.c          |  2 +
 clang/test/AST/ByteCode/cxx11.cpp             |  4 +-
 clang/test/AST/ByteCode/records.cpp           | 10 ++-
 clang/test/CXX/drs/cwg14xx.cpp                |  2 +
 clang/test/CXX/expr/expr.const/p2-0x.cpp      |  8 +-
 clang/test/Sema/const-eval.c                  |  5 +-
 .../SemaCXX/constant-expression-cxx11.cpp     |  4 +-
 .../SemaCXX/constant-expression-cxx14.cpp     | 56 +++++++++++-
 .../SemaCXX/constant-expression-cxx2a.cpp     |  2 +-
 .../SemaCXX/constexpr-backtrace-limit.cpp     |  4 +-
 .../range.zip/iterator/increment.pass.cpp     |  4 +-
 16 files changed, 165 insertions(+), 41 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 1eb3e369a302e..2f1705ba7db06 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -674,7 +674,7 @@ Improvements to Clang's diagnostics
   #GH142457, #GH139913, #GH138850, #GH137867, #GH137860, #GH107840, #GH93308,
   #GH69470, #GH59391, #GH58172, #GH46215, #GH45915, #GH45891, #GH44490,
   #GH36703, #GH32903, #GH23312, #GH69874.
-  
+
 - Clang no longer emits a spurious -Wdangling-gsl warning in C++23 when
   iterating over an element of a temporary container in a range-based
   for loop.(#GH109793, #GH145164)
@@ -970,6 +970,7 @@ Bug Fixes to C++ Support
 - Fixed a crash involving list-initialization of an empty class with a
   non-empty initializer list. (#GH147949)
 - Fixed constant evaluation of equality comparisons of constexpr-unknown references. (#GH147663)
+- Diagnose binding a reference to ``*nullptr`` during constant evaluation. (#GH48665)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index a67b9995d3b54..e02075dba30b9 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..6fc33222ac956 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_Dereference
 };
 
 /// The order of this enum is important for diagnostics.
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 1b33b6706e204..7bc772e1ef9bb 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_Dereference;
 }
 
 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_Dereference:
     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_Dereference;
 }
 
 /// 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_Dereference:
   case AK_ReadObjectRepresentation:
   case AK_Assign:
   case AK_Construct:
@@ -4407,8 +4409,9 @@ 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.
-    if (!Frame) {
+    // the variable we're reading must be const (unless we are binding to a
+    // reference).
+    if (AK != clang::AK_Dereference && !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
@@ -4472,7 +4475,11 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
       }
     }
 
-    if (!evaluateVarDeclInit(Info, E, VD, Frame, LVal.getLValueVersion(), BaseVal))
+    // When binding to a reference, the variable does not need to be constexpr
+    // or have constant initalization.
+    if (AK != clang::AK_Dereference &&
+        !evaluateVarDeclInit(Info, E, VD, Frame, LVal.getLValueVersion(),
+                             BaseVal))
       return CompleteObject();
     // If evaluateVarDeclInit sees a constexpr-unknown variable, it returns
     // a null BaseVal. Any constexpr-unknown variable seen here is an error:
@@ -4491,7 +4498,10 @@ 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_Dereference) {
     const Expr *Base = LVal.Base.dyn_cast<const Expr*>();
 
     if (!Frame) {
@@ -4572,7 +4582,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
         NoteLValueLocation(Info, LVal.Base);
         return CompleteObject();
       }
-    } else {
+    } else if (AK != clang::AK_Dereference) {
       BaseVal = Frame->getTemporary(Base, LVal.Base.getVersion());
       assert(BaseVal && "missing value for temporary");
     }
@@ -5200,6 +5210,29 @@ enum EvalStmtResult {
   ESR_CaseNotFound
 };
 }
+/// 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;
+  if (!Result.Designator.Invalid && Result.Designator.isOnePastTheEnd()) {
+    Info.FFDiag(Init, diag::note_constexpr_access_past_end) << AK_Dereference;
+    return false;
+  }
+
+  // save the result
+  Result.moveInto(Val);
+  return true;
+}
 
 static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) {
   if (VD->isInvalidDecl())
@@ -5221,7 +5254,10 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) {
   if (InitE->isValueDependent())
     return false;
 
-  if (!EvaluateInPlace(Val, Info, Result, InitE)) {
+  if (VD->getType()->isReferenceType() &&
+      !VD->getType()->isFunctionReferenceType()) {
+    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.
     Val = APValue();
@@ -6851,9 +6887,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() &&
+          !FD->getType()->isFunctionReferenceType()) {
+        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())
@@ -9287,7 +9332,10 @@ bool LValueExprEvaluator::VisitArraySubscriptExpr(const ArraySubscriptExpr *E) {
 }
 
 bool LValueExprEvaluator::VisitUnaryDeref(const UnaryOperator *E) {
-  return evaluatePointer(E->getSubExpr(), Result);
+  bool Success = evaluatePointer(E->getSubExpr(), Result);
+  return Success &&
+         (!E->getType().getNonReferenceType()->isObjectType() ||
+          findCompleteObject(Info, E, AK_Dereference, Result, E->getType()));
 }
 
 bool LValueExprEvaluator::VisitUnaryReal(const UnaryOperator *E) {
@@ -10906,9 +10954,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() &&
+        !Field->getType()->isFunctionReferenceType()) {
+      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/AST/ByteCode/complex.cpp b/clang/test/AST/ByteCode/complex.cpp
index 2c0111c53d3bf..17f315b24eccb 100644
--- a/clang/test/AST/ByteCode/complex.cpp
+++ b/clang/test/AST/ByteCode/complex.cpp
@@ -396,10 +396,9 @@ namespace ComplexConstexpr {
                                     // both-note {{cannot refer to element 3 of array of 2 elements}}
   constexpr _Complex float *p = 0;
   constexpr float pr = __real *p; // both-error {{constant expr}} \
-                                  // ref-note {{cannot access real component of null}} \
-                                  // expected-note {{read of dereferenced null pointer}}
+                                  // both-note {{read of dereferenced null pointer}}
   constexpr float pi = __imag *p; // both-error {{constant expr}} \
-                                  // ref-note {{cannot access imaginary component of null}}
+                                  // ref-note {{read of dereferenced null pointer}}
   constexpr const _Complex double *q = &test3 + 1;
   constexpr double qr = __real *q; // ref-error {{constant expr}} \
                                    // ref-note {{cannot access real component of pointer past the end}}
diff --git a/clang/test/AST/ByteCode/const-eval.c b/clang/test/AST/ByteCode/const-eval.c
index eab14c08ec809..08f5f0adb3772 100644
--- a/clang/test/AST/ByteCode/const-eval.c
+++ b/clang/test/AST/ByteCode/const-eval.c
@@ -51,6 +51,8 @@ struct s {
 };
 
 EVAL_EXPR(19, ((int)&*(char*)10 == 10 ? 1 : -1));
+// ref-error at -1 {{expression is not an integer constant expression}} \
+// ref-note at -1 {{read of dereferenced null pointer is not allowed in a constant expression}}
 
 #ifndef NEW_INTERP
 EVAL_EXPR(20, __builtin_constant_p(*((int*) 10)));
diff --git a/clang/test/AST/ByteCode/cxx11.cpp b/clang/test/AST/ByteCode/cxx11.cpp
index b34e7823220e2..58924a5fc48d9 100644
--- a/clang/test/AST/ByteCode/cxx11.cpp
+++ b/clang/test/AST/ByteCode/cxx11.cpp
@@ -39,7 +39,9 @@ struct S {
 constexpr S s = { 5 };
 constexpr const int *p = &s.m + 1;
 
-constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // ok
+constexpr const int *np2 = &(*(int(*)[4])nullptr)[0];
+// ref-error at -1 {{constexpr variable 'np2' must be initialized by a constant expression}} \
+// ref-note at -1  {{read of dereferenced null pointer is not allowed in a constant expression}}
 
 constexpr int preDec(int x) { // both-error {{never produces a constant expression}}
   return --x;                 // both-note {{subexpression}}
diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp
index d369c64bc3904..ef8dc717bda78 100644
--- a/clang/test/AST/ByteCode/records.cpp
+++ b/clang/test/AST/ByteCode/records.cpp
@@ -413,7 +413,7 @@ namespace DeriveFailures {
 
     constexpr Derived(int i) : OtherVal(i) {} // ref-error {{never produces a constant expression}} \
                                               // both-note {{non-constexpr constructor 'Base' cannot be used in a constant expression}} \
-                                              // ref-note {{non-constexpr constructor 'Base' cannot be used in a constant expression}} 
+                                              // ref-note {{non-constexpr constructor 'Base' cannot be used in a constant expression}}
   };
 
   constexpr Derived D(12); // both-error {{must be initialized by a constant expression}} \
@@ -1660,9 +1660,11 @@ namespace NullptrCast {
   constexpr A *na = nullptr;
   constexpr B *nb = nullptr;
   constexpr A &ra = *nb; // both-error {{constant expression}} \
-                         // both-note {{cannot access base class of null pointer}}
+                         // ref-note {{read of dereferenced null pointer}} \
+                         // expected-note {{cannot access base class of null pointer}}
   constexpr B &rb = (B&)*na; // both-error {{constant expression}} \
-                             // both-note {{cannot access derived class of null pointer}}
+                             // ref-note {{read of dereferenced null pointer}} \
+                             // expected-note {{cannot access derived class of null pointer}}
   constexpr bool test() {
     auto a = (A*)(B*)nullptr;
 
@@ -1740,7 +1742,7 @@ namespace CtorOfInvalidClass {
 #if __cplusplus >= 202002L
   template <typename T, auto Q>
   concept ReferenceOf = Q;
-  /// This calls a valid and constexpr copy constructor of InvalidCtor, 
+  /// This calls a valid and constexpr copy constructor of InvalidCtor,
   /// but should still be rejected.
   template<ReferenceOf<InvalidCtor> auto R, typename Rep> int F; // both-error {{non-type template argument is not a constant expression}}
 #endif
diff --git a/clang/test/CXX/drs/cwg14xx.cpp b/clang/test/CXX/drs/cwg14xx.cpp
index 17d5c2fc2e210..d1c4424ab5b7a 100644
--- a/clang/test/CXX/drs/cwg14xx.cpp
+++ b/clang/test/CXX/drs/cwg14xx.cpp
@@ -107,6 +107,8 @@ void f() {
   constexpr int p = &*a;
   // since-cxx11-error at -1 {{cannot initialize a variable of type 'const int' with an rvalue of type 'A *'}}
   constexpr A *p2 = &*a;
+  // since-cxx11-error at -1 {{constexpr variable 'p2' must be initialized by a constant expression}} \
+  // since-cxx11-note at -1 {{read of dereferenced null pointer is not allowed in a constant expression}}
 }
 
 struct A {
diff --git a/clang/test/CXX/expr/expr.const/p2-0x.cpp b/clang/test/CXX/expr/expr.const/p2-0x.cpp
index c6c3381be5523..6309192162205 100644
--- a/clang/test/CXX/expr/expr.const/p2-0x.cpp
+++ b/clang/test/CXX/expr/expr.const/p2-0x.cpp
@@ -199,15 +199,15 @@ namespace UndefinedBehavior {
 
     constexpr A *na = nullptr;
     constexpr B *nb = nullptr;
-    constexpr A &ra = *nb; // expected-error {{constant expression}} expected-note {{cannot access base class of null pointer}}
-    constexpr B &rb = (B&)*na; // expected-error {{constant expression}} expected-note {{cannot access derived class of null pointer}}
+    constexpr A &ra = *nb; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}}
+    constexpr B &rb = (B&)*na; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}}
     static_assert((A*)nb == 0, "");
     static_assert((B*)na == 0, "");
     constexpr const int &nf = nb->n; // expected-error {{constant expression}} expected-note {{cannot access field of null pointer}}
     constexpr const int &mf = nb->m; // expected-error {{constant expression}} expected-note {{cannot access field of null pointer}}
     constexpr const int *np1 = (int*)nullptr + 0; // ok
-    constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // ok
-    constexpr const int *np3 = &(*(int(*)[4])nullptr)[2]; // expected-error {{constant expression}} expected-note {{cannot perform pointer arithmetic on null pointer}}
+    constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}}
+    constexpr const int *np3 = &(*(int(*)[4])nullptr)[2]; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer is not allowed in a constant expression}}
 
     struct C {
       constexpr int f() const { return 0; }
diff --git a/clang/test/Sema/const-eval.c b/clang/test/Sema/const-eval.c
index e358aceaad5a4..f3df072710491 100644
--- a/clang/test/Sema/const-eval.c
+++ b/clang/test/Sema/const-eval.c
@@ -32,7 +32,7 @@ void f(void)
 _Complex float g16 = (1.0f + 1.0fi);
 
 // ?: in constant expressions.
-int g17[(3?:1) - 2]; 
+int g17[(3?:1) - 2];
 
 EVAL_EXPR(18, ((int)((void*)10 + 10)) == 20 ? 1 : -1);
 
@@ -41,6 +41,9 @@ struct s {
 };
 
 EVAL_EXPR(19, ((int)&*(char*)10 == 10 ? 1 : -1));
+// expected-error at -1 {{not an integer constant expression}} \
+// expected-note at -1 {{read of dereferenced null pointer is not allowed in a constant expression}}
+
 
 EVAL_EXPR(20, __builtin_constant_p(*((int*) 10)));
 
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index c390fee1c38d9..4b61354250102 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -1413,8 +1413,8 @@ namespace ComplexConstexpr {
   static_assert(t2p[2] == 0.0, ""); // expected-error {{constant expr}} expected-note {{one-past-the-end pointer}}
   static_assert(t2p[3] == 0.0, ""); // expected-error {{constant expr}} expected-note {{cannot refer to element 3 of array of 2 elements}}
   constexpr _Complex float *p = 0; // expected-warning {{'_Complex' is a C99 extension}}
-  constexpr float pr = __real *p; // expected-error {{constant expr}} expected-note {{cannot access real component of null}}
-  constexpr float pi = __imag *p; // expected-error {{constant expr}} expected-note {{cannot access imaginary component of null}}
+  constexpr float pr = __real *p; // expected-error {{constant expr}} expected-note {{read of dereferenced null pointer}}
+  constexpr float pi = __imag *p; // expected-error {{constant expr}} expected-note {{read of dereferenced null pointer}}
   constexpr const _Complex double *q = &test3 + 1; // expected-warning {{'_Complex' is a C99 extension}}
   constexpr double qr = __real *q; // expected-error {{constant expr}} expected-note {{cannot access real component of pointer past the end}}
   constexpr double qi = __imag *q; // expected-error {{constant expr}} expected-note {{cannot access imaginary component of pointer past the end}}
diff --git a/clang/test/SemaCXX/constant-expression-cxx14.cpp b/clang/test/SemaCXX/constant-expression-cxx14.cpp
index e93b98c185a82..5c7db44806d87 100644
--- a/clang/test/SemaCXX/constant-expression-cxx14.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx14.cpp
@@ -265,7 +265,7 @@ namespace const_modify {
 
 namespace null {
   constexpr int test(int *p) {
-    return *p = 123; // expected-note {{assignment to dereferenced null pointer}}
+    return *p = 123; // expected-note {{read of dereferenced null pointer}}
   }
   static_assert(test(0), ""); // expected-error {{constant expression}} expected-note {{in call}}
 }
@@ -1335,4 +1335,58 @@ namespace comparison_dead_variable {
   }
   // FIXME: This should fail.
   static_assert(f(),"");
+
+}
+namespace GH48665 {
+constexpr bool foo(int *i) {
+    int &j = *i;
+    // expected-note at -1 {{read of dereferenced null pointer}}
+    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}}
+
+constexpr bool foo_rvalue(int *i) {
+    int &&j = (int&&)*i;
+    // expected-note at -1 {{read of dereferenced null pointer}}
+    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]; // expected-note {{read of dereferenced one-past-the-end pointer}} \
+                    // cxx14_20-note {{read of dereferenced one-past-the-end pointer}} \
+                    // 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 {{read of dereferenced null pointer}}
+   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 {{read of dereferenced null pointer}}
+  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}}
+
 }
diff --git a/clang/test/SemaCXX/constant-expression-cxx2a.cpp b/clang/test/SemaCXX/constant-expression-cxx2a.cpp
index 85720606fe9de..ffb7e633c2919 100644
--- a/clang/test/SemaCXX/constant-expression-cxx2a.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx2a.cpp
@@ -927,7 +927,7 @@ namespace dynamic_alloc {
   constexpr void use_after_free() { // expected-error {{never produces a constant expression}}
     int *p = new int;
     delete p;
-    *p = 1; // expected-note {{assignment to heap allocated object that has been deleted}}
+    *p = 1; // expected-note {{read of heap allocated object that has been deleted}}
   }
   constexpr void use_after_free_2() { // expected-error {{never produces a constant expression}}
     struct X { constexpr void f() {} };
diff --git a/clang/test/SemaCXX/constexpr-backtrace-limit.cpp b/clang/test/SemaCXX/constexpr-backtrace-limit.cpp
index e867afdff5c3c..61991bf5d2931 100644
--- a/clang/test/SemaCXX/constexpr-backtrace-limit.cpp
+++ b/clang/test/SemaCXX/constexpr-backtrace-limit.cpp
@@ -15,14 +15,14 @@
 
 // RUN: not %clang_cc1 -std=c++11 -fsyntax-only %s -fconstexpr-backtrace-limit=2 -fconstexpr-depth=8 -fno-caret-diagnostics 2>&1 | FileCheck %s -check-prefix=TEST3
 // TEST3: constant expression
-// TEST3-NEXT: reinterpret_cast
+// TEST3-NEXT: read of dereferenced null pointe
 // TEST3-NEXT: in call to 'recurse(0)'
 // TEST3-NEXT: skipping 4 calls
 // TEST3-NEXT: in call to 'recurse(5)'
 
 // RUN: not %clang_cc1 -std=c++11 -fsyntax-only %s -fconstexpr-backtrace-limit=8 -fconstexpr-depth=8 -fno-caret-diagnostics 2>&1 | FileCheck %s -check-prefix=TEST4
 // TEST4: constant expression
-// TEST4-NEXT: reinterpret_cast
+// TEST4-NEXT: read of dereferenced null pointe
 // TEST4-NEXT: in call to 'recurse(0)'
 // TEST4-NEXT: in call to 'recurse(1)'
 // TEST4-NEXT: in call to 'recurse(2)'
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip/iterator/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip/iterator/increment.pass.cpp
index 0ca8d92800feb..94d2bd47e9806 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.zip/iterator/increment.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip/iterator/increment.pass.cpp
@@ -59,7 +59,7 @@ constexpr bool test() {
 
   {
     //  bidi
-    int buffer[2] = {1, 2};
+    int buffer[3] = {1, 2, 3};
 
     std::ranges::zip_view v(BidiCommonView{buffer});
     auto it = v.begin();
@@ -81,7 +81,7 @@ constexpr bool test() {
 
   {
     //  forward
-    int buffer[2] = {1, 2};
+    int buffer[3] = {1, 2, 3};
 
     std::ranges::zip_view v(ForwardSizedView{buffer});
     auto it = v.begin();

>From 07ae0de4a22eaf92d994ad4873ca39f5b68e9508 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 10 Jul 2025 14:13:37 +0200
Subject: [PATCH 2/7] address some feedback

---
 clang/lib/AST/ExprConstant.cpp | 8 ++++----
 clang/test/CXX/drs/cwg14xx.cpp | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 7bc772e1ef9bb..274b288ea061d 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1562,17 +1562,17 @@ static bool isFormalAccess(AccessKinds AK) {
          AK != AK_IsWithinLifetime && AK != AK_Dereference;
 }
 
-/// Is this kind of axcess valid on an indeterminate object value?
+/// Is this kind of access valid on an indeterminate object value?
 static bool isValidIndeterminateAccess(AccessKinds AK) {
   switch (AK) {
   case AK_Read:
   case AK_Increment:
   case AK_Decrement:
+  case AK_Dereference:
     // These need the object's value.
     return false;
 
   case AK_IsWithinLifetime:
-  case AK_Dereference:
   case AK_ReadObjectRepresentation:
   case AK_Assign:
   case AK_Construct:
@@ -5216,7 +5216,7 @@ static bool EvaluateInitForDeclOfReferenceType(EvalInfo &Info,
                                                const Expr *Init, LValue &Result,
                                                APValue &Val) {
   assert(Init->isGLValue() && D->getType()->isReferenceType());
-  // A reference is an lvalue
+  // A reference is an lvalue.
   if (!EvaluateLValue(Init, Result, Info))
     return false;
   // [C++26][decl.ref]
@@ -5229,7 +5229,7 @@ static bool EvaluateInitForDeclOfReferenceType(EvalInfo &Info,
     return false;
   }
 
-  // save the result
+  // Save the result.
   Result.moveInto(Val);
   return true;
 }
diff --git a/clang/test/CXX/drs/cwg14xx.cpp b/clang/test/CXX/drs/cwg14xx.cpp
index d1c4424ab5b7a..2b036f2adb48e 100644
--- a/clang/test/CXX/drs/cwg14xx.cpp
+++ b/clang/test/CXX/drs/cwg14xx.cpp
@@ -107,8 +107,8 @@ void f() {
   constexpr int p = &*a;
   // since-cxx11-error at -1 {{cannot initialize a variable of type 'const int' with an rvalue of type 'A *'}}
   constexpr A *p2 = &*a;
-  // since-cxx11-error at -1 {{constexpr variable 'p2' must be initialized by a constant expression}} \
-  // since-cxx11-note at -1 {{read of dereferenced null pointer is not allowed in a constant expression}}
+  // since-cxx11-error at -1 {{constexpr variable 'p2' must be initialized by a constant expression}}
+  // since-cxx11-note at -2 {{read of dereferenced null pointer is not allowed in a constant expression}}
 }
 
 struct A {

>From 43cb1b40e14bcdd0f8c08c6602a2f46ee874576c Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 10 Jul 2025 14:35:13 +0200
Subject: [PATCH 3/7] Add a disting diagnostic for dereferencing a null pointer

> dereferencing a null pointer is not allowed in a constant expression
---
 clang/include/clang/Basic/DiagnosticASTKinds.td  |  2 ++
 clang/lib/AST/ExprConstant.cpp                   | 10 ++++++++--
 clang/test/AST/ByteCode/complex.cpp              |  5 +++--
 clang/test/AST/ByteCode/const-eval.c             |  2 +-
 clang/test/AST/ByteCode/cxx11.cpp                |  2 +-
 clang/test/AST/ByteCode/records.cpp              |  4 ++--
 clang/test/CXX/drs/cwg14xx.cpp                   |  2 +-
 clang/test/CXX/expr/expr.const/p2-0x.cpp         | 10 +++++-----
 clang/test/Sema/const-eval.c                     |  2 +-
 clang/test/SemaCXX/constant-expression-cxx11.cpp |  4 ++--
 clang/test/SemaCXX/constant-expression-cxx14.cpp | 10 +++++-----
 clang/test/SemaCXX/constexpr-backtrace-limit.cpp |  4 ++--
 12 files changed, 33 insertions(+), 24 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index e02075dba30b9..1d9f021f518e9 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -223,6 +223,8 @@ def note_constexpr_ltor_incomplete_type : Note<
 def note_constexpr_access_null : Note<
   "%sub{access_kind}0 "
   "dereferenced null pointer is not allowed in a constant expression">;
+def note_constexpr_dereferencing_null : Note<
+    "dereferencing a null pointer is not allowed in a constant expression">;
 def note_constexpr_access_past_end : Note<
   "%sub{access_kind}0 dereferenced one-past-the-end pointer "
   "is not allowed in a constant expression">;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 274b288ea061d..5d325c7f35ae3 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1735,7 +1735,10 @@ namespace {
     bool checkNullPointerForFoldAccess(EvalInfo &Info, const Expr *E,
                                        AccessKinds AK) {
       return checkNullPointerDiagnosingWith([&Info, E, AK] {
-        Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
+        if(AK == AccessKinds::AK_Dereference)
+            Info.FFDiag(E, diag::note_constexpr_dereferencing_null);
+        else
+            Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
       });
     }
 
@@ -4307,7 +4310,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
   }
 
   if (!LVal.Base) {
-    Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
+    if(AK == AccessKinds::AK_Dereference)
+        Info.FFDiag(E, diag::note_constexpr_dereferencing_null);
+    else
+        Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
     return CompleteObject();
   }
 
diff --git a/clang/test/AST/ByteCode/complex.cpp b/clang/test/AST/ByteCode/complex.cpp
index 17f315b24eccb..959d759005ef4 100644
--- a/clang/test/AST/ByteCode/complex.cpp
+++ b/clang/test/AST/ByteCode/complex.cpp
@@ -396,9 +396,10 @@ namespace ComplexConstexpr {
                                     // both-note {{cannot refer to element 3 of array of 2 elements}}
   constexpr _Complex float *p = 0;
   constexpr float pr = __real *p; // both-error {{constant expr}} \
-                                  // both-note {{read of dereferenced null pointer}}
+                                  // expected-note {{read of dereferenced null pointer}} \
+                                  // ref-note {{dereferencing a null pointer}}
   constexpr float pi = __imag *p; // both-error {{constant expr}} \
-                                  // ref-note {{read of dereferenced null pointer}}
+                                  // ref-note {{dereferencing a null pointer}}
   constexpr const _Complex double *q = &test3 + 1;
   constexpr double qr = __real *q; // ref-error {{constant expr}} \
                                    // ref-note {{cannot access real component of pointer past the end}}
diff --git a/clang/test/AST/ByteCode/const-eval.c b/clang/test/AST/ByteCode/const-eval.c
index 08f5f0adb3772..c8651a744f969 100644
--- a/clang/test/AST/ByteCode/const-eval.c
+++ b/clang/test/AST/ByteCode/const-eval.c
@@ -52,7 +52,7 @@ struct s {
 
 EVAL_EXPR(19, ((int)&*(char*)10 == 10 ? 1 : -1));
 // ref-error at -1 {{expression is not an integer constant expression}} \
-// ref-note at -1 {{read of dereferenced null pointer is not allowed in a constant expression}}
+// ref-note at -1 {{dereferencing a null pointer}}
 
 #ifndef NEW_INTERP
 EVAL_EXPR(20, __builtin_constant_p(*((int*) 10)));
diff --git a/clang/test/AST/ByteCode/cxx11.cpp b/clang/test/AST/ByteCode/cxx11.cpp
index 58924a5fc48d9..55554220b0a8a 100644
--- a/clang/test/AST/ByteCode/cxx11.cpp
+++ b/clang/test/AST/ByteCode/cxx11.cpp
@@ -41,7 +41,7 @@ constexpr const int *p = &s.m + 1;
 
 constexpr const int *np2 = &(*(int(*)[4])nullptr)[0];
 // ref-error at -1 {{constexpr variable 'np2' must be initialized by a constant expression}} \
-// ref-note at -1  {{read of dereferenced null pointer is not allowed in a constant expression}}
+// ref-note at -1  {{dereferencing a null pointer is not allowed in a constant expression}}
 
 constexpr int preDec(int x) { // both-error {{never produces a constant expression}}
   return --x;                 // both-note {{subexpression}}
diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp
index ef8dc717bda78..774fed6189d64 100644
--- a/clang/test/AST/ByteCode/records.cpp
+++ b/clang/test/AST/ByteCode/records.cpp
@@ -1660,10 +1660,10 @@ namespace NullptrCast {
   constexpr A *na = nullptr;
   constexpr B *nb = nullptr;
   constexpr A &ra = *nb; // both-error {{constant expression}} \
-                         // ref-note {{read of dereferenced null pointer}} \
+                         // ref-note {{dereferencing a null pointer}} \
                          // expected-note {{cannot access base class of null pointer}}
   constexpr B &rb = (B&)*na; // both-error {{constant expression}} \
-                             // ref-note {{read of dereferenced null pointer}} \
+                             // ref-note {{dereferencing a null pointer}} \
                              // expected-note {{cannot access derived class of null pointer}}
   constexpr bool test() {
     auto a = (A*)(B*)nullptr;
diff --git a/clang/test/CXX/drs/cwg14xx.cpp b/clang/test/CXX/drs/cwg14xx.cpp
index 2b036f2adb48e..8d39018d8926c 100644
--- a/clang/test/CXX/drs/cwg14xx.cpp
+++ b/clang/test/CXX/drs/cwg14xx.cpp
@@ -108,7 +108,7 @@ void f() {
   // since-cxx11-error at -1 {{cannot initialize a variable of type 'const int' with an rvalue of type 'A *'}}
   constexpr A *p2 = &*a;
   // since-cxx11-error at -1 {{constexpr variable 'p2' must be initialized by a constant expression}}
-  // since-cxx11-note at -2 {{read of dereferenced null pointer is not allowed in a constant expression}}
+  // since-cxx11-note at -2 {{dereferencing a null pointer}}
 }
 
 struct A {
diff --git a/clang/test/CXX/expr/expr.const/p2-0x.cpp b/clang/test/CXX/expr/expr.const/p2-0x.cpp
index 6309192162205..910c8635f7353 100644
--- a/clang/test/CXX/expr/expr.const/p2-0x.cpp
+++ b/clang/test/CXX/expr/expr.const/p2-0x.cpp
@@ -199,15 +199,15 @@ namespace UndefinedBehavior {
 
     constexpr A *na = nullptr;
     constexpr B *nb = nullptr;
-    constexpr A &ra = *nb; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}}
-    constexpr B &rb = (B&)*na; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}}
+    constexpr A &ra = *nb; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}}
+    constexpr B &rb = (B&)*na; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}}
     static_assert((A*)nb == 0, "");
     static_assert((B*)na == 0, "");
     constexpr const int &nf = nb->n; // expected-error {{constant expression}} expected-note {{cannot access field of null pointer}}
     constexpr const int &mf = nb->m; // expected-error {{constant expression}} expected-note {{cannot access field of null pointer}}
     constexpr const int *np1 = (int*)nullptr + 0; // ok
-    constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}}
-    constexpr const int *np3 = &(*(int(*)[4])nullptr)[2]; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer is not allowed in a constant expression}}
+    constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}}
+    constexpr const int *np3 = &(*(int(*)[4])nullptr)[2]; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}}
 
     struct C {
       constexpr int f() const { return 0; }
@@ -485,7 +485,7 @@ namespace std {
 namespace TypeId {
   struct S { virtual void f(); };
   constexpr S *p = 0;
-  constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferenced null pointer}}
+  constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferencing a null pointer}}
 
   struct T {} t;
   constexpr const std::type_info &ti2 = typeid(t);
diff --git a/clang/test/Sema/const-eval.c b/clang/test/Sema/const-eval.c
index f3df072710491..87c21120e7c5d 100644
--- a/clang/test/Sema/const-eval.c
+++ b/clang/test/Sema/const-eval.c
@@ -42,7 +42,7 @@ struct s {
 
 EVAL_EXPR(19, ((int)&*(char*)10 == 10 ? 1 : -1));
 // expected-error at -1 {{not an integer constant expression}} \
-// expected-note at -1 {{read of dereferenced null pointer is not allowed in a constant expression}}
+// expected-note at -1 {{dereferencing a null pointer is not allowed in a constant expression}}
 
 
 EVAL_EXPR(20, __builtin_constant_p(*((int*) 10)));
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index 4b61354250102..5ecb8c607f59a 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -1413,8 +1413,8 @@ namespace ComplexConstexpr {
   static_assert(t2p[2] == 0.0, ""); // expected-error {{constant expr}} expected-note {{one-past-the-end pointer}}
   static_assert(t2p[3] == 0.0, ""); // expected-error {{constant expr}} expected-note {{cannot refer to element 3 of array of 2 elements}}
   constexpr _Complex float *p = 0; // expected-warning {{'_Complex' is a C99 extension}}
-  constexpr float pr = __real *p; // expected-error {{constant expr}} expected-note {{read of dereferenced null pointer}}
-  constexpr float pi = __imag *p; // expected-error {{constant expr}} expected-note {{read of dereferenced null pointer}}
+  constexpr float pr = __real *p; // expected-error {{constant expr}} expected-note {{dereferencing a null pointer}}
+  constexpr float pi = __imag *p; // expected-error {{constant expr}} expected-note {{dereferencing a null pointer}}
   constexpr const _Complex double *q = &test3 + 1; // expected-warning {{'_Complex' is a C99 extension}}
   constexpr double qr = __real *q; // expected-error {{constant expr}} expected-note {{cannot access real component of pointer past the end}}
   constexpr double qi = __imag *q; // expected-error {{constant expr}} expected-note {{cannot access imaginary component of pointer past the end}}
diff --git a/clang/test/SemaCXX/constant-expression-cxx14.cpp b/clang/test/SemaCXX/constant-expression-cxx14.cpp
index 5c7db44806d87..e5854aa2f2b87 100644
--- a/clang/test/SemaCXX/constant-expression-cxx14.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx14.cpp
@@ -265,7 +265,7 @@ namespace const_modify {
 
 namespace null {
   constexpr int test(int *p) {
-    return *p = 123; // expected-note {{read of dereferenced null pointer}}
+    return *p = 123; // expected-note {{dereferencing a null pointer}}
   }
   static_assert(test(0), ""); // expected-error {{constant expression}} expected-note {{in call}}
 }
@@ -1340,7 +1340,7 @@ namespace comparison_dead_variable {
 namespace GH48665 {
 constexpr bool foo(int *i) {
     int &j = *i;
-    // expected-note at -1 {{read of dereferenced null pointer}}
+    // expected-note at -1 {{dereferencing a null pointer}}
     return true;
 }
 
@@ -1349,7 +1349,7 @@ static_assert(foo(nullptr), ""); // expected-note {{in call to 'foo(nullptr)'}}
 
 constexpr bool foo_rvalue(int *i) {
     int &&j = (int&&)*i;
-    // expected-note at -1 {{read of dereferenced null pointer}}
+    // expected-note at -1 {{dereferencing a null pointer}}
     return true;
 }
 static_assert(foo_rvalue(nullptr), ""); // expected-note {{in call to 'foo_rvalue(nullptr)'}}
@@ -1370,14 +1370,14 @@ struct Aggregate {
    int &r;
 };
 constexpr bool test_agg(int *i) {
-   Aggregate a{*i}; //expected-note {{read of dereferenced null pointer}}
+   Aggregate a{*i}; //expected-note {{dereferencing a null pointer}}
    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 {{read of dereferenced null pointer}}
+  constexpr B(int *p) : r{*p} {}  // expected-note {{dereferencing a null pointer}}
   int &r;
 };
 
diff --git a/clang/test/SemaCXX/constexpr-backtrace-limit.cpp b/clang/test/SemaCXX/constexpr-backtrace-limit.cpp
index 61991bf5d2931..f0c1206a4b8d3 100644
--- a/clang/test/SemaCXX/constexpr-backtrace-limit.cpp
+++ b/clang/test/SemaCXX/constexpr-backtrace-limit.cpp
@@ -15,14 +15,14 @@
 
 // RUN: not %clang_cc1 -std=c++11 -fsyntax-only %s -fconstexpr-backtrace-limit=2 -fconstexpr-depth=8 -fno-caret-diagnostics 2>&1 | FileCheck %s -check-prefix=TEST3
 // TEST3: constant expression
-// TEST3-NEXT: read of dereferenced null pointe
+// TEST3-NEXT: dereferencing a null pointer
 // TEST3-NEXT: in call to 'recurse(0)'
 // TEST3-NEXT: skipping 4 calls
 // TEST3-NEXT: in call to 'recurse(5)'
 
 // RUN: not %clang_cc1 -std=c++11 -fsyntax-only %s -fconstexpr-backtrace-limit=8 -fconstexpr-depth=8 -fno-caret-diagnostics 2>&1 | FileCheck %s -check-prefix=TEST4
 // TEST4: constant expression
-// TEST4-NEXT: read of dereferenced null pointe
+// TEST4-NEXT: dereferencing a null pointer
 // TEST4-NEXT: in call to 'recurse(0)'
 // TEST4-NEXT: in call to 'recurse(1)'
 // TEST4-NEXT: in call to 'recurse(2)'

>From a8352d11039f18210706f592e63330dfb66c9bb8 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 10 Jul 2025 15:32:03 +0200
Subject: [PATCH 4/7] format

---
 clang/include/clang/Basic/DiagnosticASTKinds.td |  5 +++--
 clang/lib/AST/ExprConstant.cpp                  | 12 ++++++------
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 1d9f021f518e9..071a38f513911 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -223,8 +223,9 @@ def note_constexpr_ltor_incomplete_type : Note<
 def note_constexpr_access_null : Note<
   "%sub{access_kind}0 "
   "dereferenced null pointer is not allowed in a constant expression">;
-def note_constexpr_dereferencing_null : Note<
-    "dereferencing a null pointer is not allowed in a constant expression">;
+def note_constexpr_dereferencing_null
+    : Note<"dereferencing a null pointer is not allowed in a constant "
+           "expression">;
 def note_constexpr_access_past_end : Note<
   "%sub{access_kind}0 dereferenced one-past-the-end pointer "
   "is not allowed in a constant expression">;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 5d325c7f35ae3..363c88753066f 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1735,10 +1735,10 @@ namespace {
     bool checkNullPointerForFoldAccess(EvalInfo &Info, const Expr *E,
                                        AccessKinds AK) {
       return checkNullPointerDiagnosingWith([&Info, E, AK] {
-        if(AK == AccessKinds::AK_Dereference)
-            Info.FFDiag(E, diag::note_constexpr_dereferencing_null);
+        if (AK == AccessKinds::AK_Dereference)
+          Info.FFDiag(E, diag::note_constexpr_dereferencing_null);
         else
-            Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
+          Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
       });
     }
 
@@ -4310,10 +4310,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
   }
 
   if (!LVal.Base) {
-    if(AK == AccessKinds::AK_Dereference)
-        Info.FFDiag(E, diag::note_constexpr_dereferencing_null);
+    if (AK == AccessKinds::AK_Dereference)
+      Info.FFDiag(E, diag::note_constexpr_dereferencing_null);
     else
-        Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
+      Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
     return CompleteObject();
   }
 

>From 1d1a6352700b17b50f2a69affdf91a70cd3a8cf1 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 15 Jul 2025 22:56:55 +0200
Subject: [PATCH 5/7] add comment

---
 clang/lib/AST/ExprConstant.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 363c88753066f..e8c5cd516cdca 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5260,6 +5260,8 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) {
   if (InitE->isValueDependent())
     return false;
 
+  // For references to objects, check they do not designate a one-past-the-end
+  // object.
   if (VD->getType()->isReferenceType() &&
       !VD->getType()->isFunctionReferenceType()) {
     return EvaluateInitForDeclOfReferenceType(Info, VD, InitE, Result, Val);
@@ -9339,6 +9341,9 @@ bool LValueExprEvaluator::VisitArraySubscriptExpr(const ArraySubscriptExpr *E) {
 
 bool LValueExprEvaluator::VisitUnaryDeref(const UnaryOperator *E) {
   bool Success = evaluatePointer(E->getSubExpr(), Result);
+  // [C++26][expr.unary.op]
+  // If the operand points to an object or function, the result
+  // denotes that object or function; otherwise, the behavior is undefined.
   return Success &&
          (!E->getType().getNonReferenceType()->isObjectType() ||
           findCompleteObject(Info, E, AK_Dereference, Result, E->getType()));

>From cd750c9f3bbfe05c8e257e5d3f09bff891c91794 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Wed, 16 Jul 2025 07:21:01 +0200
Subject: [PATCH 6/7] fix tests

---
 clang/lib/AST/ExprConstant.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index e8c5cd516cdca..fcc8f9f8478db 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -4490,7 +4490,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
     // If evaluateVarDeclInit sees a constexpr-unknown variable, it returns
     // a null BaseVal. Any constexpr-unknown variable seen here is an error:
     // we can't access a constexpr-unknown object.
-    if (!BaseVal) {
+    if (AK != clang::AK_Dereference && !BaseVal) {
       Info.FFDiag(E, diag::note_constexpr_access_unknown_variable, 1)
           << AK << VD;
       Info.Note(VD->getLocation(), diag::note_declared_at);

>From 908d4babda99be5d59b541ad786c4a9cf343eae4 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Wed, 16 Jul 2025 10:37:38 +0200
Subject: [PATCH 7/7] add function tests, simplify

---
 clang/lib/AST/ExprConstant.cpp                |  6 +--
 .../SemaCXX/constant-expression-cxx14.cpp     | 54 +++++++++++++++++++
 2 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index fcc8f9f8478db..767cc4c3b19eb 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5262,8 +5262,7 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) {
 
   // For references to objects, check they do not designate a one-past-the-end
   // object.
-  if (VD->getType()->isReferenceType() &&
-      !VD->getType()->isFunctionReferenceType()) {
+  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
@@ -10965,8 +10964,7 @@ bool RecordExprEvaluator::VisitCXXParenListOrInitListExpr(
                                   isa<CXXDefaultInitExpr>(Init));
 
     APValue &FieldVal = Result.getStructField(Field->getFieldIndex());
-    if (Field->getType()->isReferenceType() &&
-        !Field->getType()->isFunctionReferenceType()) {
+    if (Field->getType()->isReferenceType()) {
       LValue Result;
       if (!EvaluateInitForDeclOfReferenceType(Info, Field, Init, Result,
                                               FieldVal)) {
diff --git a/clang/test/SemaCXX/constant-expression-cxx14.cpp b/clang/test/SemaCXX/constant-expression-cxx14.cpp
index e5854aa2f2b87..580980a409f4d 100644
--- a/clang/test/SemaCXX/constant-expression-cxx14.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx14.cpp
@@ -1389,4 +1389,58 @@ constexpr bool test_ctr(int *i) {
 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}}
 
+
+// verify that we can dereference function pointers
+namespace functions {
+
+constexpr int f() {return 0;}
+constexpr int(*f_ptr)() = &f;
+constexpr int(*null_ptr)() = nullptr;
+
+constexpr int(&f_ref)() = f;
+constexpr int test = (*f_ptr)();
+constexpr int test2 = (*f_ref)();
+constexpr int test3 = (*f_ref)();
+constexpr int test4 = (*null_ptr)();
+//expected-error at -1 {{constexpr variable 'test4' must be initialized by a constant expression}} \
+//expected-note at -1 {{'(*null_ptr)' evaluates to a null function pointer}}
+
+constexpr int(*f_ptr_arr[1])() = {&f};
+constexpr int test_array_ok = (f_ptr_arr[0])();
+constexpr int test_array_err = (f_ptr_arr[1])();
+// expected-error at -1 {{constexpr variable 'test_array_err' must be initialized by a constant expression}} \
+// expected-note at -1 {{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}}
+
+struct S {
+    int(*f_ptr)() = &f;
+    int(*f_ptr_arr[1])() = {&f};
+    int(&f_ref)() = f;
+    int(*null_ptr)() = nullptr;
+};
+
+constexpr int test_member() {
+    S s {};
+    (*s.f_ptr)();
+    (*s.f_ref)();
+    (s.f_ref)();
+    (s.f_ptr_arr[0])();
+    (s.f_ptr_arr[1])();
+    // expected-note at -1 {{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}}
+    return 0;
+}
+constexpr int test_member_null() {
+    S s {};
+    (*s.null_ptr)(); // expected-note {{'(*s.null_ptr)' evaluates to a null function pointer}}
+    return 0;
+}
+
+static_assert(test_member(), "");
+// expected-error at -1 {{static assertion expression is not an integral constant expression}} \
+// expected-note at -1 {{in call to 'test_member()'}}
+
+static_assert(test_member_null(), "");
+// expected-error at -1 {{static assertion expression is not an integral constant expression}} \
+// expected-note at -1 {{in call to 'test_member_null()'}}
+
+}
 }



More information about the libcxx-commits mailing list