https://github.com/tbaederr created
https://github.com/llvm/llvm-project/pull/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.
>From a663823398efbb71062f1c2973e7545ad86076d8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Mon, 12 Jan 2026 16:15:12 +0100
Subject: [PATCH] [clang][ExprConst]
---
clang/include/clang/Basic/DiagnosticASTKinds.td | 3 ---
clang/lib/AST/ByteCode/Interp.cpp | 3 ++-
clang/lib/AST/ExprConstant.cpp | 4 ++--
clang/test/AST/ByteCode/builtin-bit-cast.cpp | 2 +-
clang/test/AST/ByteCode/functions.cpp | 2 +-
clang/test/AST/ByteCode/lifetimes.cpp | 11 +++++------
clang/test/SemaCXX/builtin-is-within-lifetime.cpp | 4 ++--
clang/test/SemaCXX/constant-expression-cxx14.cpp | 2 +-
clang/test/SemaCXX/constant-expression-cxx2a.cpp | 4 ++--
clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp | 2 +-
10 files changed, 17 insertions(+), 20 deletions(-)
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 8618979d1eba0..10c7e731642ac 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