https://github.com/kashika0112 updated 
https://github.com/llvm/llvm-project/pull/219423

>From 578069cc984ca04d7eb3e1e11a0014bf996b86d1 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <[email protected]>
Date: Fri, 28 Aug 2026 09:07:38 +0000
Subject: [PATCH 1/2] Add support for capture_by on method declarations

---
 .../LifetimeSafety/LifetimeAnnotations.h      |  6 +++
 .../LifetimeSafety/FactsGenerator.cpp         | 40 +++++++-------
 .../LifetimeSafety/LifetimeAnnotations.cpp    | 18 +++++++
 clang/lib/Analysis/LifetimeSafety/Origins.cpp | 36 +++++++------
 clang/test/Sema/LifetimeSafety/capture-by.cpp | 53 +++++++++++++++++--
 5 files changed, 115 insertions(+), 38 deletions(-)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 6f1259fa867dc..dd60ea2ee0660 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -60,6 +60,12 @@ getImplicitObjectParamLifetimeBoundAttr(const FunctionDecl 
*FD);
 /// method or because it's a normal assignment operator.
 bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
 
+/// Check if a function has a lifetime_capture_by attribute on its declaration
+/// or its function type (which represents the implicit 'this' parameter for
+/// methods). Returns the attribute if found, nullptr otherwise.
+const LifetimeCaptureByAttr *
+getCaptureByAttrFromFunctionType(const FunctionDecl *FD);
+
 using LifetimeBoundParamInfo =
     llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>;
 
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index ce3039528b283..0c2f414409be4 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -1033,22 +1033,31 @@ void FactsGenerator::handleLifetimeCaptureBy(const 
FunctionDecl *FD,
   const auto *Method = dyn_cast<CXXMethodDecl>(FD);
   bool IsInstance =
       Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD);
