Author: Zhijie Wang
Date: 2026-03-12T13:55:33Z
New Revision: 428b9bda871dfdc4118227a464ac0174e8dff35a

URL: 
https://github.com/llvm/llvm-project/commit/428b9bda871dfdc4118227a464ac0174e8dff35a
DIFF: 
https://github.com/llvm/llvm-project/commit/428b9bda871dfdc4118227a464ac0174e8dff35a.diff

LOG: [LifetimeSafety] Add origin tracking for lambda captures (#185216)

This is the first step toward pointer-field sensitivity (#184344).

- `hasOrigins` extension: lambda closure types whose fields have origins
now participate in origin tracking.
- `VisitLambdaExpr`: each lambda gets a single merged origin.
- Lambda closure copy/move constructors now propagate origins.

---------

Co-authored-by: Utkarsh Saxena <[email protected]>

Added: 
    

Modified: 
    clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
    clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
    clang/lib/Analysis/LifetimeSafety/Origins.cpp
    clang/test/Sema/Inputs/lifetime-analysis.h
    clang/test/Sema/warn-lifetime-safety-invalidations.cpp
    clang/test/Sema/warn-lifetime-safety-suggestions.cpp
    clang/test/Sema/warn-lifetime-safety.cpp
    clang/unittests/Analysis/LifetimeSafetyTest.cpp

Removed: 
    


################################################################################
diff  --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index dbe5a1eeb498e..ddaa69719b666 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -50,6 +50,7 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
   void VisitInitListExpr(const InitListExpr *ILE);
   void VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *BTE);
   void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
+  void VisitLambdaExpr(const LambdaExpr *LE);
 
 private:
   OriginList *getOriginsList(const ValueDecl &D);

diff  --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 8238cf69edfcd..886111ee64e73 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -182,6 +182,20 @@ void FactsGenerator::VisitCXXConstructExpr(const 
CXXConstructExpr *CCE) {
     handleGSLPointerConstruction(CCE);
     return;
   }
+  // Implicit copy/move constructors of lambda closures lack
+  // [[clang::lifetimebound]], so `handleFunctionCall` cannot propagate 
origins.
+  // Handle them directly to keep the origin chain intact (e.g., `return
+  // lambda;` copies the closure).
+  if (const auto *RD = CCE->getType()->getAsCXXRecordDecl();
+      RD && RD->isLambda() &&
+      CCE->getConstructor()->isCopyOrMoveConstructor() &&
+      CCE->getNumArgs() == 1) {
+    const Expr *Arg = CCE->getArg(0);
+    if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) {
+      flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
+      return;
+    }
+  }
   handleFunctionCall(CCE, CCE->getConstructor(),
                      {CCE->getArgs(), CCE->getNumArgs()},
                      /*IsGslConstruction=*/false);
@@ -432,6 +446,30 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
   }
 }
 
+void FactsGenerator::VisitLambdaExpr(const LambdaExpr *LE) {
+  // The lambda gets a single merged origin that aggregates all captured
+  // pointer-like origins. Currently we only need to detect whether the lambda
+  // outlives any capture.
+  OriginList *LambdaList = getOriginsList(*LE);
+  if (!LambdaList)
+    return;
+  bool Kill = true;
+  for (const Expr *Init : LE->capture_inits()) {
+    if (!Init)
+      continue;
+    OriginList *InitList = getOriginsList(*Init);
+    if (!InitList)
+      continue;
+    // FIXME: Consider flowing all origin levels once lambdas support more than
+    // one origin. Currently only the outermost origin is flowed, so by-ref
+    // captures like `[&p]` (where p is string_view) miss inner-level
+    // invalidation.
+    CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+        LambdaList->getOuterOriginID(), InitList->getOuterOriginID(), Kill));
+    Kill = false;
+  }
+}
+
 void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
   const VarDecl *LifetimeEndsVD = LifetimeEnds.getVarDecl();
   if (!LifetimeEndsVD)

