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
