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

>From caf2b8db8a05245b2d085f9f26519cfeec8eeffd Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <[email protected]>
Date: Mon, 11 May 2026 06:40:43 +0000
Subject: [PATCH 1/3] Add support for lifetime capture_by

---
 .../Analyses/LifetimeSafety/FactsGenerator.h  |  5 ++
 .../LifetimeSafety/FactsGenerator.cpp         | 69 ++++++++++++++++++
 clang/test/Sema/warn-lifetime-safety.cpp      | 73 +++++++++++++++++++
 3 files changed, 147 insertions(+)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 766742e98101a..af545d5e9386c 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -91,6 +91,11 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
   void handleMovedArgsInCall(const FunctionDecl *FD,
                              ArrayRef<const Expr *> Args);
 
+  // Detects [[clang::lifetime_capture_by(X)]] annotations on a function call 
to
+  // create flow facts from captured arguments to the capturer
+  void handleLifetimeCaptureBy(const FunctionDecl *FD,
+                               ArrayRef<const Expr *> Args);
+
   /// Checks if a call-like expression creates a borrow by passing a value to a
   /// reference parameter, creating an IssueFact if it does.
   /// \param IsGslConstruction True if this is a GSL construction where all
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index dc80d2783fc03..2908a9f1dd924 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -864,6 +864,74 @@ void FactsGenerator::handleImplicitObjectFieldUses(const 
Expr *Call,
   });
 }
 
+void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD,
+                                             ArrayRef<const Expr *> Args) {
+  bool isInstance = false;
+  if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
+      Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) {
+    isInstance = true;
+  }
+  auto getArgCaptureBy = [FD,
+                          isInstance](unsigned I) -> LifetimeCaptureByAttr * {
+    const ParmVarDecl *PVD = nullptr;
+    // FIXME: Add support for capture_by on function declarations
+    if (isInstance) {
+      if (I > 0 && I - 1 < FD->getNumParams()) {
+        PVD = FD->getParamDecl(I - 1);
+      }
+    } else {
+      if (I < FD->getNumParams()) {
+        PVD = FD->getParamDecl(I);
+      }
+    }
+    return PVD ? PVD->getAttr<LifetimeCaptureByAttr>() : nullptr;
+  };
+  if (Args.empty())
+    return;
+  for (unsigned I = 0; I < Args.size(); ++I) {
+    const LifetimeCaptureByAttr *Attr = getArgCaptureBy(I);
+    if (!Attr)
+      continue;
+    OriginList *CapturedOriginList = getOriginsList(*Args[I]);
+    if (!CapturedOriginList)
+      continue;
+    if (isGslPointerType(Args[I]->getType())) {
+      assert(!Args[I]->isGLValue() || CapturedOriginList->getLength() >= 2);
+      CapturedOriginList = getRValueOrigins(Args[I], CapturedOriginList);
+    }
+    if (!CapturedOriginList)
+      continue;
+    for (int CapturedByIdx : Attr->params()) {
+      if (CapturedByIdx == LifetimeCaptureByAttr::Global) {
+        for (OriginList *L = CapturedOriginList; L != nullptr;
+             L = L->peelOuterOrigin())
+          EscapesInCurrentBlock.push_back(FactMgr.createFact<GlobalEscapeFact>(
+              L->getOuterOriginID(), nullptr));
+        continue;
+      }
+      if (CapturedByIdx == LifetimeCaptureByAttr::Unknown ||
+          CapturedByIdx == LifetimeCaptureByAttr::Invalid)
+        continue;
+      unsigned CapturedByArgIdx =
+          (CapturedByIdx == LifetimeCaptureByAttr::This)
+              ? 0
+              : (unsigned)CapturedByIdx + (isInstance ? 1 : 0);
+      if (CapturedByArgIdx >= Args.size())
+        continue;
+      OriginList *CapturedByOriginList =
+          getOriginsList(*Args[CapturedByArgIdx]);
+      if (CapturedByOriginList) {
+        OriginList *Dest =
+            (isGslPointerType(Args[CapturedByArgIdx]->getType()) ||
+             isGslOwnerType(Args[CapturedByArgIdx]->getType()))
+                ? CapturedByOriginList->peelOuterOrigin()
+                : CapturedByOriginList;
+        flow(Dest, CapturedOriginList, /*Kill=*/false);
+      }
+    }
+  }
+}
+
 void FactsGenerator::handleFunctionCall(const Expr *Call,
                                         const FunctionDecl *FD,
                                         ArrayRef<const Expr *> Args,
@@ -880,6 +948,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
   handleDestructiveCall(Call, FD, Args);
   handleMovedArgsInCall(FD, Args);
   handleImplicitObjectFieldUses(Call, FD);
+  handleLifetimeCaptureBy(FD, Args);
   if (!CallList)
     return;
   auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 30b450c333fbd..aaef28a85f507 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -3284,3 +3284,76 @@ void assign_non_capturing_to_function_ref(function_ref 
&r) {
 }
 
 } // namespace GH126600