-  auto getParamDeclAt = [FD, IsInstance](unsigned I) -> const ParmVarDecl * {
+  auto getArgCaptureByAttr =
+      [FD, IsInstance](
+          unsigned I) -> std::pair<const LifetimeCaptureByAttr *, QualType> {
     if (IsInstance) {
-      // FIXME: Add support for I == 0 i.e. capture_by on function declarations
-      if (I > 0 && I - 1 < FD->getNumParams())
-        return FD->getParamDecl(I - 1);
+      if (I == 0) {
+        auto *MethodDecl = cast<CXXMethodDecl>(FD);
+        QualType ParamType = MethodDecl->getFunctionObjectParameterType();
+        return {getCaptureByAttrFromFunctionType(FD), ParamType};
+      }
+      if (I > 0 && I - 1 < FD->getNumParams()) {
+        const ParmVarDecl *PVD = FD->getParamDecl(I - 1);
+        return {PVD ? PVD->getAttr<LifetimeCaptureByAttr>() : nullptr,
+                PVD->getType()};
+      }
     } else {
-      if (I < FD->getNumParams())
-        return FD->getParamDecl(I);
+      if (I < FD->getNumParams()) {
+        const ParmVarDecl *PVD = FD->getParamDecl(I);
+        return {PVD ? PVD->getAttr<LifetimeCaptureByAttr>() : nullptr,
+                PVD->getType()};
+      }
     }
-    return nullptr;
+    return {nullptr, QualType()};
   };
   for (unsigned I = 0; I < Args.size(); ++I) {
-    const ParmVarDecl *PVD = getParamDeclAt(I);
-    if (!PVD)
-      continue;
-    const auto *Attr = PVD->getAttr<LifetimeCaptureByAttr>();
+    auto [Attr, ParamType] = getArgCaptureByAttr(I);
     if (!Attr)
       continue;
     OriginList *CapturedOriginList = getOriginsList(*Args[I]);
@@ -1056,7 +1065,7 @@ void FactsGenerator::handleLifetimeCaptureBy(const 
FunctionDecl *FD,
       continue;
     // For references to pointer-like types, peel the outer origin (the pointer
     // object itself) so that we capture the underlying data (the inner 
origin).
-    if (QualType ParamType = PVD->getType();
+    if (!ParamType.isNull() &&
         (ParamType->isReferenceType() &&
          isPointerLikeType(ParamType->getPointeeType())) &&
         CapturedOriginList->getLength() > 1)
@@ -1067,13 +1076,8 @@ void FactsGenerator::handleLifetimeCaptureBy(const 
FunctionDecl *FD,
           CapturingArgIdx == LifetimeCaptureByAttr::Unknown ||
           CapturingArgIdx == LifetimeCaptureByAttr::Invalid)
         continue;
-      ArrayRef<const Expr *> CallArgs = IsInstance ? Args.drop_front() : Args;
-      const Expr *CapturedByArg =
-          (CapturingArgIdx == LifetimeCaptureByAttr::This)
-              ? Args[0]
-              : CallArgs[CapturingArgIdx];
+      const Expr *CapturedByArg = Args[CapturingArgIdx];
       assert(CapturedByArg && "Capturer expression must be valid");
-
       OriginList *CapturingOriginList = getOriginsList(*CapturedByArg);
       OriginList *Dest = getRValueOrigins(CapturedByArg, CapturingOriginList);
       if (!Dest)
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp 
b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 154657193cabc..bef5d2ad3ec72 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -136,6 +136,24 @@ FunctionCallInfo getFunctionCallInfo(const Expr *Call) {
   return Info;
 }
 
+const LifetimeCaptureByAttr *
+getCaptureByAttrFromFunctionType(const FunctionDecl *FD) {
+  const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+  if (!TSI)
+    return nullptr;
+  // Walk through the type layers looking for a capture_by attribute.
+  TypeLoc TL = TSI->getTypeLoc();
+  while (true) {
+    auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
+    if (!ATL)
+      break;
+    if (auto *Attr = ATL.getAttrAs<LifetimeCaptureByAttr>())
+      return Attr;
+    TL = ATL.getModifiedLoc();
+  }
+  return nullptr;
+}
+
 std::optional<LifetimeBoundParamInfo>
 getTrackedArgInfo(const FunctionDecl *FD, llvm::ArrayRef<const Expr *> Args,
                   unsigned I) {
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp 
b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 0c0c280d73cb6..4b528f463db7e 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -107,23 +107,29 @@ class LifetimeAnnotatedOriginTypeCollector
     const auto *MD = dyn_cast<CXXMethodDecl>(FD);
     bool IsInstance = MD && MD->isInstance();
     int Offset = (MD && MD->isImplicitObjectMemberFunction()) ? 1 : 0;
+    auto ProcessAttr = [&](const LifetimeCaptureByAttr *Attr) {
+      for (int Idx : Attr->params()) {
+        if (Idx == LifetimeCaptureByAttr::Global ||
+            Idx == LifetimeCaptureByAttr::Unknown ||
+            Idx == LifetimeCaptureByAttr::Invalid)
+          continue;
+        if (Idx == LifetimeCaptureByAttr::This) {
+          if (IsInstance)
+            CollectedTypes.push_back(MD->getFunctionObjectParameterType());
+        } else if (int LogicalIdx = Idx - Offset;
+                   LogicalIdx >= 0 &&
+                   (unsigned)LogicalIdx < FD->getNumParams()) {
+          CollectedTypes.push_back(
+              FD->getParamDecl(LogicalIdx)->getType().getNonReferenceType());
+        }
+      }
+    };
+    if (const auto *MethodAttr =
+            IsInstance ? getCaptureByAttrFromFunctionType(FD) : nullptr)
+      ProcessAttr(MethodAttr);
     for (const auto *Param : FD->parameters()) {
       if (auto *Attr = Param->getAttr<LifetimeCaptureByAttr>()) {
-        for (int Idx : Attr->params()) {
-          if (Idx == LifetimeCaptureByAttr::Global ||
-              Idx == LifetimeCaptureByAttr::Unknown ||
-              Idx == LifetimeCaptureByAttr::Invalid)
-            continue;
-          if (Idx == LifetimeCaptureByAttr::This) {
-            if (IsInstance)
-              CollectedTypes.push_back(MD->getFunctionObjectParameterType());
-          } else if (int LogicalIdx = Idx - Offset;
-                     LogicalIdx >= 0 &&
-                     (unsigned)LogicalIdx < FD->getNumParams()) {
-            CollectedTypes.push_back(
-                FD->getParamDecl(LogicalIdx)->getType().getNonReferenceType());
-          }
-        }
+        ProcessAttr(Attr);
       }
     }
   }
diff --git a/clang/test/Sema/LifetimeSafety/capture-by.cpp 
b/clang/test/Sema/LifetimeSafety/capture-by.cpp
index e9e745b52ec0b..e6b0cb2e24e36 100644
--- a/clang/test/Sema/LifetimeSafety/capture-by.cpp
+++ b/clang/test/Sema/LifetimeSafety/capture-by.cpp
@@ -252,19 +252,62 @@ void initializer_list_capture() {
 // Implicit object param 'this' is captured
 // ****************************************************************************
 namespace this_is_captured {
-struct X {} x;
+struct X {} x;    // cfg-note {{this global dangles}}
 struct S {
   void capture(X &x) [[clang::lifetime_capture_by(x)]];
 };
 
-// FIXME: Add support for capture of method declarations in -Wlifetime-safety
 void use() {
-  S{}.capture(x); // expected-warning {{object whose reference is captured by 
'x' will be destroyed at the end of the full-expression}}
-  S s;
-  s.capture(x);
+  S{}.capture(x); // expected-warning {{object whose reference is captured by 
'x' will be destroyed at the end of the full-expression}} \
+                  // cfg-warning {{temporary object does not live long 
enough}} \
+                  // cfg-note {{temporary object is destroyed here}}
+  (void)x;        // cfg-note {{later used here}}
+  S s;            
+  s.capture(x);   // cfg-warning {{stack memory associated with local variable 
's' escapes to the global variable 'x' which will dangle}}
 }
 } // namespace this_is_captured
 
