Author: Utkarsh Saxena
Date: 2026-08-05T16:57:07+02:00
New Revision: 8b6f3dcd31eded82c50519d0e2cd5c985f0093fd

URL: 
https://github.com/llvm/llvm-project/commit/8b6f3dcd31eded82c50519d0e2cd5c985f0093fd
DIFF: 
https://github.com/llvm/llvm-project/commit/8b6f3dcd31eded82c50519d0e2cd5c985f0093fd.diff

LOG: [LifetimeSafety] Suppress dangling field warnings for RAII resetters in 
permissive mode
 (#214212)

In `-Wlifetime-safety-permissive` mode, suppress dangling field warnings
(`-Wlifetime-safety-dangling-field`) when `this` or the escaping field
declaration is captured by a lambda within the function. This accounts
for common RAII field resetters that clean up dangling pointers on scope
exit.

### Motivating Example
Dangling field analysis can report false positives when an RAII field
resetter (such as `absl::MakeCleanup` or `absl::Cleanup`) captures
`this` or the field to reset the pointer before function exit:

```cpp
struct TimeServerInstance {
  Handler* handler_;

  void init() {
    Handler local_handler;
    handler_ = &local_handler;
    // False positive: intra-procedural analysis does not evaluate RAII
    // cleanup lambdas that reset dangling fields on scope exit.
    absl::Cleanup cleanup = [this] {
      handler_ = nullptr;
    };
  }
};

Added: 
    

Modified: 
    clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
    clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/Analysis/LifetimeSafety/Checker.cpp
    clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
    clang/lib/Sema/SemaLifetimeSafety.h
    clang/test/Sema/LifetimeSafety/dangling-field.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 94db2a7f311ae..04024efc6276b 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -20,6 +20,7 @@
 #include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"
 #include "clang/Analysis/AnalysisDeclContext.h"
 #include "clang/Analysis/CFG.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/Debug.h"
@@ -394,6 +395,12 @@ 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() { IsThisCapturedByLambda = true; }
+
 private:
   FactID NextFactID{0};
   LoanManager LoanMgr;
@@ -401,6 +408,13 @@ class FactManager {
   /// Facts for each CFG block, indexed by block ID.
   llvm::SmallVector<llvm::SmallVector<const Fact *>> BlockToFacts;
   llvm::BumpPtrAllocator FactAllocator;
+
+  /// Set of field declarations that are explicitly or init-captured in any
+  /// lambda within the analyzed function.
+  llvm::DenseSet<const FieldDecl *> CapturedFields;
+  /// Whether the 'this' pointer is captured by any lambda within the analyzed
+  /// function. When true, any field of 'this' is considered captured.
+  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 698f15c57aa9e..3f6f88cb46bca 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 6bd9871e91b45..b57ff6c197d1d 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..2f817378a9a14 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -323,11 +323,14 @@ 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.
+          bool IsCapturedByLambda =
+              FactMgr.isFieldCapturedByLambda(FieldEscape->getFieldDecl());
           SemaHelper->reportDanglingField(
-              IssueExpr, FieldEscape->getFieldDecl(), MovedExpr, ExpiryLoc);
-        else if (const auto *GlobalEscape = dyn_cast<GlobalEscapeFact>(OEF))
+              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..7cab40d9e929c 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -696,6 +696,20 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
 }
 
 void FactsGenerator::VisitLambdaExpr(const LambdaExpr *LE) {
+  for (const LambdaCapture &C : LE->captures()) {
+    if (C.capturesThis())
+      FactMgr.setThisCapturedByLambda();
+    else if (C.capturesVariable() && C.getCapturedVar()->isInitCapture()) {
+      const Expr *Init = cast<VarDecl>(C.getCapturedVar())->getInit();
+      if (!Init)
+        continue;
+      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..880f8e0f01a0e 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -145,11 +145,13 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
 
   void reportDanglingField(const Expr *IssueExpr,
                            const FieldDecl *DanglingField,
-                           const Expr *MovedExpr,
+                           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..8b68d962d807c 100644
--- a/clang/test/Sema/LifetimeSafety/dangling-field.cpp
+++ b/clang/test/Sema/LifetimeSafety/dangling-field.cpp
@@ -255,3 +255,34 @@ struct DtorSet {
   }
 };
 } // namespace DtorNoWarn
+
+namespace LambdaCaptureReset {
+struct MyObj {};
+struct HasField {
+  MyObj* ptr; // expected-note 3 {{this field dangles}}
+
+  void this_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 = [this]() {
+      ptr = nullptr;
+    };
+  }
+
+  void capture_by_ref() {
+    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 = [&]() {
+      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]() {
+      p = nullptr;
+    };
+  }
+};
+} // namespace LambdaCaptureReset


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

Reply via email to