diff  --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp 
b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index a9e40d6b7aaf1..0122f7a734541 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -51,7 +51,19 @@ class MissingOriginCollector
 } // namespace
 
 bool hasOrigins(QualType QT) {
-  return QT->isPointerOrReferenceType() || isGslPointerType(QT);
+  if (QT->isPointerOrReferenceType() || isGslPointerType(QT))
+    return true;
+  const auto *RD = QT->getAsCXXRecordDecl();
+  if (!RD)
+    return false;
+  // TODO: Limit to lambdas for now. This will be extended to user-defined
+  // structs with pointer-like fields.
+  if (!RD->isLambda())
+    return false;
+  for (const auto *FD : RD->fields())
+    if (hasOrigins(FD->getType()))
+      return true;
+  return false;
 }
 
 /// Determines if an expression has origins that need to be tracked.

diff  --git a/clang/test/Sema/Inputs/lifetime-analysis.h 
b/clang/test/Sema/Inputs/lifetime-analysis.h
index 85b5a5fe5e07f..56cacdd964f79 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -152,6 +152,7 @@ struct basic_string_view {
   basic_string_view(const T *);
   const T *begin() const;
   const T *data() const;
+  int size() const;
 };
 using string_view = basic_string_view<char>;
 
@@ -174,6 +175,8 @@ struct basic_string {
   basic_string& operator=(const basic_string&);
   basic_string& operator+=(const basic_string&);
   basic_string& operator+=(const T*);
+  void push_back(T);
+  void clear();
   const T *c_str() const;
   operator basic_string_view<T> () const;
   using const_iterator = iter<T>;

diff  --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp 
b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index c50c1e2d77d65..486edd7a1a023 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -468,3 +468,42 @@ void 
FlatMapSubscriptMultipleCallsInvalidate(std::flat_map<int, int> mp, int a,
 }
 
 } // namespace AssociativeContainers
+
+namespace lambda_capture_invalidation {
+void captured_view_invalidated_by_owner() {
+  std::string s = "42";
+  std::string_view p = s; // expected-warning {{object whose reference is 
captured is later invalidated}}
+  auto lambda = [=]() { return p; };
+  s.push_back('c');  // expected-note {{invalidated here}}
+  lambda();  // expected-note {{later used here}}
+}
+
+void multiple_captures_one_invalidated() {
+  std::string s1 = "a", s2 = "b";
+  std::string_view p1 = s1, p2 = s2; // expected-warning {{object whose 
reference is captured is later invalidated}}
+  auto lambda = [=]() { return p1.size() + p2.size(); };
+  s1.clear();  // expected-note {{invalidated here}}
+  lambda();  // expected-note {{later used here}}
+}
+
+// FIXME: By-ref captures flow only the outermost origin, so
+// invalidation of the captured view's pointee is not propagated.
+void ref_capture_owner_invalidated() {
+  std::string s = "42";
+  std::string_view p = s;
+  auto lambda = [&]() { return p; };
+  s.push_back('c');  // invalidates p
+  lambda();  // should warn: use-after-invalidate
+}
+
+// FIXME: Once inner origins are tracked, this case must remain a no-warning.
+// Reassigning `p` through the by-ref capture should invalidate the link to 
`s`.
+void ref_capture_reassigned_to_safe() {
+  std::string s = "42", safe = "not modified";
+  std::string_view p = s;
+  auto lambda = [&]() { return p; };
+  p = safe;  // p now points to 'safe', not 's'
+  s.push_back('c');  // does not invalidate p anymore
+  lambda();  // should not warn
+}
+} // namespace lambda_capture_invalidation

diff  --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp 
b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index 4bd8a717d9d30..b7e6c5951ba8b 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -394,6 +394,8 @@ View Reassigned(View a) {
   return a;
 }
 
+namespace lambda_captures {
+
 struct NoSuggestionForThisCapturedByLambda {
   MyObj s;
   bool cond;
@@ -403,3 +405,21 @@ struct NoSuggestionForThisCapturedByLambda {
     };
   }
 };
