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
