Author: Kashika Akhouri
Date: 2026-06-01T16:28:48+05:30
New Revision: 0059fe6596aee0052f9ba40188ee2cef27b93af0

URL: 
https://github.com/llvm/llvm-project/commit/0059fe6596aee0052f9ba40188ee2cef27b93af0
DIFF: 
https://github.com/llvm/llvm-project/commit/0059fe6596aee0052f9ba40188ee2cef27b93af0.diff

LOG: [LifetimeSafety] Add support for lifetime capture_by (#196884)

This PR implements support for the `[[clang::lifetime_capture_by(X)]]`
attribute within the lifetime-safety analysis.

The PR introduces a new helper in `FactGenerator.cpp` called
`handleLifetimeCaptureBy` which detects
`[[clang::lifetime_capture_by(X)]]` on parameters. If detected, the
analyzer now generates an `OriginFlowFact` ensuring that captured
dependencies are added to the capturer's state. The PR supports
capture_by params and `this` and currently doesn't implement attributes
on function declarations.

Example:
Integrate `[[clang::lifetimebound]]`: This existing Clang annotation is
crucial for specifying that the lifetime of a function's output is tied
to one of its inputs.

```cpp
void setS(std::string_view& in, std::string_view out 
[[clang::lifetime_capture_by(X)]]);

std::string_view foo() {
  std::string a = "on stack";
  std::string_view res;
  setS(res, a);
  return res;  // warning: returning address of local variable 'a'.
}
```

Lifetime Facts:
```cpp
Function: foo
  Block B2:
  End of Block
  Block B1:
    OriginFlow: 
        Dest: 0 (Expr: ImplicitCastExpr, Type : const char *)
        Src:  1 (Expr: StringLiteral, Type : const char (&)[9])
    Use (0 (Expr: ImplicitCastExpr, Type : const char *), Read)
    Use (2 (Expr: CXXDefaultArgExpr, Type : const class std::allocator<char> 
&), Read)
    Expire (MaterializeTemporaryExpr at 0x562afa69fdb0)
    OriginFlow: 
        Dest: 3 (Decl: res, Type : std::string_view)
        Src:  4 (Expr: CXXConstructExpr, Type : std::string_view)
    Use (3 (Decl: res, Type : std::string_view), Read)
    Issue (0 (Path: res), ToOrigin: 7 (Expr: DeclRefExpr, Decl: res))
    Issue (1 (Path: a), ToOrigin: 8 (Expr: DeclRefExpr, Decl: a))
    OriginFlow: 
        Dest: 9 (Expr: ImplicitCastExpr, Type : const class 
std::basic_string<char> &)
        Src:  8 (Expr: DeclRefExpr, Decl: a)
    Use (9 (Expr: ImplicitCastExpr, Type : const class std::basic_string<char> 
&), Read)
    OriginFlow: 
        Dest: 10 (Expr: CXXMemberCallExpr, Type : __sv_type)
        Src:  9 (Expr: ImplicitCastExpr, Type : const class 
std::basic_string<char> &)
    OriginFlow: 
        Dest: 11 (Expr: ImplicitCastExpr, Type : __sv_type)
        Src:  10 (Expr: CXXMemberCallExpr, Type : __sv_type)
    Use (11 (Expr: ImplicitCastExpr, Type : __sv_type), Read)
// New Origin Flow fact added    
OriginFlow: 
        Dest: 3 (Decl: res, Type : std::string_view)
        Src:  11 (Expr: ImplicitCastExpr, Type : __sv_type), Merge
    Use (3 (Decl: res, Type : std::string_view), Read)
    Issue (2 (Path: res), ToOrigin: 12 (Expr: DeclRefExpr, Decl: res))
    OriginFlow: 
        Dest: 13 (Expr: ImplicitCastExpr, Type : const std::string_view &)
        Src:  12 (Expr: DeclRefExpr, Decl: res)
    OriginFlow: 
        Dest: 14 (Expr: ImplicitCastExpr, Type : std::string_view)
        Src:  3 (Decl: res, Type : std::string_view)
    OriginFlow: 
        Dest: 15 (Expr: CXXConstructExpr, Type : std::string_view)
        Src:  14 (Expr: ImplicitCastExpr, Type : std::string_view)
    Expire (a)
    Expire (res, Origin: 3 (Decl: res, Type : std::string_view))
    OriginEscapes (15 (Expr: CXXConstructExpr, Type : std::string_view), via 
Return)
  End of Block
  Block B0:
  End of Block
```

Added: 
    

Modified: 
    clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
    clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
    clang/test/Sema/warn-lifetime-safety.cpp

Removed: 
    


################################################################################
diff  --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 0b6d8c4fc026e..8f0670728bae9 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -100,6 +100,11 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
   void handleMovedArgsInCall(const FunctionDecl *FD,
                              ArrayRef<const Expr *> Args);
 
+  // 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);
+
   /// 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 b34a04f1f3bda..17617fc92a7e8 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -878,6 +878,66 @@ void FactsGenerator::handleImplicitObjectFieldUses(const 
Expr *Call,
   });
 }
 
