Author: Timm Baeder
Date: 2026-01-14T08:59:28+01:00
New Revision: ef44d25971cc5cf0a4f0488aacadb0709e164ed7

URL: 
https://github.com/llvm/llvm-project/commit/ef44d25971cc5cf0a4f0488aacadb0709e164ed7
DIFF: 
https://github.com/llvm/llvm-project/commit/ef44d25971cc5cf0a4f0488aacadb0709e164ed7.diff

LOG: [clang][ExprConst] Diagnose out-of-lifetime access consistently (#175562)

Previously, we had two very similar diagnostics, "read of object outside
its lifetime" and "read of variable whose lifetime has ended".
The difference, as far as I can tell, is that the latter was used when
the variable was created in a function frame that has since vanished,
i.e. in this case:
```c++
constexpr const int& return_local() { return 5; }
static_assert(return_local() == 5);
```
so the output used to be:
```console
array.cpp:602:15: error: static assertion expression is not an integral 
constant expression
  602 | static_assert(return_local() == 5);
      |               ^~~~~~~~~~~~~~~~~~~
array.cpp:602:15: note: read of temporary whose lifetime has ended
array.cpp:601:46: note: temporary created here
  601 | constexpr const int& return_local() { return 5; }
      |                                              ^
```

But then this scenario gets the other diagnostic:
```c++
constexpr int b = b;
```
```console
array.cpp:603:15: error: constexpr variable 'b' must be initialized by a 
constant expression
  603 | constexpr int b = b;
      |               ^   ~
array.cpp:603:19: note: read of object outside its lifetime is not allowed in a 
constant expression
  603 | constexpr int b = b;
      |                   ^
```

With this patch, we diagnose both cases similarly:
```c++
constexpr const int& return_local() { return 5; }
static_assert(return_local() == 5);
constexpr int b = b;
```
```console
array.cpp:602:15: error: static assertion expression is not an integral 
constant expression
  602 | static_assert(return_local() == 5);
      |               ^~~~~~~~~~~~~~~~~~~
array.cpp:602:15: note: read of object outside its lifetime is not allowed in a 
constant expression
  602 | static_assert(return_local() == 5);
      |               ^~~~~~~~~~~~~~
array.cpp:601:46: note: temporary created here
  601 | constexpr const int& return_local() { return 5; }
      |                                              ^
array.cpp:603:15: error: constexpr variable 'b' must be initialized by a 
constant expression
  603 | constexpr int b = b;
      |               ^   ~
array.cpp:603:19: note: read of object outside its lifetime is not allowed in a 
constant expression
  603 | constexpr int b = b;
      |                   ^
```

We do lose the "object" vs. "temporary" distinction in the note and only
mention it in the "created here" note. That can be added back if it's
important enough. I wasn't sure.

Added: 
    

Modified: 
    clang/include/clang/Basic/DiagnosticASTKinds.td
    clang/lib/AST/ByteCode/Interp.cpp
    clang/lib/AST/ExprConstant.cpp
    clang/test/AST/ByteCode/builtin-bit-cast.cpp
    clang/test/AST/ByteCode/functions.cpp
    clang/test/AST/ByteCode/lifetimes.cpp
    clang/test/SemaCXX/builtin-is-within-lifetime.cpp
    clang/test/SemaCXX/constant-expression-cxx14.cpp
    clang/test/SemaCXX/constant-expression-cxx2a.cpp
    clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/DiagnosticASTKinds.td 
b/clang/include/clang/Basic/DiagnosticASTKinds.td
index e90ee9d376e07..f36c02851a6a1 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -186,9 +186,6 @@ def access_kind_subobject : TextSubstitution<
 def access_kind_volatile : TextSubstitution<
   "%select{read of|read of|assignment to|increment of|decrement of|"
   "<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>}0">;
-def note_constexpr_lifetime_ended : Note<
-  "%sub{access_kind}0 %select{temporary|variable}1 whose "
-  "%plural{8:storage duration|:lifetime}0 has ended">;
 def note_constexpr_access_uninit : Note<
   "%sub{access_kind_subobject}0 "
   "%select{object outside its lifetime|uninitialized object}1 "

diff  --git a/clang/lib/AST/ByteCode/Interp.cpp 
b/clang/lib/AST/ByteCode/Interp.cpp
index b5a66ff6fbab3..826a26a0e6e94 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -458,7 +458,8 @@ bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer 
&Ptr,
       S.FFDiag(Src, diag::note_constexpr_access_deleted_object) << AK;
     } else if (!S.checkingPotentialConstantExpression()) {
       bool IsTemp = Ptr.isTemporary();
-      S.FFDiag(Src, diag::note_constexpr_lifetime_ended, 1) << AK << !IsTemp;
+      S.FFDiag(Src, diag::note_constexpr_access_uninit)
+          << AK << /*uninitialized=*/false << S.Current->getRange(OpPC);
 
       if (IsTemp)
         S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here);

