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

Reply via email to