https://github.com/usx95 created 
https://github.com/llvm/llvm-project/pull/214212

None

>From bd20deef1bc72b95054c117bf0244e817a6d3438 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <[email protected]>
Date: Wed, 5 Aug 2026 12:18:32 +0000
Subject: [PATCH] RAII reseters and dangling field

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  | 11 +++++++++
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |  1 +
 clang/include/clang/Basic/DiagnosticGroups.td |  7 ++++++
 .../clang/Basic/DiagnosticSemaKinds.td        |  6 +++++
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 12 ++++++----
 .../LifetimeSafety/FactsGenerator.cpp         | 15 ++++++++++++
 clang/lib/Sema/SemaLifetimeSafety.h           |  9 +++++---
 .../Sema/LifetimeSafety/dangling-field.cpp    | 23 +++++++++++++++++++
 8 files changed, 77 insertions(+), 7 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 94db2a7f311ae..b80dd4acad4c5 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -22,6 +22,7 @@
 #include "clang/Analysis/CFG.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/raw_ostream.h"
 #include <cstdint>
@@ -394,6 +395,14 @@ class FactManager {
   OriginManager &getOriginMgr() { return OriginMgr; }
   const OriginManager &getOriginMgr() const { return OriginMgr; }
 
+  void addCapturedField(const FieldDecl *FD) { CapturedFields.insert(FD); }
+  bool isFieldCapturedByLambda(const FieldDecl *FD) const {
+    return IsThisCapturedByLambda || CapturedFields.contains(FD);
+  }
+  void setThisCapturedByLambda(bool Val = true) {
+    IsThisCapturedByLambda = Val;
+  }
+
 private:
   FactID NextFactID{0};
   LoanManager LoanMgr;
@@ -401,6 +410,8 @@ class FactManager {
   /// Facts for each CFG block, indexed by block ID.
   llvm::SmallVector<llvm::SmallVector<const Fact *>> BlockToFacts;
   llvm::BumpPtrAllocator FactAllocator;
+  llvm::DenseSet<const FieldDecl *> CapturedFields;
+  bool IsThisCapturedByLambda = false;
 };
 } // namespace clang::lifetimes::internal
 
diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index a51ef2f7cc0ba..ed62cdc538175 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -76,6 +76,7 @@ class LifetimeSafetySemaHelper {
   virtual void reportDanglingField(const Expr *IssueExpr,
                                    const FieldDecl *Field,
                                    const Expr *MovedExpr,
+                                   bool IsCapturedByLambda,
                                    SourceLocation ExpiryLoc) {}
 
   virtual void reportDanglingGlobal(const Expr *IssueExpr,
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index b7072634cccf3..f670f442a8c0d 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -608,6 +608,12 @@ Warning to detect dangling field references.
 This may contain false-positives, e.g. when the borrowed storage is 
potentially moved and is not destroyed at function exit.
   }];
 }
+def LifetimeSafetyDanglingFieldLambdaCapture : 
DiagGroup<"lifetime-safety-dangling-field-lambda-capture"> {
+  code Documentation = [{
+Warning to detect dangling field references.
+This may contain false-positives, e.g. when the field is captured by a lambda 
that resets the field before function exit.
+  }];
+}
 
 def LifetimeSafetyDanglingGlobal : 