diff  --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index c91261988434e..2a228d2896730 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -4635,8 +4635,8 @@ static CompleteObject findCompleteObject(EvalInfo &Info, 
const Expr *E,
     std::tie(Frame, Depth) =
         Info.getCallFrameAndDepth(LVal.getLValueCallIndex());
     if (!Frame) {
-      Info.FFDiag(E, diag::note_constexpr_lifetime_ended, 1)
-        << AK << LVal.Base.is<const ValueDecl*>();
+      Info.FFDiag(E, diag::note_constexpr_access_uninit, 1)
+          << AK << /*Indeterminate=*/false << E->getSourceRange();
       NoteLValueLocation(Info, LVal.Base);
       return CompleteObject();
     }

diff  --git a/clang/test/AST/ByteCode/builtin-bit-cast.cpp 
b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
index c1d29b2ca4c00..09a67e60fb3be 100644
--- a/clang/test/AST/ByteCode/builtin-bit-cast.cpp
+++ b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
@@ -527,7 +527,7 @@ constexpr unsigned short bad_bool9_to_short = 
__builtin_bit_cast(unsigned short,
 constexpr const intptr_t &returns_local() { return 0L; }
 
 // both-error@+2 {{constexpr variable 'test_nullptr_bad' must be initialized 
by a constant expression}}
-// both-note@+1 {{read of temporary whose lifetime has ended}}
+// both-note@+1 {{read of object outside its lifetime}}
 constexpr nullptr_t test_nullptr_bad = __builtin_bit_cast(nullptr_t, 
returns_local());
 
 #ifdef __SIZEOF_INT128__

diff  --git a/clang/test/AST/ByteCode/functions.cpp 
b/clang/test/AST/ByteCode/functions.cpp
index 328bd7f36c640..21d3ddaafaee3 100644
--- a/clang/test/AST/ByteCode/functions.cpp
+++ b/clang/test/AST/ByteCode/functions.cpp
@@ -315,7 +315,7 @@ namespace ReturnLocalPtr {
   }
 
   static_assert(p2() == 12, ""); // both-error {{not an integral constant 
expression}} \
-                                 // both-note {{read of variable whose 
lifetime has ended}}
+                                 // both-note {{read of object outside its 
lifetime}}
 }
 
 namespace VoidReturn {

diff  --git a/clang/test/AST/ByteCode/lifetimes.cpp 
b/clang/test/AST/ByteCode/lifetimes.cpp
index d3b02d215b442..96c868209c5b2 100644
--- a/clang/test/AST/ByteCode/lifetimes.cpp
+++ b/clang/test/AST/ByteCode/lifetimes.cpp
@@ -15,7 +15,7 @@ constexpr int dead1() {
     F2 = &F;
   } // Ends lifetime of F.
 
-  return F2->a; // expected-note {{read of variable whose lifetime has ended}} 
\
+  return F2->a; // expected-note {{read of object outside its lifetime}} \
                 // ref-note {{read of object outside its lifetime is not 
allowed in a constant expression}}
 }
 static_assert(dead1() == 1, ""); // both-error {{not an integral constant 
expression}} \
@@ -26,9 +26,8 @@ struct S {
   int &&r; // both-note {{reference member declared here}}
   int t;
   constexpr S() : r(0), t(r) {} // both-error {{reference member 'r' binds to 
a temporary object whose lifetime would be shorter than the lifetime of the 
constructed object}} \
-                                // ref-note {{read of object outside its 
lifetime is not allowed in a constant expression}} \
-                                // expected-note {{temporary created here}} \
-                                // expected-note {{read of temporary whose 
lifetime has ended}}
+                                // both-note {{read of object outside its 
lifetime is not allowed in a constant expression}} \
+                                // expected-note {{temporary created here}}
 };
 constexpr int k1 = S().t; // both-error {{must be initialized by a constant 
expression}} \
                           // both-note {{in call to}}
@@ -97,11 +96,11 @@ namespace CallScope {
   constexpr Q *out_of_lifetime(Q q) { return &q; } // both-warning {{address 
of stack}} \
                                                    // expected-note 
2{{declared here}}
   constexpr int k3 = out_of_lifetime({})->n; // both-error {{must be 
