https://github.com/usx95 updated 
https://github.com/llvm/llvm-project/pull/177363

>From 2b590c113adf4ab6578271d2c91a85fddda2020b Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <[email protected]>
Date: Thu, 22 Jan 2026 15:48:44 +0000
Subject: [PATCH] [LifetimeSafety] Detect dangling fields

---
 clang/docs/ReleaseNotes.rst                   |  12 +-
 .../Analysis/Analyses/LifetimeSafety/Facts.h  |  52 +++++-
 .../Analyses/LifetimeSafety/FactsGenerator.h  |   4 +-
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |   6 +-
 clang/include/clang/Analysis/CFG.h            |   5 +
 clang/include/clang/Basic/DiagnosticGroups.td |  11 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |   6 +
 clang/lib/Analysis/CFG.cpp                    |  21 ++-
 .../lib/Analysis/FlowSensitive/AdornedCFG.cpp |   1 +
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  17 +-
 clang/lib/Analysis/LifetimeSafety/Facts.cpp   |  13 +-
 .../LifetimeSafety/FactsGenerator.cpp         |  68 ++++++--
 .../Analysis/LifetimeSafety/LiveOrigins.cpp   |   9 +-
 clang/lib/Analysis/LifetimeSafety/Origins.cpp |  25 ++-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |  18 ++-
 clang/test/Analysis/lifetime-cfg-output.cpp   |  28 ----
 clang/test/Analysis/scopes-cfg-output.cpp     |   2 -
 .../Sema/warn-lifetime-analysis-nocfg.cpp     |  23 ++-
 .../warn-lifetime-safety-dangling-field.cpp   | 151 ++++++++++++++++++
 .../Sema/warn-lifetime-safety-dataflow.cpp    |   2 +-
 20 files changed, 381 insertions(+), 93 deletions(-)
 create mode 100644 clang/test/Sema/warn-lifetime-safety-dangling-field.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 791212dafd342..a98e43d8ed37f 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -128,7 +128,7 @@ Improvements to Clang's diagnostics
   a CFG-based intra-procedural analysis that detects use-after-free and related
   temporal safety bugs. See the
   `RFC 
<https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-analysis-in-clang/86291>`_
-  for more details. By design, this warning is enabled in ``-Wall``. To disable
+  for more details. By design, this warning is enabled in ``-Weverything``. To 
disable
   the analysis, use ``-Wno-lifetime-safety`` or ``-fno-lifetime-safety``.
 
 - Added ``-Wlifetime-safety-suggestions`` to enable lifetime annotation 
suggestions.
@@ -152,6 +152,16 @@ Improvements to Clang's diagnostics
     int* p(int *in) { return in; }
                              ^~
 
+- Added ``-Wlifetime-safety-dangling-field`` to detect dangling field 
references
+  when stack memory escapes to class fields. This is part of 
``-Wlifetime-safety``
+  and detects cases where local variables or parameters are stored in fields 
but
+  outlive their scope. For example:
+  .. code-block:: c++
+    struct DanglingView {
+      std::string_view view;
+      DanglingView(std::string s) : view(s) {}  // warning: address of stack 
memory escapes to a field
+    };
+
 Improvements to Clang's time-trace
 ----------------------------------
 
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index a66925b7302ca..61a4eea05bcb5 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -136,19 +136,63 @@ class OriginFlowFact : public Fact {
             const OriginManager &OM) const override;
 };
 