+
+//===----------------------------------------------------------------------===//
+// lifetime-capture-by
+//===----------------------------------------------------------------------===//
+
+void setCaptureBy(View& in, View out [[clang::lifetime_capture_by(in)]]);
+
+void use_after_free_capture_by() {
+  View res;
+  {
+    MyObj a;      
+    setCaptureBy(res, a); // expected-warning {{object whose reference is 
captured does not live long enough}}
+  }               // expected-note {{destroyed here}}
+  (void)res;      // expected-note {{later used here}}      
+}
+
+View use_after_return_capture_by() {
+  MyObj a;
+  View res;
+  setCaptureBy(res, a);   // expected-warning {{address of stack memory is 
returned later}}
+  return res;     // expected-note {{returned here}}  
+                   
+}
+
+void transitive_capture() {
+  View v1, v2;
+  {
+    MyObj local;
+    setCaptureBy(v1, local); // expected-warning {{object whose reference is 
captured does not live long enough}}
+    setCaptureBy(v2, v1);   
+  }                 // expected-note {{destroyed here}}
+  (void)v2;         // expected-note {{later used here}}   
+}
+
+struct [[gsl::Pointer]] MyContainer {
+  View stored;
+  void set(View s [[clang::lifetime_capture_by(this)]]);
+};
+
+void member_capture() {
+  MyContainer c;
+  {
+    MyObj local;
+    c.set(local);   // expected-warning {{object whose reference is captured 
does not live long enough}}
+  }                 // expected-note {{destroyed here}}
+  (void)c.stored;   // expected-note {{later used here}}
+}
+
+void captureTwo(View& into, 
+                View a [[clang::lifetime_capture_by(into)]], 
+                View b [[clang::lifetime_capture_by(into)]]);
+
+void multiple_captures() {
+  View res;
+  MyObj val1;
+  {
+    MyObj val2;
+    captureTwo(res, val1, val2); // expected-warning {{object whose reference 
is captured does not live long enough}}
+  }                              // expected-note {{destroyed here}}
+  (void)res;                     // expected-note {{later used here}}          
    
+}
+
+void multiple_local_captures() {
+  View res;
+  {
+    MyObj val1;
+    MyObj val2;
+    captureTwo(res, val1, val2); // expected-warning 2 {{object whose 
reference is captured does not live long enough}}
+  }                              // expected-note 2 {{destroyed here}}
+  (void)res;                     // expected-note 2 {{later used here}}        
      
+}
+
+

>From 4311c1f4a8ed347bca632eca058880e72b074fca Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <[email protected]>
Date: Thu, 21 May 2026 10:09:28 +0000
Subject: [PATCH 2/3] Modify origin flow logic and add more tests

