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
