[clang] [LifetimeSafety] Add origin tracking for lambda captures (PR #185216)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Mar 7 10:33:38 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-temporal-safety
Author: Zhijie Wang (aeft)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/185216.diff
5 Files Affected:
- (modified) clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h (+1)
- (modified) clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp (+29)
- (modified) clang/lib/Analysis/LifetimeSafety/Origins.cpp (+13-1)
- (modified) clang/test/Sema/warn-lifetime-safety.cpp (+75)
- (modified) clang/unittests/Analysis/LifetimeSafetyTest.cpp (+47)
``````````diff
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
``````````
</details>
https://github.com/llvm/llvm-project/pull/185216
More information about the cfe-commits
mailing list