Author: Utkarsh Saxena Date: 2026-01-13T13:51:44+05:30 New Revision: ef90ba684d012790c86ac1b5e7c6b325abe78803
URL: https://github.com/llvm/llvm-project/commit/ef90ba684d012790c86ac1b5e7c6b325abe78803 DIFF: https://github.com/llvm/llvm-project/commit/ef90ba684d012790c86ac1b5e7c6b325abe78803.diff LOG: [LifetimeSafety] Merge lifetimebound attribute on implicit 'this' across method redeclarations (#172146) Followup on https://github.com/llvm/llvm-project/pull/107627 Fixes https://github.com/llvm/llvm-project/issues/62072 Fixes https://github.com/llvm/llvm-project/issues/172013 Fixes https://github.com/llvm/llvm-project/issues/175391 This PR adds support for merging the `lifetimebound` attribute on the implicit `this` parameter when merging method declarations. Previously, if a method was declared with `lifetimebound` on its function type (which represents the implicit `this` parameter), this attribute would not be propagated to the method definition, causing lifetime safety warnings to be missed. The implementation adds helper functions to extract the `lifetimebound` attribute from a function type and to merge this attribute from an old method declaration to a new one when appropriate. Added: Modified: clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp clang/lib/Sema/SemaDecl.cpp clang/test/Sema/warn-lifetime-analysis-nocfg.cpp clang/test/Sema/warn-lifetime-safety.cpp clang/test/SemaCXX/attr-lifetimebound.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h index f96d412aa63d2..fea49b768e59e 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 { @@ -45,6 +46,12 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD); /// method or because it's a normal assignment operator. bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD); +/// 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. +const LifetimeBoundAttr * +getLifetimeBoundAttrFromFunctionType(const TypeSourceInfo &TSI); + // Returns true if the implicit object argument (this) of a method call should // be tracked for GSL lifetime analysis. This applies to STL methods that return // pointers or references that depend on the lifetime of the object, such as diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp index 2772fe20de19b..e07ef0ccb2f75 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp @@ -52,23 +52,28 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD) { CMD->getParamDecl(0)->hasAttr<clang::LifetimeBoundAttr>(); } +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>()) - return true; - } - - return isNormalAssignmentOperator(FD); + return getLifetimeBoundAttrFromFunctionType(*TSI) != nullptr || + isNormalAssignmentOperator(FD); } bool isInStlNamespace(const Decl *D) { diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index e778bb8c9ce82..f9580b34aaec6 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -28,6 +28,7 @@ #include "clang/AST/Randstruct.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/Type.h" +#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/DiagnosticComment.h" #include "clang/Basic/HLSLRuntime.h" @@ -4470,6 +4471,35 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S, return true; } +/// Merge lifetimebound attribute on function type (implicit 'this') +/// from Old to New method declaration. +static void mergeLifetimeBoundAttrOnMethod(Sema &S, CXXMethodDecl *New, + const CXXMethodDecl *Old) { + const TypeSourceInfo *OldTSI = Old->getTypeSourceInfo(); + const TypeSourceInfo *NewTSI = New->getTypeSourceInfo(); + + if (!OldTSI || !NewTSI) + return; + + const LifetimeBoundAttr *OldLBAttr = + lifetimes::getLifetimeBoundAttrFromFunctionType(*OldTSI); + const LifetimeBoundAttr *NewLBAttr = + lifetimes::getLifetimeBoundAttrFromFunctionType(*NewTSI); + + // If Old has lifetimebound but New doesn't, add it to New. + if (OldLBAttr && !NewLBAttr) { + QualType NewMethodType = New->getType(); + QualType AttributedType = + S.Context.getAttributedType(OldLBAttr, NewMethodType, NewMethodType); + TypeLocBuilder TLB; + TLB.pushFullCopy(NewTSI->getTypeLoc()); + AttributedTypeLoc TyLoc = TLB.push<AttributedTypeLoc>(AttributedType); + TyLoc.setAttr(OldLBAttr); + New->setType(AttributedType); + New->setTypeSourceInfo(TLB.getTypeSourceInfo(S.Context, AttributedType)); + } +} + bool Sema::MergeCompatibleFunctionDecls(FunctionDecl *New, FunctionDecl *Old, Scope *S, bool MergeTypeWithOld) { // Merge the attributes @@ -4486,12 +4516,16 @@ bool Sema::MergeCompatibleFunctionDecls(FunctionDecl *New, FunctionDecl *Old, // Merge attributes from the parameters. These can mismatch with K&R // declarations. if (New->getNumParams() == Old->getNumParams()) - for (unsigned i = 0, e = New->getNumParams(); i != e; ++i) { - ParmVarDecl *NewParam = New->getParamDecl(i); - ParmVarDecl *OldParam = Old->getParamDecl(i); - mergeParamDeclAttributes(NewParam, OldParam, *this); - mergeParamDeclTypes(NewParam, OldParam, *this); - } + for (unsigned i = 0, e = New->getNumParams(); i != e; ++i) { + ParmVarDecl *NewParam = New->getParamDecl(i); + ParmVarDecl *OldParam = Old->getParamDecl(i); + mergeParamDeclAttributes(NewParam, OldParam, *this); + mergeParamDeclTypes(NewParam, OldParam, *this); + } + + // Merge function type attributes (e.g., lifetimebound on implicit 'this'). + if (auto *NewMethod = dyn_cast<CXXMethodDecl>(New)) + mergeLifetimeBoundAttrOnMethod(*this, NewMethod, cast<CXXMethodDecl>(Old)); if (getLangOpts().CPlusPlus) return MergeCXXFunctionDecl(New, Old, S); diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp index 7634dbf2f6733..5ada22e721912 100644 --- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp @@ -876,3 +876,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 5706d195c383b..59cd23e281bfc 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -1358,3 +1358,25 @@ void add(int c, MyObj* node) { arr[4] = node; } } // namespace CppCoverage + +// 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