+/// Represents that an origin escapes the current scope through various means.
+/// This is the base class for different escape scenarios.
 class OriginEscapesFact : public Fact {
   OriginID OID;
-  const Expr *EscapeExpr;
 
 public:
+  /// The way an origin can escape the current scope.
+  enum class EscapeKind : uint8_t {
+    Return, /// Escapes via return statement.
+    Field,  /// Escapes via assignment to a field.
+    // FIXME: Add support for escape to global (dangling global ptr).
+  } EscKind;
+
   static bool classof(const Fact *F) {
     return F->getKind() == Kind::OriginEscapes;
   }
 
-  OriginEscapesFact(OriginID OID, const Expr *EscapeExpr)
-      : Fact(Kind::OriginEscapes), OID(OID), EscapeExpr(EscapeExpr) {}
+  OriginEscapesFact(OriginID OID, EscapeKind EscKind)
+      : Fact(Kind::OriginEscapes), OID(OID), EscKind(EscKind) {}
   OriginID getEscapedOriginID() const { return OID; }
-  const Expr *getEscapeExpr() const { return EscapeExpr; };
+  EscapeKind getEscapeKind() const { return EscKind; }
+};
+
+/// Represents that an origin escapes via a return statement.
+class ReturnEscapeFact : public OriginEscapesFact {
+  const Expr *ReturnExpr;
+
+public:
+  ReturnEscapeFact(OriginID OID, const Expr *ReturnExpr)
+      : OriginEscapesFact(OID, EscapeKind::Return), ReturnExpr(ReturnExpr) {}
+
+  static bool classof(const Fact *F) {
+    return F->getKind() == Kind::OriginEscapes &&
+           static_cast<const OriginEscapesFact *>(F)->getEscapeKind() ==
+               EscapeKind::Return;
+  }
+  const Expr *getReturnExpr() const { return ReturnExpr; };
+  void dump(llvm::raw_ostream &OS, const LoanManager &,
+            const OriginManager &OM) const override;
+};
+
+/// Represents that an origin escapes via assignment to a field.
+/// Example: `this->view = local_var;` where local_var outlives the assignment
+/// but not the object containing the field.
+class FieldEscapeFact : public OriginEscapesFact {
+  const FieldDecl *FDecl;
+
+public:
+  FieldEscapeFact(OriginID OID, const FieldDecl *FDecl)
+      : OriginEscapesFact(OID, EscapeKind::Field), FDecl(FDecl) {}
+
+  static bool classof(const Fact *F) {
+    return F->getKind() == Kind::OriginEscapes &&
+           static_cast<const OriginEscapesFact *>(F)->getEscapeKind() ==
+               EscapeKind::Field;
+  }
+  const FieldDecl *getFieldDecl() const { return FDecl; };
   void dump(llvm::raw_ostream &OS, const LoanManager &,
             const OriginManager &OM) const override;
 };
diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index a47505ee9f159..e4487b0d1dbc7 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -58,10 +58,12 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
 
   void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr);
 
+  void handleCXXCtorInitializer(const CXXCtorInitializer *CII);
   void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);
-
   void handleTemporaryDtor(const CFGTemporaryDtor &TemporaryDtor);
 
+  void handleExitBlock();
+
   void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
 
   /// Checks if a call-like expression creates a borrow by passing a value to a
diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 9c91355355233..8256d5829dcb9 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -52,10 +52,14 @@ class LifetimeSafetyReporter {
                                   Confidence Confidence) {}
 
   virtual void reportUseAfterReturn(const Expr *IssueExpr,
-                                    const Expr *EscapeExpr,
+                                    const Expr *ReturnExpr,
                                     SourceLocation ExpiryLoc,
                                     Confidence Confidence) {}
 
+  virtual void reportDanglingField(const Expr *IssueExpr,
+                                   const FieldDecl *Field,
+                                   SourceLocation ExpiryLoc) {}
+
   // Suggests lifetime bound annotations for function paramters
   virtual void suggestAnnotation(SuggestionScope Scope,
                                  const ParmVarDecl *ParmToAnnotate,
diff --git a/clang/include/clang/Analysis/CFG.h 
b/clang/include/clang/Analysis/CFG.h
index a4bafd4927df0..16efb24e58211 100644
--- a/clang/include/clang/Analysis/CFG.h
+++ b/clang/include/clang/Analysis/CFG.h
@@ -1235,6 +1235,11 @@ class CFG {
     bool AddEHEdges = false;
     bool AddInitializers = false;
     bool AddImplicitDtors = false;
+    // Add dtors for function parameters. In principle, function parameters are
+    // constructed and destructed in the caller context but analyses could 
still
+    // choose to include these in the callee's CFG to represent the dtors run 
on
+    // function exit.
+    bool AddParameterDtors = false;
     bool AddLifetime = false;
     bool AddLoopExit = false;
     bool AddTemporaryDtors = false;
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 34624dd3eed3a..c4cae4a30ec4f 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -533,14 +533,21 @@ def Dangling : DiagGroup<"dangling", [DanglingAssignment,
                                       DanglingGsl,
                                       ReturnStackAddress]>;
 
-def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive">;
+def LifetimeSafetyDanglingField : DiagGroup<"lifetime-safety-dangling-field"> {
+  code Documentation = [{Warning to detect dangling field references.}];
+}
+
+def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive",
+                                         [LifetimeSafetyDanglingField]>;
 def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict">;
+
 def LifetimeSafety : DiagGroup<"lifetime-safety",
                                [LifetimeSafetyPermissive, 
LifetimeSafetyStrict]> {
   code Documentation = [{
-    Experimental warnings to detect use-after-free and related temporal safety 
bugs based on lifetime safety analysis.
+    Warnings to detect use-after-free and related temporal safety bugs based 
on lifetime safety analysis.
   }];
 }
+
 def LifetimeSafetyCrossTUSuggestions
     : DiagGroup<"lifetime-safety-cross-tu-suggestions">;
 def LifetimeSafetyIntraTUSuggestions
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a2be7ab3791b9..8b05b0ffdd04d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10816,9 +10816,15 @@ def warn_lifetime_safety_return_stack_addr_strict
       InGroup<LifetimeSafetyStrict>,
       DefaultIgnore;
 
+def warn_lifetime_safety_dangling_field
+    : Warning<"address of stack memory escapes to a field">,
+      InGroup<LifetimeSafetyDanglingField>,
+      DefaultIgnore;
+
 def note_lifetime_safety_used_here : Note<"later used here">;
 def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
 def note_lifetime_safety_returned_here : Note<"returned here">;
+def note_lifetime_safety_dangling_field_here: Note<"this field dangles">;
 
 def warn_lifetime_safety_intra_tu_suggestion
     : Warning<"parameter in intra-TU function should be marked "
diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index a9c7baa00543c..fdca00a0f196f 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -1667,12 +1667,21 @@ std::unique_ptr<CFG> CFGBuilder::buildCFG(const Decl 
*D, Stmt *Statement) {
   assert(Succ == &cfg->getExit());
   Block = nullptr;  // the EXIT block is empty.  Create all other blocks 
lazily.
 
-  // Add parameters to the initial scope to handle their dtos and lifetime 
ends.
-  LocalScope *paramScope = nullptr;
-  if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D))
-    for (ParmVarDecl *PD : FD->parameters())
-      paramScope = addLocalScopeForVarDecl(PD, paramScope);
-
+  if (BuildOpts.AddParameterDtors) {
+    // Add parameters to the initial scope to handle their dtos and lifetime
+    // ends.
+    LocalScope *paramScope = nullptr;
+    if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D))
+      for (ParmVarDecl *PD : FD->parameters()) {
+        paramScope = addLocalScopeForVarDecl(PD, paramScope);
+      }
+    if (auto *C = dyn_cast<CompoundStmt>(Statement))
+      if (C->body_empty() || !isa<ReturnStmt>(*C->body_rbegin()))
+        // If the body ends with a ReturnStmt, the dtors will be added in
+        // VisitReturnStmt.
+        addAutomaticObjHandling(ScopePos, LocalScope::const_iterator(),
+                                Statement);
+  }
   if (BuildOpts.AddImplicitDtors)
     if (const CXXDestructorDecl *DD = dyn_cast_or_null<CXXDestructorDecl>(D))
       addImplicitDtorsForDestructor(DD);