DiagGroup<"lifetime-safety-dangling-global"> {
   code Documentation = [{
@@ -667,6 +673,7 @@ def LifetimeSafetyStrict : 
DiagGroup<"lifetime-safety-strict",
                                     LifetimeSafetyUseAfterScopeMoved,
                                     LifetimeSafetyReturnStackAddrMoved,
                                     LifetimeSafetyDanglingFieldMoved,
+                                    LifetimeSafetyDanglingFieldLambdaCapture,
                                     LifetimeSafetyDanglingGlobalMoved,
                                     LifetimeSafetyInvalidation]>;
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index cce6f70a58893..a1c51e4ce065a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11057,6 +11057,12 @@ def warn_lifetime_safety_dangling_field_moved
       "Consider moving first and then aliasing later to resolve the issue">,
       InGroup<LifetimeSafetyDanglingFieldMoved>,
       DefaultIgnore;
+def warn_lifetime_safety_dangling_field_lambda_capture
+    : Warning<"stack memory associated with %0 may escape to the %1 which will 
dangle. "
+      "This could be a false positive as the field was captured by a lambda 
and "
+      "may have been reset before escaping">,
+      InGroup<LifetimeSafetyDanglingFieldLambdaCapture>,
+      DefaultIgnore;
 def warn_lifetime_safety_dangling_global
     : Warning<"stack memory associated with %0 escapes to the %1 which will 
dangle">,
       InGroup<LifetimeSafetyDanglingGlobal>,
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 155c6072a33a5..a69768d7b92cc 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -323,11 +323,15 @@ class LifetimeChecker {
           // Return stack address.
           SemaHelper->reportUseAfterReturn(
               IssueExpr, RetEscape->getReturnExpr(), MovedExpr);
-        else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF))
+        else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF)) {
           // Dangling field.
-          SemaHelper->reportDanglingField(
-              IssueExpr, FieldEscape->getFieldDecl(), MovedExpr, ExpiryLoc);
-        else if (const auto *GlobalEscape = dyn_cast<GlobalEscapeFact>(OEF))
+          bool IsCapturedByLambda =
+              FactMgr.isFieldCapturedByLambda(FieldEscape->getFieldDecl());
+          SemaHelper->reportDanglingField(IssueExpr,
+                                          FieldEscape->getFieldDecl(),
+                                          MovedExpr, IsCapturedByLambda,
+                                          ExpiryLoc);
+        } else if (const auto *GlobalEscape = dyn_cast<GlobalEscapeFact>(OEF))
           // Global escape.
           SemaHelper->reportDanglingGlobal(IssueExpr, 
GlobalEscape->getGlobal(),
                                            MovedExpr, ExpiryLoc);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index ac6267dabf48e..dfe0f916cfe85 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -696,6 +696,21 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
 }
 
 void FactsGenerator::VisitLambdaExpr(const LambdaExpr *LE) {
+  for (const LambdaCapture &C : LE->captures()) {
+    if (C.capturesThis())
+      FactMgr.setThisCapturedByLambda(true);
+    else if (C.capturesVariable() && C.getCapturedVar()->isInitCapture()) {
+      const Expr *Init = C.getCapturedVar()->getInit();
+      if (Init) {
+        if (const auto *ME =
+                dyn_cast<MemberExpr>(Init->IgnoreParenImpCasts())) {
+          if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl()))
+            FactMgr.addCapturedField(FD);
+        }
+      }
+    }
+  }
+
   // 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.
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h 
b/clang/lib/Sema/SemaLifetimeSafety.h
index 1d9f94be7e22d..e37ab74730d9f 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -146,10 +146,13 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
   void reportDanglingField(const Expr *IssueExpr,
                            const FieldDecl *DanglingField,
                            const Expr *MovedExpr,
+                           bool IsCapturedByLambda,
                            SourceLocation ExpiryLoc) override {
-    unsigned DiagID = MovedExpr
-                          ? diag::warn_lifetime_safety_dangling_field_moved
-                          : diag::warn_lifetime_safety_dangling_field;
+    unsigned DiagID =
+        IsCapturedByLambda
+            ? diag::warn_lifetime_safety_dangling_field_lambda_capture
+            : (MovedExpr ? diag::warn_lifetime_safety_dangling_field_moved
+                         : diag::warn_lifetime_safety_dangling_field);
 
     S.Diag(IssueExpr->getExprLoc(), DiagID)
         << getDiagSubjectDescription(IssueExpr)
diff --git a/clang/test/Sema/LifetimeSafety/dangling-field.cpp 
b/clang/test/Sema/LifetimeSafety/dangling-field.cpp
index bc73c4f7e8644..09e988c97931d 100644
--- a/clang/test/Sema/LifetimeSafety/dangling-field.cpp
+++ b/clang/test/Sema/LifetimeSafety/dangling-field.cpp
@@ -255,3 +255,26 @@ struct DtorSet {
   }
 };
 } // namespace DtorNoWarn
+
+namespace LambdaCaptureReset {
+struct MyObj {};
+struct HasField {
+  MyObj* ptr; // expected-note 2 {{this field dangles}}
+
+  void foo() {
+    MyObj local;
+    ptr = &local; // expected-warning-re {{stack memory associated with local 
variable 'local' may escape to the field 'ptr' which will dangle. {{.*}} 
captured by a lambda}}
+    auto cleanup = [this]() { // expected-note {{potentially captured by 
lambda here}}
+      ptr = nullptr;
+    };
+  }
+
+  void foo_init_capture() {
+    MyObj local;
+    ptr = &local; // expected-warning-re {{stack memory associated with local 
variable 'local' may escape to the field 'ptr' which will dangle. {{.*}} 
captured by a lambda}}
+    auto cleanup = [&p = ptr]() { // expected-note {{potentially captured by 
lambda here}}
+      p = nullptr;
+    };
+  }
+};
+} // namespace LambdaCaptureReset

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

Reply via email to