+void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD,
+                                             ArrayRef<const Expr *> Args) {
+  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 * {
+    const ParmVarDecl *PVD = nullptr;
+    if (IsInstance) {
+      // FIXME: Add support for I == 0 i.e. capture_by on function declarations
+      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;
+  };
+  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 (!CapturedOriginList)
+      continue;
+    for (int CapturingArgIdx : Attr->params()) {
+      // FIXME: Add support for capturing to Global/unknown.
+      if (CapturingArgIdx == LifetimeCaptureByAttr::Global ||
+          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];
+      assert(CapturedByArg && "Capturer expression must be valid");
+
+      OriginList *CapturingOriginList = getOriginsList(*CapturedByArg);
+      OriginList *Dest = getRValueOrigins(CapturedByArg, CapturingOriginList);
+      if (!Dest)
+        continue;
+      // KillDest=false because we cannot know if previous captures are being
+      // replaced or accumulated. Multiple successive captures into the same
+      // destination must all be tracked, so captured lifetimes are always
+      // merged.
+      CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+          Dest->getOuterOriginID(), CapturedOriginList->getOuterOriginID(),
+          /*KillDest=*/false));
+    }
+  }
+}
+
 void FactsGenerator::handleFunctionCall(const Expr *Call,
                                         const FunctionDecl *FD,
                                         ArrayRef<const Expr *> Args,
@@ -894,6 +954,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
   handleDestructiveCall(Call, FD, Args);
   handleMovedArgsInCall(FD, Args);
   handleImplicitObjectFieldUses(Call, FD);
+  handleLifetimeCaptureBy(FD, Args);
   if (!CallList)
     return;
   if (isStdReferenceCast(FD)) {

diff  --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 06097d4600af5..8209ce7dda73b 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -3420,3 +3420,163 @@ namespace GH191954 {
     return std::move(f);
   }
 } // namespace GH191954
+
+//===----------------------------------------------------------------------===//
+// lifetime-capture-by
+//===----------------------------------------------------------------------===//
+
+void setCaptureBy(View& res, View in [[clang::lifetime_capture_by(res)]]);
+
+void use_after_free_capture_by() {
+  View res;
+  {
+    MyObj a;      
+    setCaptureBy(res, a); // expected-warning {{local variable 'a' 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 {{stack memory associated with 
local variable 'a' is returned}}
+  return res;     // expected-note {{returned here}}  
+                   
+}
+
+void transitive_capture() {
+  View v1, v2;
+  {
+    MyObj local;
+    setCaptureBy(v1, local); // expected-warning {{local variable 'local' does 
not live long enough}}
+    setCaptureBy(v2, v1);   
+  }                 // expected-note {{destroyed here}}
+  (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 {{local variable 'local' 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 test_pointer_to_pointer_2(MyObj **ptr) {
+  {
+    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 {{local variable 'local' 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)]]);
+};
+
+void member_capture() {
+  MyContainer c;
+  {
+    MyObj local;
+    c.set(local);   // expected-warning {{local variable 'local' does not live 
long enough}}
+  }                 // expected-note {{destroyed here}}
+  (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)]]);
+
+void multiple_captures() {
+  View res;
+  MyObj val1;
+  {
+    MyObj val2;
+    captureTwo(res, val1, val2); // expected-warning {{local variable 'val2' 
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 {{local variable 'val1' 
does not live long enough}} // expected-warning {{local variable 'val2' does 
not live long enough}}
+  }                              // expected-note 2 {{destroyed here}}
+  (void)res;                     // expected-note 2 {{later used here}}        
      
+}
+
+void captureIntoTwo(View& v1, View& v2, 
+                    View s [[clang::lifetime_capture_by(v1, v2)]]);
+
+void captured_by_multiple_params() {
+   View v1, v2;
+  {
+    MyObj local;
+    captureIntoTwo(v1, v2, local);  // expected-warning {{local variable 
'local' does not live long enough}}
+  }                                 // expected-note {{destroyed here}}        
                          
+  (void)v1;                         // expected-note {{later used here}}       
              
+}
+
+void captured_by_multiple_params_2() {
+   View v1, v2;
+  {
+    MyObj local;
+    captureIntoTwo(v1, v2, local);  // expected-warning {{local variable 
'local' does not live long enough}}
+  }                                 // expected-note {{destroyed here}}        
                          
+  (void)v2;                         // expected-note {{later used here}}       
              
+}
+
+void capturing_multiple_locals() {
+    View v;
+    {
+        MyObj local1;
+        setCaptureBy(v, local1);    // expected-warning{{local variable 
'local1' does not live long enough}}
+        MyObj local2;   
+        setCaptureBy(v, local2);    // expected-warning{{local variable 
'local2' does not live long enough}}
+    }                               // expected-note 2 {{destroyed here}} 
+    (void)v;                        // expected-note 2 {{later used here}}
+}
\ No newline at end of file


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

Reply via email to