+
+void Foo(int, int*, const MyObj&, View);
+
+auto implicit_ref_capture(int integer, int* ptr,
+                          const MyObj& ref, // expected-warning {{parameter in 
intra-TU function should be marked [[clang::lifetimebound]]}}
+                          View view) {
+  return [&]() { Foo(integer, ptr, ref, view); }; // expected-warning 3 
{{address of stack memory is returned later}} \
+                                                  // expected-note 3 
{{returned here}} \
+                                                  // expected-note {{param 
returned here}}
+}
+
+auto implicit_value_capture(int integer,
+                            int* ptr, // expected-warning {{parameter in 
intra-TU function should be marked [[clang::lifetimebound]]}}
+                            const MyObj& ref,
+                            View view) { // expected-warning {{parameter in 
intra-TU function should be marked [[clang::lifetimebound]]}}
+  return [=]() { Foo(integer, ptr, ref, view); }; // expected-note 2 {{param 
returned here}}
+}
+} // namespace lambda_captures

diff  --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index a75c70aa3674a..7034c8686b315 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1784,3 +1784,150 @@ void test_optional_view_arrow() {
     (void)*p;
 }
 } // namespace OwnerArrowOperator
+
+namespace lambda_captures {
+auto return_ref_capture() {
+  int local = 1;
+  auto lambda = [&local]() { return local; }; // expected-warning {{address of 
stack memory is returned later}}
+  return lambda; // expected-note {{returned here}}
+}
+
+void safe_ref_capture() {
+  int local = 1;
+  auto lambda = [&local]() { return local; };
+  lambda();
+}
+
+auto capture_int_by_value() {
+  int x = 1;
+  auto lambda = [x]() { return x; };
+  return lambda;
+}
+
+auto capture_view_by_value() {
+  MyObj obj;
+  View v(obj); // expected-warning {{address of stack memory is returned 
later}}
+  auto lambda = [v]() { return v; };
+  return lambda; // expected-note {{returned here}}
+}
+
+void capture_view_by_value_safe() {
+  MyObj obj;
+  View v(obj);
+  auto lambda = [v]() { return v; };
+  lambda();
+}
+
+auto capture_pointer_by_ref() {
+  MyObj obj;
+  MyObj* p = &obj;
+  auto lambda = [&p]() { return p; }; // expected-warning {{address of stack 
memory is returned later}}
+  return lambda; // expected-note {{returned here}}
+}
+
+auto capture_multiple() {
+  int a, b;
+  auto lambda = [
+    &a,  // expected-warning {{address of stack memory is returned later}}
+    &b   // expected-warning {{address of stack memory is returned later}}
+  ]() { return a + b; };
+  return lambda; // expected-note 2 {{returned here}}
+}
+
+auto capture_raw_pointer_by_value() {
+  int x;
+  int* p = &x; // expected-warning {{address of stack memory is returned 
later}}
+  auto lambda = [p]() { return p; };
+  return lambda; // expected-note {{returned here}}
+}
+
+auto capture_raw_pointer_init_capture() {
+  int x;
+  int* p = &x; // expected-warning {{address of stack memory is returned 
later}}
+  auto lambda = [q = p]() { return q; };
+  return lambda; // expected-note {{returned here}}
+}
+
+auto capture_view_init_capture() {
+  MyObj obj;
+  View v(obj); // expected-warning {{address of stack memory is returned 
later}}
+  auto lambda = [w = v]() { return w; };
+  return lambda; // expected-note {{returned here}}
+}
+
+auto capture_lambda() {
+  int x;
+  auto inner = [&x]() { return x; }; // expected-warning {{address of stack 
memory is returned later}}
+  auto outer = [inner]() { return inner(); };
+  return outer; // expected-note {{returned here}}
+}
+
+auto return_copied_lambda() {
+  int local = 1;
+  auto lambda = [&local]() { return local; }; // expected-warning {{address of 
stack memory is returned later}}
+  auto lambda_copy = lambda;
+  return lambda_copy; // expected-note {{returned here}}
+}
+
+auto implicit_ref_capture() {
+  int local = 1;
+  auto lambda = [&]() { return local; }; // expected-warning {{address of 
stack memory is returned later}}
+  return lambda; // expected-note {{returned here}}
+}
+
+// TODO: Include the name of the variable in the diagnostic to improve
+// clarity, especially for implicit lambda captures where multiple warnings
+// can point to the same source location.
+auto implicit_ref_capture_multiple() {
+  int local = 1, local2 = 2;
+  auto lambda = [&]() { return local + local2; }; // expected-warning 2 
{{address of stack memory is returned later}}
+  return lambda; // expected-note 2 {{returned here}}
+}
+
+auto implicit_value_capture() {
+  MyObj obj;
+  View v(obj); // expected-warning {{address of stack memory is returned 
later}}
+  auto lambda = [=]() { return v; };
+  return lambda; // expected-note {{returned here}}
+}
+
+auto* pointer_to_lambda_outlives() {
+  auto lambda = []() { return 42; };
+  return &lambda; // expected-warning {{address of stack memory is returned 
later}} \
+                  // expected-note {{returned here}}
+}
+
+auto capture_static() {
+  static int local = 1;
+  // Only automatic storage duration variables may be captured.
+  // Variables with static storage duration behave like globals and are 
directly accessible.
+  // The below lambdas should not capture `local`.
+  auto lambda = [&]() { return local; };
+  auto lambda2 = []() { return local; };
+  lambda2();
+  return lambda;
+}
+
+auto capture_static_address_by_value() {
+  static int local = 1;
+  int* p = &local;
+  auto lambda = [p]() { return p; };
+  return lambda;
+}
+
+auto capture_static_address_by_ref() {
+  static int local = 1;
+  int* p = &local;
+  auto lambda = [&p]() { return p; }; // expected-warning {{address of stack 
memory is returned later}}
+  return lambda; // expected-note {{returned here}}
+}
+
+auto capture_multilevel_pointer() {
+  int x;
+  int *p = &x; // expected-warning {{address of stack memory is returned 
later}}
+  int **q = &p; // expected-warning {{address of stack memory is returned 
later}}
+  int ***r = &q; // expected-warning {{address of stack memory is returned 
later}}
+  auto lambda = [=]() { return *p + **q + ***r; };
+  return lambda; // expected-note 3 {{returned here}}
+}
+} // namespace lambda_captures

