Skip to content

Conversation

@usx95
Copy link
Contributor

@usx95 usx95 commented Dec 13, 2025

Followup on #107627
Fixes #62072
Fixes #172013

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.

Copy link
Contributor Author

usx95 commented Dec 13, 2025

@usx95 usx95 force-pushed the users/usx95/12-13-merge-attr-implicit-this branch 2 times, most recently from ce0a708 to 166a926 Compare December 13, 2025 10:13
@usx95 usx95 changed the title merge-attr-implicit-this [LifetimeSafety] Merge lifetimebound attribute on implicit 'this' across method redeclarations Dec 13, 2025
@usx95 usx95 marked this pull request as ready for review December 13, 2025 11:08
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:temporal-safety Issue/FR relating to the lifetime analysis in Clang (-Wdangling, -Wreturn-local-addr) labels Dec 13, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 13, 2025

@llvm/pr-subscribers-clang

Author: Utkarsh Saxena (usx95)

Changes

Followup on #107627
Fixes #62072
Fixes #172013

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.


Full diff: https://github.com/llvm/llvm-project/pull/172146.diff

3 Files Affected:

  • (modified) clang/lib/Sema/SemaDecl.cpp (+57-6)
  • (modified) clang/test/Sema/warn-lifetime-safety.cpp (+22)
  • (modified) clang/test/SemaCXX/attr-lifetimebound.cpp (+12)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 47bd7295e93f6..3dd62c1428271 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4469,6 +4469,52 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
   return true;
 }
 
+/// 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 *
+getLifetimeBoundAttrFromType(const TypeSourceInfo *TSI) {
+  if (!TSI)
+    return nullptr;
+
+  for (TypeLoc TL = TSI->getTypeLoc();;) {
+    auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
+    if (!ATL)
+      break;
+    if (auto *LBAttr = ATL.getAttrAs<LifetimeBoundAttr>())
+      return LBAttr;
+    TL = ATL.getModifiedLoc();
+  }
+  return nullptr;
+}
+
+/// 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 = getLifetimeBoundAttrFromType(OldTSI);
+  const LifetimeBoundAttr *NewLBAttr = getLifetimeBoundAttrFromType(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
@@ -4485,12 +4531,17 @@ 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))
+    if (auto *OldMethod = dyn_cast<CXXMethodDecl>(Old))
+      mergeLifetimeBoundAttrOnMethod(*this, NewMethod, OldMethod);
 
   if (getLangOpts().CPlusPlus)
     return MergeCXXFunctionDecl(New, Old, S);
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 1191469e23df1..658d68db44369 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -943,3 +943,25 @@ void parentheses(bool cond) {
   }  // expected-note 4 {{destroyed here}}
   (void)*p;  // expected-note 4 {{later used here}}
 }
+
+// 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..5b0aa0e518cb3 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -75,6 +75,18 @@ 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; }
+
+  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}}
+  }
+
   struct FieldCheck {
     struct Set {
       int a;

@llvmbot
Copy link
Member

llvmbot commented Dec 13, 2025

@llvm/pr-subscribers-clang-temporal-safety

Author: Utkarsh Saxena (usx95)

Changes

Followup on #107627
Fixes #62072
Fixes #172013

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.


Full diff: https://github.com/llvm/llvm-project/pull/172146.diff

3 Files Affected:

  • (modified) clang/lib/Sema/SemaDecl.cpp (+57-6)
  • (modified) clang/test/Sema/warn-lifetime-safety.cpp (+22)
  • (modified) clang/test/SemaCXX/attr-lifetimebound.cpp (+12)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 47bd7295e93f6..3dd62c1428271 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4469,6 +4469,52 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
   return true;
 }
 
+/// 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 *
+getLifetimeBoundAttrFromType(const TypeSourceInfo *TSI) {
+  if (!TSI)
+    return nullptr;
+
+  for (TypeLoc TL = TSI->getTypeLoc();;) {
+    auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
+    if (!ATL)
+      break;
+    if (auto *LBAttr = ATL.getAttrAs<LifetimeBoundAttr>())
+      return LBAttr;
+    TL = ATL.getModifiedLoc();
+  }
+  return nullptr;
+}
+
+/// 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 = getLifetimeBoundAttrFromType(OldTSI);
+  const LifetimeBoundAttr *NewLBAttr = getLifetimeBoundAttrFromType(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
@@ -4485,12 +4531,17 @@ 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))
+    if (auto *OldMethod = dyn_cast<CXXMethodDecl>(Old))
+      mergeLifetimeBoundAttrOnMethod(*this, NewMethod, OldMethod);
 
   if (getLangOpts().CPlusPlus)
     return MergeCXXFunctionDecl(New, Old, S);
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 1191469e23df1..658d68db44369 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -943,3 +943,25 @@ void parentheses(bool cond) {
   }  // expected-note 4 {{destroyed here}}
   (void)*p;  // expected-note 4 {{later used here}}
 }
+
+// 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..5b0aa0e518cb3 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -75,6 +75,18 @@ 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; }
+
+  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}}
+  }
+
   struct FieldCheck {
     struct Set {
       int a;

@usx95 usx95 requested review from Xazax-hun and ymand December 13, 2025 11:09
Comment on lines +4505 to +4517
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));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the opposite scenario?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to read mostRecentDecl for analysis and it would reflect all the merged attributes seen until now.

Comment on lines 4480 to 4487
for (TypeLoc TL = TSI->getTypeLoc();;) {
auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
if (!ATL)
break;
if (auto *LBAttr = ATL.getAttrAs<LifetimeBoundAttr>())
return LBAttr;
TL = ATL.getModifiedLoc();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a bit too clever - I'd prefer a while(true) with a comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@usx95 usx95 force-pushed the users/usx95/12-13-merge-attr-implicit-this branch from 166a926 to 066e874 Compare December 13, 2025 17:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:temporal-safety Issue/FR relating to the lifetime analysis in Clang (-Wdangling, -Wreturn-local-addr) clang Clang issues not falling into any other category

Projects

Status: No status

4 participants