https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/176188
>From d89360c098e5ce23eada2490e5a13f0804017046 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Thu, 15 Jan 2026 15:40:13 +0000 Subject: [PATCH] Merge lifetimebound attribute on implicit 'this' across method redeclarations --- .../LifetimeSafety/LifetimeAnnotations.h | 1 + .../LifetimeSafety/LifetimeAnnotations.cpp | 35 +++-- .../Sema/warn-lifetime-analysis-nocfg.cpp | 138 ++++++++++++++++++ clang/test/Sema/warn-lifetime-safety.cpp | 22 +++ clang/test/SemaCXX/attr-lifetimebound.cpp | 21 +++ 5 files changed, 205 insertions(+), 12 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h index f96d412aa63d2..760d34d33b15b 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h @@ -10,6 +10,7 @@ #ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H #define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H +#include "clang/AST/Attr.h" #include "clang/AST/DeclCXX.h" namespace clang ::lifetimes { diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp index 2772fe20de19b..ced0ad537604a 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp @@ -52,22 +52,33 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD) { CMD->getParamDecl(0)->hasAttr<clang::LifetimeBoundAttr>(); } +/// Check if a function has a lifetimebound attribute on its function type +/// (which represents the implicit 'this' parameter for methods). +/// Returns the attribute if found, nullptr otherwise. +static const LifetimeBoundAttr * +getLifetimeBoundAttrFromFunctionType(const TypeSourceInfo &TSI) { + // Walk through the type layers looking for a lifetimebound attribute. + TypeLoc TL = TSI.getTypeLoc(); + while (true) { + auto ATL = TL.getAsAdjusted<AttributedTypeLoc>(); + if (!ATL) + break; + if (auto *LBAttr = ATL.getAttrAs<LifetimeBoundAttr>()) + return LBAttr; + TL = ATL.getModifiedLoc(); + } + return nullptr; +} + bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) { FD = getDeclWithMergedLifetimeBoundAttrs(FD); - const TypeSourceInfo *TSI = FD->getTypeSourceInfo(); - if (!TSI) - return false; - // Don't declare this variable in the second operand of the for-statement; - // GCC miscompiles that by ending its lifetime before evaluating the - // third operand. See gcc.gnu.org/PR86769. - AttributedTypeLoc ATL; - for (TypeLoc TL = TSI->getTypeLoc(); - (ATL = TL.getAsAdjusted<AttributedTypeLoc>()); - TL = ATL.getModifiedLoc()) { - if (ATL.getAttrAs<clang::LifetimeBoundAttr>()) + // Attribute merging doesn't work well with attributes on function types (like + // 'this' param). We need to check all redeclarations. + for (const FunctionDecl *Redecl : FD->redecls()) { + const TypeSourceInfo *TSI = Redecl->getTypeSourceInfo(); + if (TSI && getLifetimeBoundAttrFromFunctionType(*TSI)) return true; } - return isNormalAssignmentOperator(FD); } diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp index 7fdc493dbd17a..441f9fc602916 100644 --- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp @@ -1039,3 +1039,141 @@ const char* foo() { } } // namespace GH127195 + +// Lifetimebound on definition vs declaration on implicit this param. +namespace GH175391 { +// Version A: Attribute on declaration only +class StringA { +public: + const char* data() const [[clang::lifetimebound]]; // Declaration with attribute +private: + char buffer[32] = "hello"; +}; +inline const char* StringA::data() const { // Definition WITHOUT attribute + return buffer; +} + +// Version B: Attribute on definition only +class StringB { +public: + const char* data() const; // No attribute +private: + char buffer[32] = "hello"; +}; +inline const char* StringB::data() const [[clang::lifetimebound]] { + return buffer; +} + +// Version C: Attribute on BOTH declaration and definition +class StringC { +public: + const char* data() const [[clang::lifetimebound]]; +private: + char buffer[32] = "hello"; +}; +inline const char* StringC::data() const [[clang::lifetimebound]] { + return buffer; +} + +// TEMPLATED VERSIONS + +// Template Version A: Attribute on declaration only +template<typename T> +class StringTemplateA { +public: + const T* data() const [[clang::lifetimebound]]; // Declaration with attribute +private: + T buffer[32]; +}; +template<typename T> +inline const T* StringTemplateA<T>::data() const { // Definition WITHOUT attribute + return buffer; +} + +// Template Version B: Attribute on definition only +template<typename T> +class StringTemplateB { +public: + const T* data() const; // No attribute +private: + T buffer[32]; +}; +template<typename T> +inline const T* StringTemplateB<T>::data() const [[clang::lifetimebound]] { + return buffer; +} + +// Template Version C: Attribute on BOTH declaration and definition +template<typename T> +class StringTemplateC { +public: + const T* data() const [[clang::lifetimebound]]; +private: + T buffer[32]; +}; +template<typename T> +inline const T* StringTemplateC<T>::data() const [[clang::lifetimebound]] { + return buffer; +} + +// TEMPLATE SPECIALIZATION VERSIONS + +// Template predeclarations for specializations +template<typename T> class StringTemplateSpecA; +template<typename T> class StringTemplateSpecB; +template<typename T> class StringTemplateSpecC; + +// Template Specialization Version A: Attribute on declaration only - <char> specialization +template<> +class StringTemplateSpecA<char> { +public: + const char* data() const [[clang::lifetimebound]]; // Declaration with attribute +private: + char buffer[32] = "hello"; +}; +inline const char* StringTemplateSpecA<char>::data() const { // Definition WITHOUT attribute + return buffer; +} + +// Template Specialization Version B: Attribute on definition only - <char> specialization +template<> +class StringTemplateSpecB<char> { +public: + const char* data() const; // No attribute +private: + char buffer[32] = "hello"; +}; +inline const char* StringTemplateSpecB<char>::data() const [[clang::lifetimebound]] { + return buffer; +} + +// Template Specialization Version C: Attribute on BOTH declaration and definition - <char> specialization +template<> +class StringTemplateSpecC<char> { +public: + const char* data() const [[clang::lifetimebound]]; +private: + char buffer[32] = "hello"; +}; +inline const char* StringTemplateSpecC<char>::data() const [[clang::lifetimebound]] { + return buffer; +} + +void test() { + // Non-templated tests + const auto ptrA = StringA().data(); // Declaration-only attribute // expected-warning {{temporary whose address is used}} + const auto ptrB = StringB().data(); // Definition-only attribute // expected-warning {{temporary whose address is used}} + const auto ptrC = StringC().data(); // Both have attribute // expected-warning {{temporary whose address is used}} + + // Templated tests (generic templates) + const auto ptrTA = StringTemplateA<char>().data(); // Declaration-only attribute // expected-warning {{temporary whose address is used}} + // FIXME: Definition is not instantiated until the end of TU. The attribute is not merged when this call is processed. + const auto ptrTB = StringTemplateB<char>().data(); // Definition-only attribute + const auto ptrTC = StringTemplateC<char>().data(); // Both have attribute // expected-warning {{temporary whose address is used}} + + // Template specialization tests + const auto ptrTSA = StringTemplateSpecA<char>().data(); // Declaration-only attribute // expected-warning {{temporary whose address is used}} + const auto ptrTSB = StringTemplateSpecB<char>().data(); // Definition-only attribute // expected-warning {{temporary whose address is used}} + const auto ptrTSC = StringTemplateSpecC<char>().data(); // Both have attribute // expected-warning {{temporary whose address is used}} +} +} // namespace GH175391 diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 0b1962b7cb651..24ac72e7aad4d 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -1431,3 +1431,25 @@ void not_silenced_via_conditional(bool cond) { (void)v; // expected-note 2 {{later used here}} } } // namespace do_not_warn_on_std_move + +// Implicit this annotations with redecls. +namespace GH172013 { +// https://github.com/llvm/llvm-project/issues/62072 +// https://github.com/llvm/llvm-project/issues/172013 +struct S { + View x() const [[clang::lifetimebound]]; + MyObj i; +}; + +View S::x() const { return i; } + +void bar() { + View x; + { + S s; + x = s.x(); // expected-warning {{object whose reference is captured does not live long enough}} + View y = S().x(); // FIXME: Handle temporaries. + } // expected-note {{destroyed here}} + (void)x; // expected-note {{used here}} +} +} diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp index 111bad65f7e1b..9e2aaff6559c4 100644 --- a/clang/test/SemaCXX/attr-lifetimebound.cpp +++ b/clang/test/SemaCXX/attr-lifetimebound.cpp @@ -75,6 +75,27 @@ namespace usage_ok { r = A(1); // expected-warning {{object backing the pointer 'r' will be destroyed at the end of the full-expression}} } + // Test that lifetimebound on implicit 'this' is propagated across redeclarations + struct B { + int *method() [[clang::lifetimebound]]; + int i; + }; + int *B::method() { return &i; } + + // Test that lifetimebound on implicit 'this' is propagated across redeclarations + struct C { + int *method(); + int i; + }; + int *C::method() [[clang::lifetimebound]] { return &i; } + + void test_lifetimebound_on_implicit_this() { + int *t = B().method(); // expected-warning {{temporary whose address is used as value of local variable 't' will be destroyed at the end of the full-expression}} + t = {B().method()}; // expected-warning {{object backing the pointer 't' will be destroyed at the end of the full-expression}} + t = C().method(); // expected-warning {{object backing the pointer 't' will be destroyed at the end of the full-expression}} + t = {C().method()}; // expected-warning {{object backing the pointer 't' will be destroyed at the end of the full-expression}} + } + struct FieldCheck { struct Set { int a; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