diff --git a/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp 
b/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp
index 6c4847c7c23fb..c47d7b5f9c3d6 100644
--- a/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp
+++ b/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp
@@ -159,6 +159,7 @@ llvm::Expected<AdornedCFG> AdornedCFG::build(const Decl &D, 
Stmt &S,
   Options.PruneTriviallyFalseEdges = true;
   Options.AddImplicitDtors = true;
   Options.AddTemporaryDtors = true;
+  Options.AddParameterDtors = true;
   Options.AddInitializers = true;
   Options.AddCXXDefaultInitExprInCtors = true;
   Options.AddLifetime = true;
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index f7383126fac38..9ae17168d9b93 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -91,7 +91,8 @@ class LifetimeChecker {
         const ParmVarDecl *PVD = PL->getParmVarDecl();
         if (PVD->hasAttr<LifetimeBoundAttr>())
           continue;
-        AnnotationWarningsMap.try_emplace(PVD, OEF->getEscapeExpr());
+        if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+          AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
       }
     }
   }
@@ -154,10 +155,16 @@ class LifetimeChecker {
         Reporter->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc,
                                      Confidence);
       else if (const auto *OEF =
-                   CausingFact.dyn_cast<const OriginEscapesFact *>())
-        Reporter->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(),
-                                       ExpiryLoc, Confidence);
-      else
+                   CausingFact.dyn_cast<const OriginEscapesFact *>()) {
+        if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
+          Reporter->reportUseAfterReturn(IssueExpr, RetEscape->getReturnExpr(),
+                                         ExpiryLoc, Confidence);
+        else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF))
+          Reporter->reportDanglingField(IssueExpr, FieldEscape->getFieldDecl(),
+                                        ExpiryLoc);
+        else
+          llvm_unreachable("Unhandled OriginEscapesFact type");
+      } else
         llvm_unreachable("Unhandled CausingFact type");
     }
   }
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp 
b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 2673ce5ba354b..1fc72aa0a4259 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -45,11 +45,18 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const 
LoanManager &,
   OS << "\n";
 }
 
-void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &,
-                             const OriginManager &OM) const {
+void ReturnEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+                            const OriginManager &OM) const {
   OS << "OriginEscapes (";
   OM.dump(getEscapedOriginID(), OS);
-  OS << ")\n";
+  OS << ", via Return)\n";
+}
+
+void FieldEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+                           const OriginManager &OM) const {
+  OS << "OriginEscapes (";
+  OM.dump(getEscapedOriginID(), OS);
+  OS << ", via Field)\n";
 }
 
 void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index d5990597e1614..47c919b7d139d 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "clang/AST/OperationKinds.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
