[clang] [LifetimeSafety] Add origin tracking for lambda captures (PR #185216)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 12 06:22:42 PDT 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/185216
>From ee2dcc8f33a2df8f7cd3e2bcafa28210e65aa157 Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Sat, 7 Mar 2026 10:19:58 -0800
Subject: [PATCH 01/11] [LifetimeSafety] Add origin tracking for lambda
captures
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 1 +
.../LifetimeSafety/FactsGenerator.cpp | 29 +++++++
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 14 +++-
clang/test/Sema/warn-lifetime-safety.cpp | 75 +++++++++++++++++++
.../unittests/Analysis/LifetimeSafetyTest.cpp | 47 ++++++++++++
5 files changed, 165 insertions(+), 1 deletion(-)
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 f39d677758393..61fd7359633d2 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -181,6 +181,14 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
handleGSLPointerConstruction(CCE);
return;
}
+ if (const auto *RD = CCE->getType()->getAsCXXRecordDecl();
+ RD && RD->isLambda() && 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);
@@ -431,6 +439,27 @@ 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 (unsigned I = 0; I < LE->capture_size(); ++I) {
+ const Expr *Init = LE->capture_init_begin()[I];
+ if (!Init)
+ continue;
+ OriginList *InitList = getOriginsList(*Init);
+ if (!InitList)
+ continue;
+ 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/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index a75c70aa3674a..dc7ce22913755 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1784,3 +1784,78 @@ 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, &b]() { return a + b; }; // expected-warning 2 {{address of stack memory is returned later}}
+ 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}}
+ }
+} // 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
>From 1ada107c9c5b14cc8e20df78e610afbedc6054ce Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Mon, 9 Mar 2026 13:01:42 -0700
Subject: [PATCH 02/11] add comment for VisitCXXConstructExpr
---
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 8 +++++++-
clang/test/Sema/warn-lifetime-safety.cpp | 7 +++++++
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 61fd7359633d2..9e3c5cce3e255 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -181,8 +181,14 @@ 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->getNumArgs() == 1) {
+ 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);
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index dc7ce22913755..fa43e25c2cef6 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1858,4 +1858,11 @@ namespace lambda_captures {
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}}
+ }
} // namespace lambda_captures
>From 99a6fba7873e8dd804f2531f92c2fb162c14b689 Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Mon, 9 Mar 2026 13:11:54 -0700
Subject: [PATCH 03/11] add tests for lambda implict capture
---
.../lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 3 +--
clang/test/Sema/warn-lifetime-safety.cpp | 13 +++++++++++++
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 9e3c5cce3e255..a16f92a3a6b9b 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -453,8 +453,7 @@ void FactsGenerator::VisitLambdaExpr(const LambdaExpr *LE) {
if (!LambdaList)
return;
bool Kill = true;
- for (unsigned I = 0; I < LE->capture_size(); ++I) {
- const Expr *Init = LE->capture_init_begin()[I];
+ for (const Expr *Init : LE->capture_inits()) {
if (!Init)
continue;
OriginList *InitList = getOriginsList(*Init);
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index fa43e25c2cef6..b019d9be9a536 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1865,4 +1865,17 @@ namespace lambda_captures {
auto lambda_copy = lambda;
return lambda_copy; // expected-note {{returned here}}
}
+
+ auto implicit_ref_capture() {
+ int local = 1, local2 = 2;
+ auto lambda = [&]() { return local; }; // expected-warning {{address of stack memory is returned later}}
+ return lambda; // expected-note {{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}}
+ }
} // namespace lambda_captures
>From 60589967136680408b2823db8cbbcc7693134ff4 Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Mon, 9 Mar 2026 15:51:29 -0700
Subject: [PATCH 04/11] add TODO to improve diagnostic for implicit lambda
captures
---
clang/test/Sema/warn-lifetime-safety.cpp | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index b019d9be9a536..4a8112cb88ac3 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1827,7 +1827,10 @@ namespace lambda_captures {
auto capture_multiple() {
int a, b;
- auto lambda = [&a, &b]() { return a + b; }; // expected-warning 2 {{address of stack memory is returned later}}
+ 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}}
}
@@ -1872,6 +1875,15 @@ namespace lambda_captures {
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}}
>From ff7fa91df9ce901a643ba719885e1ca661152c17 Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Mon, 9 Mar 2026 15:58:27 -0700
Subject: [PATCH 05/11] add test pointer_to_lambda_outlives
---
clang/test/Sema/warn-lifetime-safety.cpp | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 4a8112cb88ac3..d1fe668695117 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1890,4 +1890,10 @@ namespace lambda_captures {
auto lambda = [=]() { return v; };
return lambda; // expected-note {{returned here}}
}
+
+ auto* pointer_to_lambda_outlives() {
+ auto lambda = []() { return 42; };
+ return λ // expected-warning {{address of stack memory is returned later}} \
+ // expected-note {{returned here}}
+ }
} // namespace lambda_captures
>From daf8c1c4b981ce7caecb8c8d5e4bd3c19a97e37f Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Tue, 10 Mar 2026 14:07:33 -0700
Subject: [PATCH 06/11] add tests
---
.../Sema/warn-lifetime-safety-suggestions.cpp | 19 +++++++++++++++++++
clang/test/Sema/warn-lifetime-safety.cpp | 11 +++++++++++
2 files changed, 30 insertions(+)
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index 4bd8a717d9d30..d574101f762fc 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -403,3 +403,22 @@ struct NoSuggestionForThisCapturedByLambda {
};
}
};
+
+namespace lambda_captures {
+ 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 d1fe668695117..6c2a08fb1d0ff 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1896,4 +1896,15 @@ namespace lambda_captures {
return λ // 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;
+ }
} // namespace lambda_captures
>From d891015de2e2f0c07aec777b8c61776721413994 Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Tue, 10 Mar 2026 14:20:49 -0700
Subject: [PATCH 07/11] add test: capture_static_address
---
clang/test/Sema/warn-lifetime-safety.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 6c2a08fb1d0ff..07317a4e6c3a4 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1907,4 +1907,11 @@ namespace lambda_captures {
lambda2();
return lambda;
}
+
+ auto capture_static_address() {
+ static int local = 1;
+ int* p = &local;
+ auto lambda = [p]() { return p; };
+ return lambda;
+ }
} // namespace lambda_captures
>From 99abe37a88573d37a7bade875e3d542f7dcf9c3f Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Tue, 10 Mar 2026 15:18:01 -0700
Subject: [PATCH 08/11] add test: capture_static_address_by_ref
---
clang/test/Sema/warn-lifetime-safety.cpp | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 07317a4e6c3a4..3639bce1a0364 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1908,10 +1908,17 @@ namespace lambda_captures {
return lambda;
}
- auto capture_static_address() {
+ 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}}
+ }
} // namespace lambda_captures
>From 4b62f648f9dfd75102db1112f4bd20c39af07d5f Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Wed, 11 Mar 2026 15:52:45 -0700
Subject: [PATCH 09/11] add tests: lambda_capture_invalidation,
capture_multilevel_pointer
---
.../LifetimeSafety/FactsGenerator.cpp | 4 ++
clang/test/Sema/Inputs/lifetime-analysis.h | 3 ++
.../warn-lifetime-safety-invalidations.cpp | 39 +++++++++++++++++++
clang/test/Sema/warn-lifetime-safety.cpp | 27 ++++++++-----
4 files changed, 64 insertions(+), 9 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index a16f92a3a6b9b..00df3b8007a89 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -459,6 +459,10 @@ void FactsGenerator::VisitLambdaExpr(const LambdaExpr *LE) {
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;
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..519a7c2986b12 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.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 3639bce1a0364..6ae62f49a7b8c 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1870,7 +1870,7 @@ namespace lambda_captures {
}
auto implicit_ref_capture() {
- int local = 1, local2 = 2;
+ int local = 1;
auto lambda = [&]() { return local; }; // expected-warning {{address of stack memory is returned later}}
return lambda; // expected-note {{returned here}}
}
@@ -1909,16 +1909,25 @@ namespace lambda_captures {
}
auto capture_static_address_by_value() {
- static int local = 1;
- int* p = &local;
- auto lambda = [p]() { return p; };
- return lambda;
+ 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}}
+ 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
>From 683b919be20680c9786b132154c2fd865acca963 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Thu, 12 Mar 2026 14:18:31 +0100
Subject: [PATCH 10/11] Fix indentation inside namespace
---
.../warn-lifetime-safety-invalidations.cpp | 72 +++++++++----------
1 file changed, 36 insertions(+), 36 deletions(-)
diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index 519a7c2986b12..486edd7a1a023 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -470,40 +470,40 @@ 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
- }
+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
>From 7dd73cabda7883eeb822fac0f726f9ba8b486474 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Thu, 12 Mar 2026 14:22:27 +0100
Subject: [PATCH 11/11] Fix indentation inside namespace
---
clang/test/Sema/warn-lifetime-safety.cpp | 250 +++++++++++------------
1 file changed, 125 insertions(+), 125 deletions(-)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 6ae62f49a7b8c..7034c8686b315 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1786,148 +1786,148 @@ void test_optional_view_arrow() {
} // 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}}
- }
+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();
- }
+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_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}}
- }
+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();
- }
+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_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_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_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_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_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 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 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}}
- }
+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}}
- }
+// 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 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 λ // expected-warning {{address of stack memory is returned later}} \
- // expected-note {{returned here}}
- }
+auto* pointer_to_lambda_outlives() {
+ auto lambda = []() { return 42; };
+ return λ // 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() {
+ 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_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_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}}
- }
+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
More information about the cfe-commits
mailing list