initialized by a constant expression}} \
-                                             // expected-note {{read of 
variable whose lifetime has ended}} \
+                                             // expected-note {{read of object 
outside its lifetime}} \
                                              // ref-note {{read of object 
outside its lifetime}}
 
   constexpr int k4 = out_of_lifetime({})->f(); // both-error {{must be 
initialized by a constant expression}} \
-                                               // expected-note {{member call 
on variable whose lifetime has ended}} \
+                                               // expected-note {{member call 
on object outside its lifetime}} \
                                                // ref-note {{member call on 
object outside its lifetime}}
 }
 

diff  --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp 
b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
index 62ff2681952ce..b47efc9f79630 100644
--- a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
+++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
@@ -390,10 +390,10 @@ constexpr T* test_dangling() {
 }
 static_assert(__builtin_is_within_lifetime(test_dangling<int>())); // 
expected-note {{in instantiation of function template specialization}}
 // expected-error@-1 {{static assertion expression is not an integral constant 
expression}}
-//   expected-note@-2 {{read of variable whose lifetime has ended}}
+//   expected-note@-2 {{read of object outside its lifetime}}
 static_assert(__builtin_is_within_lifetime(test_dangling<int[1]>())); // 
expected-note {{in instantiation of function template specialization}}
 // expected-error@-1 {{static assertion expression is not an integral constant 
expression}}
-//   expected-note@-2 {{read of variable whose lifetime has ended}}
+//   expected-note@-2 {{read of object outside its lifetime}}
 
 template<auto F>
 concept CanCallAndPassToIsWithinLifetime = 
std::bool_constant<__builtin_is_within_lifetime(F())>::value;

diff  --git a/clang/test/SemaCXX/constant-expression-cxx14.cpp 
b/clang/test/SemaCXX/constant-expression-cxx14.cpp
index 1fc6e5ec4cc55..68e15985d97b8 100644
--- a/clang/test/SemaCXX/constant-expression-cxx14.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx14.cpp
@@ -250,7 +250,7 @@ namespace subobject {
 namespace lifetime {
   constexpr int &&id(int &&n) { return static_cast<int&&>(n); }
   constexpr int &&dead() { return id(0); } // expected-note {{temporary 
created here}}
-  constexpr int bad() { int &&n = dead(); n = 1; return n; } // expected-note 
{{assignment to temporary whose lifetime has ended}}
+  constexpr int bad() { int &&n = dead(); n = 1; return n; } // expected-note 
{{assignment to object outside its lifetime}}
   static_assert(bad(), ""); // expected-error {{constant expression}} 
expected-note {{in call}}
 }
 

diff  --git a/clang/test/SemaCXX/constant-expression-cxx2a.cpp 
b/clang/test/SemaCXX/constant-expression-cxx2a.cpp
index b22a82c57ef06..4fcd243b4442c 100644
--- a/clang/test/SemaCXX/constant-expression-cxx2a.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx2a.cpp
@@ -1195,13 +1195,13 @@ namespace dtor_call {
 
   constexpr void destroy_after_lifetime2() {
     A *p = []{ A a; return &a; }(); // expected-warning {{}} expected-note 
{{declared here}}
-    p->~A(); // expected-note {{destruction of variable whose lifetime has 
ended}}
+    p->~A(); // expected-note {{destruction of object outside its lifetime}}
   }
   static_assert((destroy_after_lifetime2(), true)); // expected-error {{}} 
expected-note {{in call}}
 
   constexpr void destroy_after_lifetime3() {
     A *p = []{ return &(A&)(A&&)A(); }(); // expected-warning {{}} 
expected-note {{temporary created here}}
-    p->~A(); // expected-note {{destruction of temporary whose lifetime has 
ended}}
+    p->~A(); // expected-note {{destruction of object outside its lifetime}}
   }
   static_assert((destroy_after_lifetime3(), true)); // expected-error {{}} 
expected-note {{in call}}
 

diff  --git a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp 
b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
index 7a6d7cb353158..0a7668b1ed1fe 100644
--- a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
+++ b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
@@ -262,7 +262,7 @@ constexpr int ttn = test_to_nullptr();
 constexpr const long &returns_local() { return 0L; }
 
 // expected-error@+2 {{constexpr variable 'test_nullptr_bad' must be 
initialized by a constant expression}}
-// expected-note@+1 {{read of temporary whose lifetime has ended}}
+// expected-note@+1 {{read of object outside its lifetime}}
 constexpr nullptr_t test_nullptr_bad = __builtin_bit_cast(nullptr_t, 
returns_local());
 
 constexpr int test_indeterminate(bool read_indet) {


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to