diff  --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp 
b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index a27f746fffb60..2116f7736c4be 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -1904,5 +1904,52 @@ TEST_F(LifetimeAnalysisTest, 
DerivedViewWithNoAnnotation) {
   // EXPECT_THAT(Origin("view"), HasLoansTo({"my_obj_or"}, "p1"));
 }
 
+TEST_F(LifetimeAnalysisTest, LambdaCaptureByRef) {
+  SetupTest(R"(
+    void target() {
+      int x;
+      int* p = &x;
+      auto lambda = [&p]() { return p; };
+      POINT(after_lambda);
+    }
+  )");
+  EXPECT_THAT(Origin("lambda"), HasLoansTo({"p"}, "after_lambda"));
+}
+
+TEST_F(LifetimeAnalysisTest, LambdaCaptureViewByValue) {
+  SetupTest(R"(
+    void target() {
+      MyObj obj;
+      View v(obj);
+      auto lambda = [v]() { return v; };
+      POINT(after_lambda);
+    }
+  )");
+  EXPECT_THAT(Origin("lambda"), HasLoansTo({"obj"}, "after_lambda"));
+}
+
+TEST_F(LifetimeAnalysisTest, LambdaInitCaptureRawPointerByValue) {
+  SetupTest(R"(
+    void target() {
+      int x;
+      int* p = &x;
+      auto lambda = [q = p]() { return q; };
+      POINT(after_lambda);
+    }
+  )");
+  EXPECT_THAT(Origin("lambda"), HasLoansTo({"x"}, "after_lambda"));
+}
+
+TEST_F(LifetimeAnalysisTest, LambdaInitCaptureViewByValue) {
+  SetupTest(R"(
+    void target() {
+      MyObj obj;
+      View v(obj);
+      auto lambda = [w = v]() { return w; };
+      POINT(after_lambda);
+    }
+  )");
+  EXPECT_THAT(Origin("lambda"), HasLoansTo({"obj"}, "after_lambda"));
+}
 } // anonymous namespace
 } // namespace clang::lifetimes::internal


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

Reply via email to