@@ -107,6 +108,9 @@ void FactsGenerator::run() {
       const CFGElement &Element = Block->Elements[I];
       if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
         Visit(CS->getStmt());
+      else if (std::optional<CFGInitializer> Initializer =
+                   Element.getAs<CFGInitializer>())
+        handleCXXCtorInitializer(Initializer->getInitializer());
       else if (std::optional<CFGLifetimeEnds> LifetimeEnds =
                    Element.getAs<CFGLifetimeEnds>())
         handleLifetimeEnds(*LifetimeEnds);
@@ -114,6 +118,9 @@ void FactsGenerator::run() {
                    Element.getAs<CFGTemporaryDtor>())
         handleTemporaryDtor(*TemporaryDtor);
     }
+    if (Block == &Cfg.getExit())
+      handleExitBlock();
+
     CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
                              EscapesInCurrentBlock.end());
     FactMgr.addBlockFacts(Block, CurrentBlockFacts);
@@ -180,6 +187,13 @@ void FactsGenerator::VisitCXXConstructExpr(const 
CXXConstructExpr *CCE) {
   }
 }
 
+void FactsGenerator::handleCXXCtorInitializer(const CXXCtorInitializer *CII) {
+  // Flows origins from the initializer expression to the field.
+  // Example: `MyObj(std::string s) : view(s) {}`
+  if (const FieldDecl *FD = CII->getAnyMember())
+    killAndFlowOrigin(*FD, *CII->getInit());
+}
+
 void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
   // Specifically for conversion operators,
   // like `std::string_view p = std::string{};`
@@ -316,31 +330,42 @@ void FactsGenerator::VisitReturnStmt(const ReturnStmt 
*RS) {
   if (const Expr *RetExpr = RS->getRetValue()) {
     if (OriginList *List = getOriginsList(*RetExpr))
       for (OriginList *L = List; L != nullptr; L = L->peelOuterOrigin())
-        EscapesInCurrentBlock.push_back(FactMgr.createFact<OriginEscapesFact>(
+        EscapesInCurrentBlock.push_back(FactMgr.createFact<ReturnEscapeFact>(
             L->getOuterOriginID(), RetExpr));
   }
 }
 
 void FactsGenerator::handleAssignment(const Expr *LHSExpr,
                                       const Expr *RHSExpr) {
-  if (const auto *DRE_LHS =
-          dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
-    OriginList *LHSList = getOriginsList(*DRE_LHS);
-    assert(LHSList && "LHS is a DRE and should have an origin list");
-    OriginList *RHSList = getOriginsList(*RHSExpr);
-
-    // For operator= with reference parameters (e.g.,
-    // `View& operator=(const View&)`), the RHS argument stays an lvalue,
-    // unlike built-in assignment where LValueToRValue cast strips the outer
-    // lvalue origin. Strip it manually to get the actual value origins being
-    // assigned.
-    RHSList = getRValueOrigins(RHSExpr, RHSList);
+  LHSExpr = LHSExpr->IgnoreParenImpCasts();
+  OriginList *LHSList = nullptr;
 
-    markUseAsWrite(DRE_LHS);
-    // Kill the old loans of the destination origin and flow the new loans
-    // from the source origin.
-    flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
+  if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
+    LHSList = getOriginsList(*DRE_LHS);
+    assert(LHSList && "LHS is a DRE and should have an origin list");
+  }
+  // Handle assignment to member fields (e.g., `this->view = s` or `view = s`).
+  // This enables detection of dangling fields when local values escape to
+  // fields.
+  if (const auto *ME_LHS = dyn_cast<MemberExpr>(LHSExpr)) {
+    LHSList = getOriginsList(*ME_LHS);
+    assert(LHSList && "LHS is a MemberExpr and should have an origin list");
   }
+  if (!LHSList)
+    return;
+  OriginList *RHSList = getOriginsList(*RHSExpr);
+  // For operator= with reference parameters (e.g.,
+  // `View& operator=(const View&)`), the RHS argument stays an lvalue,
+  // unlike built-in assignment where LValueToRValue cast strips the outer
+  // lvalue origin. Strip it manually to get the actual value origins being
+  // assigned.
+  RHSList = getRValueOrigins(RHSExpr, RHSList);
+
+  if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr))
+    markUseAsWrite(DRE_LHS);
+  // Kill the old loans of the destination origin and flow the new loans
+  // from the source origin.
+  flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
 }
 
 void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
@@ -463,6 +488,15 @@ void FactsGenerator::handleTemporaryDtor(
   }
 }
 