---
 .../LifetimeSafety/FactsGenerator.cpp         | 65 ++++++++-----------
 clang/test/Sema/warn-lifetime-safety.cpp      | 51 ++++++++++++++-
 2 files changed, 78 insertions(+), 38 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 2908a9f1dd924..e126fcd6101a3 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -866,28 +866,27 @@ void FactsGenerator::handleImplicitObjectFieldUses(const 
Expr *Call,
 
 void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD,
                                              ArrayRef<const Expr *> Args) {
-  bool isInstance = false;
-  if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
-      Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) {
-    isInstance = true;
-  }
+  if (Args.empty())
+    return;
+  // FIXME: Add support for capture_by on constructors.
+  if (isa<CXXConstructorDecl>(FD))
+    return;
+  const auto *Method = dyn_cast<CXXMethodDecl>(FD);
+  bool IsInstance =
+      Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD);
   auto getArgCaptureBy = [FD,
-                          isInstance](unsigned I) -> LifetimeCaptureByAttr * {
+                          IsInstance](unsigned I) -> LifetimeCaptureByAttr * {
     const ParmVarDecl *PVD = nullptr;
-    // FIXME: Add support for capture_by on function declarations
-    if (isInstance) {
-      if (I > 0 && I - 1 < FD->getNumParams()) {
+    if (IsInstance) {
+      // FIXME: Add support for capture_by on function declarations
+      if (I > 0 && I - 1 < FD->getNumParams())
         PVD = FD->getParamDecl(I - 1);
-      }
     } else {
-      if (I < FD->getNumParams()) {
+      if (I < FD->getNumParams())
         PVD = FD->getParamDecl(I);
-      }
     }
     return PVD ? PVD->getAttr<LifetimeCaptureByAttr>() : nullptr;
   };
-  if (Args.empty())
-    return;
   for (unsigned I = 0; I < Args.size(); ++I) {
     const LifetimeCaptureByAttr *Attr = getArgCaptureBy(I);
     if (!Attr)
@@ -902,32 +901,24 @@ void FactsGenerator::handleLifetimeCaptureBy(const 
FunctionDecl *FD,
     if (!CapturedOriginList)
       continue;
     for (int CapturedByIdx : Attr->params()) {
-      if (CapturedByIdx == LifetimeCaptureByAttr::Global) {
-        for (OriginList *L = CapturedOriginList; L != nullptr;
-             L = L->peelOuterOrigin())
-          EscapesInCurrentBlock.push_back(FactMgr.createFact<GlobalEscapeFact>(
-              L->getOuterOriginID(), nullptr));
-        continue;
-      }
-      if (CapturedByIdx == LifetimeCaptureByAttr::Unknown ||
+      // FIXME: Add support for capturing to Global/unknown.
+      if (CapturedByIdx == LifetimeCaptureByAttr::Global ||
+          CapturedByIdx == LifetimeCaptureByAttr::Unknown ||
           CapturedByIdx == LifetimeCaptureByAttr::Invalid)
         continue;
-      unsigned CapturedByArgIdx =
-          (CapturedByIdx == LifetimeCaptureByAttr::This)
-              ? 0
-              : (unsigned)CapturedByIdx + (isInstance ? 1 : 0);
-      if (CapturedByArgIdx >= Args.size())
+      ArrayRef<const Expr *> CallArgs = IsInstance ? Args.slice(1) : Args;
+      const Expr *CapturedByArg = (CapturedByIdx == 
LifetimeCaptureByAttr::This)
+                                      ? Args[0]
+                                      : CallArgs[CapturedByIdx];
+      assert(CapturedByArg && "Capturer expression must be valid");
+
+      OriginList *CapturedByOriginList = getOriginsList(*CapturedByArg);
+      OriginList *Dest = getRValueOrigins(CapturedByArg, CapturedByOriginList);
+      if (!Dest)
         continue;
-      OriginList *CapturedByOriginList =
-          getOriginsList(*Args[CapturedByArgIdx]);
-      if (CapturedByOriginList) {
-        OriginList *Dest =
-            (isGslPointerType(Args[CapturedByArgIdx]->getType()) ||
-             isGslOwnerType(Args[CapturedByArgIdx]->getType()))
-                ? CapturedByOriginList->peelOuterOrigin()
-                : CapturedByOriginList;
-        flow(Dest, CapturedOriginList, /*Kill=*/false);
-      }
+      CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+          Dest->getOuterOriginID(), CapturedOriginList->getOuterOriginID(),
+          /*KillDest=*/false));
     }
   }
 }
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index aaef28a85f507..fb581f1d00f1b 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -3289,7 +3289,7 @@ void assign_non_capturing_to_function_ref(function_ref 
&r) {
 // lifetime-capture-by
 
//===----------------------------------------------------------------------===//
 
-void setCaptureBy(View& in, View out [[clang::lifetime_capture_by(in)]]);
+void setCaptureBy(View& res, View in [[clang::lifetime_capture_by(res)]]);
 
 void use_after_free_capture_by() {
   View res;
@@ -3318,6 +3318,40 @@ void transitive_capture() {
   (void)v2;         // expected-note {{later used here}}   
 }
 
+void set1(View& res, const MyObj& in [[clang::lifetime_capture_by(res)]]);
+
+void test_reference_to_view() {
+  View v;
+  {
+    MyObj local;
+    set1(v, local);   // expected-warning {{object whose reference is captured 
does not live long enough}}
+  }                   // expected-note {{destroyed here}}
+  (void)v;            // expected-note {{later used here}} 
+}
+
+// FIXME: Add special handling for multi-level pointers and lvalue expressions 
which are not DeclRefExpr.
+void set2(MyObj** res, const MyObj& in [[clang::lifetime_capture_by(res)]]);
+
+void test_pointer_to_pointer() {
+  MyObj *ptr = nullptr;
+  {
+    MyObj local;
+    set2(&ptr, local);
+  }
+  (void)ptr;
+}
+
+void set3(MyObj*& res, const MyObj& in [[clang::lifetime_capture_by(res)]]);
+
+void test_reference_to_pointer() {
+  MyObj *ptr = nullptr;
+  {
+    MyObj local;
+    set3(ptr, local);   // expected-warning {{object whose reference is 
captured does not live long enough}}
+  }                     // expected-note {{destroyed here}}
+  (void)ptr;            // expected-note {{later used here}} 
+}
+
 struct [[gsl::Pointer]] MyContainer {
   View stored;
   void set(View s [[clang::lifetime_capture_by(this)]]);
@@ -3332,6 +3366,21 @@ void member_capture() {
   (void)c.stored;   // expected-note {{later used here}}
 }
 
+// FIXME: Add support for simple containers without annotations.
+struct SimpleContainer {
+  View stored;
+  void set(View s [[clang::lifetime_capture_by(this)]]);
+};
+
+void member_capture_simple_container() {
+  SimpleContainer c;
+  {
+    MyObj local;
+    c.set(local);   
+  }                 
+  (void)c.stored;   
+}
+
 void captureTwo(View& into, 
                 View a [[clang::lifetime_capture_by(into)]], 
                 View b [[clang::lifetime_capture_by(into)]]);

>From 947bb147c418b32ea5995e4eb7894b812033e96d Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <[email protected]>
Date: Thu, 21 May 2026 10:28:42 +0000
Subject: [PATCH 3/3] Nit change

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

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index af545d5e9386c..f3eadc16473a4 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -91,7 +91,7 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
   void handleMovedArgsInCall(const FunctionDecl *FD,
                              ArrayRef<const Expr *> Args);
 
-  // Detects [[clang::lifetime_capture_by(X)]] annotations on a function call 
to
+  // Handles [[clang::lifetime_capture_by(X)]] annotations on a function call 
to
   // create flow facts from captured arguments to the capturer
   void handleLifetimeCaptureBy(const FunctionDecl *FD,
                                ArrayRef<const Expr *> Args);

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

Reply via email to