+namespace method_decl_capture {
+struct Container {
+  const void* stored = nullptr;
+  void add(const void* s) { stored = s; }
+};
+struct Obj {
+  void register_into(Container& c) const [[clang::lifetime_capture_by(c)]] {
+    c.add(this);
+  }
+};
+void test() {
+  Container c;
+  {
+    Obj local_obj;
+    local_obj.register_into(c);   // cfg-warning {{local variable 'local_obj' 
does not live long enough}}
+  }                               // cfg-note {{local variable 'local_obj' is 
destroyed here}}
+  (void)c.stored;                 // cfg-note {{later used here}}
+}
+} // namespace method_decl_capture
+
+namespace method_capture_chaining {
+struct Container {
+  const void* stored = nullptr;
+  void capture(const void* v [[clang::lifetime_capture_by_this]]);
+};
+struct Obj {
+  const void* data = nullptr;
+  void addTo(Container& c) const [[clang::lifetime_capture_by(c)]] {
+    c.capture(data);
+  }
+};
+void test() {
+  Container c;
+  {
+    Obj local_obj;
+    local_obj.addTo(c);           // cfg-warning {{local variable 'local_obj' 
does not live long enough}}
+  }                               // cfg-note {{local variable 'local_obj' is 
destroyed here}}
+  (void)c.stored;                 // cfg-note {{later used here}}
+}
+} // namespace method_capture_chaining
+
 namespace temporary_capturing_object {
 struct S {
   void add(const int& x [[clang::lifetime_capture_by_this]]);

>From 73b58e4e1b049d04b4691599ba25cf17e598ec09 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <[email protected]>
Date: Mon, 31 Aug 2026 09:37:27 +0000
Subject: [PATCH 2/2] Add header

---
 .../clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h | 1 +
 1 file changed, 1 insertion(+)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index f937e2b76743d..fdc5b1f8cce14 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"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/PointerUnion.h"

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to