+void FactsGenerator::handleExitBlock() {
+  // Creates FieldEscapeFacts for all field origins that remain live at exit.
+  for (const Origin &O : FactMgr.getOriginMgr().getOrigins())
+    if (O.getDecl())
+      if (auto *FD = dyn_cast<FieldDecl>(O.getDecl()))
+        EscapesInCurrentBlock.push_back(
+            FactMgr.createFact<FieldEscapeFact>(O.ID, FD));
+}
+
 void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) 
{
   assert(isGslPointerType(CCE->getType()));
   if (CCE->getNumArgs() != 1)
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp 
b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index 862dca256280f..f210fb4d752d4 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -8,6 +8,7 @@
 
 #include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
 #include "Dataflow.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
 #include "llvm/Support/ErrorHandling.h"
 
 namespace clang::lifetimes::internal {
@@ -56,8 +57,12 @@ struct Lattice {
 static SourceLocation GetFactLoc(CausingFactType F) {
   if (const auto *UF = F.dyn_cast<const UseFact *>())
     return UF->getUseExpr()->getExprLoc();
-  if (const auto *OEF = F.dyn_cast<const OriginEscapesFact *>())
-    return OEF->getEscapeExpr()->getExprLoc();
+  if (const auto *OEF = F.dyn_cast<const OriginEscapesFact *>()) {
+    if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+      return ReturnEsc->getReturnExpr()->getExprLoc();
+    if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
+      return FieldEsc->getFieldDecl()->getLocation();
+  }
   llvm_unreachable("unhandled causing fact in PointerUnion");
 }
 
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp 
b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index ca933f612eb08..6062b3d42ebe2 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -9,6 +9,7 @@
 #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Attr.h"
+#include "clang/AST/Decl.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclTemplate.h"
 #include "clang/AST/Expr.h"
@@ -138,25 +139,33 @@ OriginList *OriginManager::getOrCreateList(const Expr *E) 
{
 
   QualType Type = E->getType();
 
-  // Special handling for DeclRefExpr to share origins with the underlying 
decl.
-  if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+  // Special handling for expressions referring to a decl to share origins with
+  // the underlying decl.
+  const ValueDecl *ReferencedDecl = nullptr;
+  if (auto *DRE = dyn_cast<DeclRefExpr>(E))
+    ReferencedDecl = DRE->getDecl();
+  if (auto *ME = dyn_cast<MemberExpr>(E))
+    if (auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
+        Field && isa<CXXThisExpr>(ME->getBase()))
+      ReferencedDecl = Field;
+  if (ReferencedDecl) {
     OriginList *Head = nullptr;
-    // For non-reference declarations (e.g., `int* p`), the DeclRefExpr is an
+    // For non-reference declarations (e.g., `int* p`), the expression is an
     // lvalue (addressable) that can be borrowed, so we create an outer origin
     // for the lvalue itself, with the pointee being the declaration's list.
     // This models taking the address: `&p` borrows the storage of `p`, not 
what
     // `p` points to.
-    if (doesDeclHaveStorage(DRE->getDecl())) {
-      Head = createNode(DRE, QualType{});
-      // This ensures origin sharing: multiple DeclRefExprs to the same
+    if (doesDeclHaveStorage(ReferencedDecl)) {
+      Head = createNode(E, QualType{});
+      // This ensures origin sharing: multiple expressions to the same
       // declaration share the same underlying origins.
-      Head->setInnerOriginList(getOrCreateList(DRE->getDecl()));
+      Head->setInnerOriginList(getOrCreateList(ReferencedDecl));
     } else {
       // For reference-typed declarations (e.g., `int& r = p`) which have no
       // storage, the DeclRefExpr directly reuses the declaration's list since
       // references don't add an extra level of indirection at the expression
       // level.
-      Head = getOrCreateList(DRE->getDecl());
+      Head = getOrCreateList(ReferencedDecl);
     }
     return ExprToList[E] = Head;
   }
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 03d84fc935b8e..9f88d43d6f6f8 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2889,16 +2889,24 @@ class LifetimeSafetyReporterImpl : public 
LifetimeSafetyReporter {
         << UseExpr->getSourceRange();
   }
 
-  void reportUseAfterReturn(const Expr *IssueExpr, const Expr *EscapeExpr,
+  void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr,
                             SourceLocation ExpiryLoc, Confidence C) override {
     S.Diag(IssueExpr->getExprLoc(),
            C == Confidence::Definite
                ? diag::warn_lifetime_safety_return_stack_addr_permissive
                : diag::warn_lifetime_safety_return_stack_addr_strict)
         << IssueExpr->getSourceRange();
-
-    S.Diag(EscapeExpr->getExprLoc(), diag::note_lifetime_safety_returned_here)
-        << EscapeExpr->getSourceRange();
+    S.Diag(ReturnExpr->getExprLoc(), diag::note_lifetime_safety_returned_here)
+        << ReturnExpr->getSourceRange();
+  }
+  void reportDanglingField(const Expr *IssueExpr,
+                           const FieldDecl *DanglingField,
+                           SourceLocation ExpiryLoc) override {
+    S.Diag(IssueExpr->getExprLoc(), diag::warn_lifetime_safety_dangling_field)
+        << IssueExpr->getSourceRange();
+    S.Diag(DanglingField->getLocation(),
+           diag::note_lifetime_safety_dangling_field_here)
+        << DanglingField->getEndLoc();
   }
 
   void suggestAnnotation(SuggestionScope Scope,
@@ -2952,6 +2960,7 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU,
     AC.getCFGBuildOptions().PruneTriviallyFalseEdges = false;
     AC.getCFGBuildOptions().AddLifetime = true;
     AC.getCFGBuildOptions().AddImplicitDtors = true;
+    AC.getCFGBuildOptions().AddParameterDtors = true;
     AC.getCFGBuildOptions().AddTemporaryDtors = true;
     AC.getCFGBuildOptions().setAllAlwaysAdd();
     if (AC.getCFG())
@@ -3059,6 +3068,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
   AC.getCFGBuildOptions().AddEHEdges = false;
   AC.getCFGBuildOptions().AddInitializers = true;
   AC.getCFGBuildOptions().AddImplicitDtors = true;
+  AC.getCFGBuildOptions().AddParameterDtors = true;
   AC.getCFGBuildOptions().AddTemporaryDtors = true;
   AC.getCFGBuildOptions().AddCXXNewAllocator = false;
   AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true;
diff --git a/clang/test/Analysis/lifetime-cfg-output.cpp 
b/clang/test/Analysis/lifetime-cfg-output.cpp
index 36b36eddc440c..0a75c5bcc0bcc 100644
--- a/clang/test/Analysis/lifetime-cfg-output.cpp
+++ b/clang/test/Analysis/lifetime-cfg-output.cpp
@@ -935,31 +935,3 @@ int backpatched_goto() {
   goto label;
   i++;
 }
-
-// CHECK:       [B2 (ENTRY)]
-// CHECK-NEXT:    Succs (1): B1
-// CHECK:       [B1]
-// CHECK-NEXT:    1: a
-// CHECK-NEXT:    2: [B1.1] (ImplicitCastExpr, LValueToRValue, int)
-// CHECK-NEXT:    3: b
-// CHECK-NEXT:    4: [B1.3] (ImplicitCastExpr, LValueToRValue, int)
-// CHECK-NEXT:    5: [B1.2] + [B1.4]
-// CHECK-NEXT:    6: c
-// CHECK-NEXT:    7: [B1.6] (ImplicitCastExpr, LValueToRValue, int)
-// CHECK-NEXT:    8: [B1.5] + [B1.7]
-// CHECK-NEXT:    9: int res = a + b + c;
-// CHECK-NEXT:    10: res
-// CHECK-NEXT:    11: [B1.10] (ImplicitCastExpr, LValueToRValue, int)
-// CHECK-NEXT:    12: return [B1.11];
-// CHECK-NEXT:    13: [B1.9] (Lifetime ends)
-// CHECK-NEXT:    14: [Parm: c] (Lifetime ends)
-// CHECK-NEXT:    15: [Parm: b] (Lifetime ends)
-// CHECK-NEXT:    16: [Parm: a] (Lifetime ends)
-// CHECK-NEXT:    Preds (1): B2
-// CHECK-NEXT:    Succs (1): B0
-// CHECK:       [B0 (EXIT)]
-// CHECK-NEXT:    Preds (1): B1
-int test_param_scope_end_order(int a, int b, int c) {
-  int res = a + b + c;
-  return res; 
-}
diff --git a/clang/test/Analysis/scopes-cfg-output.cpp 
b/clang/test/Analysis/scopes-cfg-output.cpp
index 9c75492c33a42..6ed6f3638f75b 100644
--- a/clang/test/Analysis/scopes-cfg-output.cpp
+++ b/clang/test/Analysis/scopes-cfg-output.cpp
@@ -1437,14 +1437,12 @@ void test_cleanup_functions() {
 // CHECK-NEXT:    4: return;
 // CHECK-NEXT:    5: CleanupFunction (cleanup_int)
 // CHECK-NEXT:    6: CFGScopeEnd(i)
-// CHECK-NEXT:    7: CFGScopeEnd(m)
 // CHECK-NEXT:    Preds (1): B3
 // CHECK-NEXT:    Succs (1): B0
 // CHECK:      [B2]
 // CHECK-NEXT:    1: return;
 // CHECK-NEXT:    2: CleanupFunction (cleanup_int)
 // CHECK-NEXT:    3: CFGScopeEnd(i)
-// CHECK-NEXT:    4: CFGScopeEnd(m)
 // CHECK-NEXT:    Preds (1): B3
 // CHECK-NEXT:    Succs (1): B0
 // CHECK:      [B3]
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp 
b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 99a796c360a7f..ae768c1e2077c 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -1,6 +1,5 @@
 // RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s
 // RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=cfg %s
-// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference 
-fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling 
-verify=cfg %s
 
 #include "Inputs/lifetime-analysis.h"
 
@@ -80,11 +79,18 @@ void dangligGslPtrFromTemporary() {
 }
 
 struct DanglingGslPtrField {
-  MyIntPointer p; // expected-note {{pointer member declared here}}
-  MyLongPointerFromConversion p2; // expected-note {{pointer member declared 
here}}
-  DanglingGslPtrField(int i) : p(&i) {} // TODO
-  DanglingGslPtrField() : p2(MyLongOwnerWithConversion{}) {} // 
expected-warning {{initializing pointer member 'p2' to point to a temporary 
object whose lifetime is shorter than the lifetime of the constructed object}}
-  DanglingGslPtrField(double) : p(MyIntOwner{}) {} // expected-warning 
{{initializing pointer member 'p' to point to a temporary object whose lifetime 
is shorter than the lifetime of the constructed object}}
+  MyIntPointer p; // expected-note {{pointer member declared here}} \
+                  // cfg-note 3 {{this field dangles}}
+  MyLongPointerFromConversion p2; // expected-note {{pointer member declared 
here}} \
+                                  // cfg-note 2 {{this field dangles}}
+
+  DanglingGslPtrField(int i) : p(&i) {} // cfg-warning {{address of stack 
memory escapes to a field}}
+  DanglingGslPtrField() : p2(MyLongOwnerWithConversion{}) {}  // 
expected-warning {{initializing pointer member 'p2' to point to a temporary 
object whose lifetime is shorter than the lifetime of the constructed object}} \
+                                                              // cfg-warning 
{{address of stack memory escapes to a field}}
+  DanglingGslPtrField(double) : p(MyIntOwner{}) {}  // expected-warning 
{{initializing pointer member 'p' to point to a temporary object whose lifetime 
is shorter than the lifetime of the constructed object}} \
+                                                    // cfg-warning {{address 
of stack memory escapes to a field}}
+  DanglingGslPtrField(MyIntOwner io) : p(io) {} // cfg-warning {{address of 
stack memory escapes to a field}}
+  DanglingGslPtrField(MyLongOwnerWithConversion lo) : p2(lo) {} // cfg-warning 
{{address of stack memory escapes to a field}}
 };
 
 MyIntPointer danglingGslPtrFromLocal() {
@@ -1099,10 +1105,11 @@ struct Foo2 {
 };
 
 struct Test {
-  Test(Foo2 foo) : bar(foo.bar.get()), // OK
+  Test(Foo2 foo) : bar(foo.bar.get()), // OK \
+      // FIXME: cfg-warning {{address of stack memory escapes to a field}}
       storage(std::move(foo.bar)) {};
 
-  Bar* bar;
+  Bar* bar; // cfg-note {{this field dangles}}
   std::unique_ptr<Bar> storage;
 };
 
diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp 
b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
new file mode 100644
index 0000000000000..c12b9088e9a38
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
@@ -0,0 +1,151 @@
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+template<int N> struct Dummy {};
+static std::string kGlobal = "GLOBAL";
+void takeString(std::string&& s);
+
+std::string_view construct_view(const std::string& str 
[[clang::lifetimebound]]);
+
+struct CtorInit {
+  std::string_view view;  // expected-note {{this field dangles}}
+  CtorInit(std::string s) : view(s) {} // expected-warning {{address of stack 
memory escapes to a field}}
+};
+
+struct CtorSet {
+  std::string_view view;  // expected-note {{this field dangles}}
+  CtorSet(std::string s) { view = s; } // expected-warning {{address of stack 
memory escapes to a field}}
+};
+
+struct CtorInitLifetimeBound {
+  std::string_view view;  // expected-note {{this field dangles}}
+  CtorInitLifetimeBound(std::string s) : view(construct_view(s)) {} // 
expected-warning {{address of stack memory escapes to a field}}
+};
+
+struct CtorInitButMoved {
+  std::string_view view;
+  CtorInitButMoved(std::string s) : view(s) { takeString(std::move(s)); }
+};
+
+struct CtorInitButMovedOwned {
+  std::string owned;
+  std::string_view view;
+  CtorInitButMovedOwned(std::string s) : view(s), owned(std::move(s)) {}
+  CtorInitButMovedOwned(Dummy<1>, std::string s) : owned(std::move(s)), 
view(owned) {}
+};
+
+struct CtorInitMultipleViews {
+  std::string_view view1; // expected-note {{this field dangles}}
+  std::string_view view2; // expected-note {{this field dangles}}
+  CtorInitMultipleViews(std::string s) : view1(s),   // expected-warning 
{{address of stack memory escapes to a field}}
+                                         view2(s) {} // expected-warning 
{{address of stack memory escapes to a field}}
+};
+
+struct CtorInitMultipleParams {
+  std::string_view view1; // expected-note {{this field dangles}}
+  std::string_view view2; // expected-note {{this field dangles}}
+  CtorInitMultipleParams(std::string s1, std::string s2) : view1(s1),   // 
expected-warning {{address of stack memory escapes to a field}}
+                                                           view2(s2) {} // 
expected-warning {{address of stack memory escapes to a field}}
+};
+
+struct CtorRefField {
+  const std::string& str;       // expected-note {{this field dangles}}
+  const std::string_view& view; // expected-note {{this field dangles}}
+  CtorRefField(std::string s, std::string_view v) : str(s),     // 
expected-warning {{address of stack memory escapes to a field}}
+                                                    view(v) {}  // 
expected-warning {{address of stack memory escapes to a field}}
+  CtorRefField(Dummy<1> ok, const std::string& s, const std::string_view& v): 
str(s), view(v) {}
+};
+
+struct CtorPointerField {
+  const char* ptr; // expected-note {{this field dangles}}
+  CtorPointerField(std::string s) : ptr(s.data()) {}  // expected-warning 
{{address of stack memory escapes to a field}}
+  CtorPointerField(Dummy<1> ok, const std::string& s) : ptr(s.data()) {}
+  CtorPointerField(Dummy<2> ok, std::string_view view) : ptr(view.data()) {}
+};
+
+struct MemberSetters {
+  std::string_view view;  // expected-note 5 {{this field dangles}}
+  const char* p;          // expected-note 5 {{this field dangles}}
+
+  void setWithParam(std::string s) {
+    view = s;     // expected-warning {{address of stack memory escapes to a 
field}}
+    p = s.data(); // expected-warning {{address of stack memory escapes to a 
field}}
+  }
+
+  void setWithParamAndReturn(std::string s) {
+    view = s;     // expected-warning {{address of stack memory escapes to a 
field}}
+    p = s.data(); // expected-warning {{address of stack memory escapes to a 
field}}
+    return;
+  }
+
+  void setWithParamOk(const std::string& s) {
+    view = s;
+    p = s.data();
+  }
+
+  void setWithParamOkAndReturn(const std::string& s) {
+    view = s;
+    p = s.data();
+    return;
+  }
+
+  void setWithLocal() {
+    std::string s;
+    view = s;     // expected-warning {{address of stack memory escapes to a 
field}}
+    p = s.data(); // expected-warning {{address of stack memory escapes to a 
field}}
+  }
+  
+  void setWithLocalButMoved() {
+    std::string s;
+    view = s;
+    p = s.data();
+    takeString(std::move(s));
+  }
+
+  void setWithGlobal() {
+    view = kGlobal;
+    p = kGlobal.data();
+  }
+
+  void setWithLocalThenWithGlobal() {
+    std::string local;
+    view = local;
+    p = local.data();
+
+    view = kGlobal;
+    p = kGlobal.data();
+  }
+
+  void setWithGlobalThenWithLocal() {
+    view = kGlobal;
+    p = kGlobal.data();
+
+    std::string local;
+    view = local;     // expected-warning {{address of stack memory escapes to 
a field}}
+    p = local.data(); // expected-warning {{address of stack memory escapes to 
a field}}
+  }
+
+  void use_after_scope() {
+    {
+      std::string local;
+      view = local;     // expected-warning {{address of stack memory escapes 
to a field}}
+      p = local.data(); // expected-warning {{address of stack memory escapes 
to a field}}
+    }
+    (void)view;
+    (void)p;
+  }
+
+  void use_after_scope_saved_after_reassignment() {
+    {
+      std::string local;
+      view = local;
+      p = local.data();
+    }
+    (void)view;
+    (void)p;
+
+    view = kGlobal;
+    p = kGlobal.data();
+  }
+};
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp 
b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index a45100feb3f28..7e2215b8deedc 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -27,7 +27,7 @@ MyObj* return_local_addr() {
 // CHECK-NEXT:       Src:  [[O_P]] (Decl: p, Type : MyObj *)
 // CHECK:   Expire ([[L_X]] (Path: x))
 // CHECK:   Expire ({{[0-9]+}} (Path: p))
-// CHECK:   OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr, Type : MyObj 
*))
+// CHECK:   OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr, Type : MyObj 
*), via Return)
 }
 
 // Loan Expiration (Automatic Variable, C++)

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

Reply via email to