Author: Utkarsh Saxena
Date: 2026-01-28T13:42:32Z
New Revision: 47e9c1db618c4d4a7dc6d9880160cc32b317b686

URL: 
https://github.com/llvm/llvm-project/commit/47e9c1db618c4d4a7dc6d9880160cc32b317b686
DIFF: 
https://github.com/llvm/llvm-project/commit/47e9c1db618c4d4a7dc6d9880160cc32b317b686.diff

LOG: [LifetimeSafety] Detect dangling fields (#177363)

Detect dangling field references when stack memory escapes to class
fields. This change extends lifetime safety analysis to detect a common
class of temporal memory safety bugs where local variables or parameters
are stored in class fields but outlive their scope.

- Added a new `FieldEscapeFact` class to represent when an origin
escapes via assignment to a field
- Refactored `OriginEscapesFact` into a base class with specialized
subclasses for different escape scenarios
- Added detection for stack memory escaping to fields in constructors
and member functions
- Implemented new diagnostic for dangling field references with
appropriate warning messages

Importantly,
- Added `AddParameterDtors` option to CFG to add parameter dtors and
lifetime ends behind an option. In principle, parameters ctors and dtors
do not belong in the function context but in the caller context. This
becomes incorrect to include in function's CFG when we have inlined CFGs
like some analyses in the analyzer (produces double dtors for
arguments). Therefore this provides a way to opt-in to know about
destructed params on function exits.

Added: 
    clang/test/Sema/warn-lifetime-safety-dangling-field.cpp

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
    clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
    clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
    clang/include/clang/Analysis/CFG.h
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/Analysis/CFG.cpp
    clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp
    clang/lib/Analysis/LifetimeSafety/Checker.cpp
    clang/lib/Analysis/LifetimeSafety/Facts.cpp
    clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
    clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
    clang/lib/Analysis/LifetimeSafety/Origins.cpp
    clang/lib/Sema/AnalysisBasedWarnings.cpp
    clang/test/Analysis/lifetime-cfg-output.cpp
    clang/test/Analysis/scopes-cfg-output.cpp
    clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
    clang/test/Sema/warn-lifetime-safety-dataflow.cpp
    clang/test/Sema/warn-lifetime-safety-noescape.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index e22e7e0fce05c..b5327c01fbb81 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -145,7 +145,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.
@@ -187,6 +187,18 @@ Improvements to Clang's diagnostics
     int* p(int *in [[clang::noescape]]) { 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 1bb34e6986857..d948965af34d5 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 
diff erent 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 2ab60d918c8d1..9f22db20e79b1 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -62,10 +62,14 @@ class LifetimeSafetySemaHelper {
                                   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 suggestLifetimeboundToParmVar(SuggestionScope Scope,
                                              const ParmVarDecl *ParmToAnnotate,
@@ -74,6 +78,9 @@ class LifetimeSafetySemaHelper {
   // Reports misuse of [[clang::noescape]] when parameter escapes through 
return
   virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
                                        const Expr *EscapeExpr) {}
+  // Reports misuse of [[clang::noescape]] when parameter escapes through field
+  virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
+                                       const FieldDecl *EscapeField) {}
 
   // Suggests lifetime bound annotations for implicit this.
   virtual void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,

diff  --git a/clang/include/clang/Analysis/CFG.h 
b/clang/include/clang/Analysis/CFG.h
index a4bafd4927df0..e675935d9230a 100644
--- a/clang/include/clang/Analysis/CFG.h
+++ b/clang/include/clang/Analysis/CFG.h
@@ -1236,6 +1236,11 @@ class CFG {
     bool AddInitializers = false;
     bool AddImplicitDtors = false;
     bool AddLifetime = false;
+    // Add lifetime markers 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 lifetime ends of parameters on function exit.
+    bool AddParameterLifetimes = false;
     bool AddLoopExit = false;
     bool AddTemporaryDtors = false;
     bool AddScopes = false;

diff  --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 488f3a94c4fb6..d36ee57fe7651 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -533,14 +533,23 @@ 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 c786eb4486829..807440c107897 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10826,9 +10826,16 @@ 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 note_lifetime_safety_escapes_to_field_here: Note<"escapes to this field">;
 
 def warn_lifetime_safety_intra_tu_param_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..8001a67a5e158 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -1667,12 +1667,20 @@ 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.AddLifetime && BuildOpts.AddParameterLifetimes) {
+    // Add parameters to the initial scope to handle 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..6f4c3e6531e9b 100644
--- a/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp
+++ b/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp
@@ -162,6 +162,7 @@ llvm::Expected<AdornedCFG> AdornedCFG::build(const Decl &D, 
Stmt &S,
   Options.AddInitializers = true;
   Options.AddCXXDefaultInitExprInCtors = true;
   Options.AddLifetime = true;
+  Options.AddParameterLifetimes = true;
 
   // Ensure that all sub-expressions in basic blocks are evaluated.
   Options.setAllAlwaysAdd();

diff  --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index c6368786f34fe..c954a9b14bcdf 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -12,6 +12,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "clang/Analysis/Analyses/LifetimeSafety/Checker.h"
+#include "clang/AST/Decl.h"
 #include "clang/AST/Expr.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
@@ -51,12 +52,13 @@ struct PendingWarning {
 
 using AnnotationTarget =
     llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>;
+using EscapingTarget = llvm::PointerUnion<const Expr *, const FieldDecl *>;
 
 class LifetimeChecker {
 private:
   llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
   llvm::DenseMap<AnnotationTarget, const Expr *> AnnotationWarningsMap;
-  llvm::DenseMap<const ParmVarDecl *, const Expr *> NoescapeWarningsMap;
+  llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap;
   const LoanPropagationAnalysis &LoanPropagation;
   const LiveOriginsAnalysis &LiveOrigins;
   const FactManager &FactMgr;
@@ -92,22 +94,36 @@ class LifetimeChecker {
   void checkAnnotations(const OriginEscapesFact *OEF) {
     OriginID EscapedOID = OEF->getEscapedOriginID();
     LoanSet EscapedLoans = LoanPropagation.getLoans(EscapedOID, OEF);
+    auto CheckParam = [&](const ParmVarDecl *PVD) {
+      // NoEscape param should not escape.
+      if (PVD->hasAttr<NoEscapeAttr>()) {
+        if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+          NoescapeWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
+        if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
+          NoescapeWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
+        return;
+      }
+      // Suggest lifetimebound for parameter escaping through return.
+      if (!PVD->hasAttr<LifetimeBoundAttr>())
+        if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+          AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
+      // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a
+      // field!
+    };
+    auto CheckImplicitThis = [&](const CXXMethodDecl *MD) {
+      if (!implicitObjectParamIsLifetimeBound(MD))
+        if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+          AnnotationWarningsMap.try_emplace(MD, ReturnEsc->getReturnExpr());
+    };
     for (LoanID LID : EscapedLoans) {
       const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
-      if (const auto *PL = dyn_cast<PlaceholderLoan>(L)) {
-        if (const auto *PVD = PL->getParmVarDecl()) {
-          if (PVD->hasAttr<NoEscapeAttr>()) {
-            NoescapeWarningsMap.try_emplace(PVD, OEF->getEscapeExpr());
-            continue;
-          }
-          if (PVD->hasAttr<LifetimeBoundAttr>())
-            continue;
-          AnnotationWarningsMap.try_emplace(PVD, OEF->getEscapeExpr());
-        } else if (const auto *MD = PL->getMethodDecl()) {
-          if (!implicitObjectParamIsLifetimeBound(MD))
-            AnnotationWarningsMap.try_emplace(MD, OEF->getEscapeExpr());
-        }
-      }
+      const auto *PL = dyn_cast<PlaceholderLoan>(L);
+      if (!PL)
+        continue;
+      if (const auto *PVD = PL->getParmVarDecl())
+        CheckParam(PVD);
+      else if (const auto *MD = PL->getMethodDecl())
+        CheckImplicitThis(MD);
     }
   }
 
@@ -169,10 +185,16 @@ class LifetimeChecker {
         SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc,
                                        Confidence);
       else if (const auto *OEF =
-                   CausingFact.dyn_cast<const OriginEscapesFact *>())
-        SemaHelper->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(),
-                                         ExpiryLoc, Confidence);
-      else
+                   CausingFact.dyn_cast<const OriginEscapesFact *>()) {
+        if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
+          SemaHelper->reportUseAfterReturn(
+              IssueExpr, RetEscape->getReturnExpr(), ExpiryLoc, Confidence);
+        else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF))
+          SemaHelper->reportDanglingField(
+              IssueExpr, FieldEscape->getFieldDecl(), ExpiryLoc);
+        else
+          llvm_unreachable("Unhandled OriginEscapesFact type");
+      } else
         llvm_unreachable("Unhandled CausingFact type");
     }
   }
@@ -237,8 +259,14 @@ class LifetimeChecker {
   }
 
   void reportNoescapeViolations() {
-    for (auto [PVD, EscapeExpr] : NoescapeWarningsMap)
-      SemaHelper->reportNoescapeViolation(PVD, EscapeExpr);
+    for (auto [PVD, EscapeTarget] : NoescapeWarningsMap) {
+      if (const auto *E = EscapeTarget.dyn_cast<const Expr *>())
+        SemaHelper->reportNoescapeViolation(PVD, E);
+      else if (const auto *FD = EscapeTarget.dyn_cast<const FieldDecl *>())
+        SemaHelper->reportNoescapeViolation(PVD, FD);
+      else
+        llvm_unreachable("Unhandled EscapingTarget type");
+    }
   }
 
   void inferAnnotations() {

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 c982b255d54ea..c1b8322c5ec55 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{};`
@@ -317,31 +331,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) {
@@ -464,6 +489,14 @@ 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 (auto *FD = dyn_cast_if_present<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 bf539303695b1..9141859a81345 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"
@@ -150,25 +151,33 @@ OriginList *OriginManager::getOrCreateList(const Expr *E) 
{
     return *ThisOrigins;
   }
 
-  // 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();
+  else 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 913962dc0c3e0..0c96b0afef1a7 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2891,16 +2891,24 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
         << 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 suggestLifetimeboundToParmVar(SuggestionScope Scope,
@@ -2951,6 +2959,17 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
         << EscapeExpr->getSourceRange();
   }
 
+  void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
+                               const FieldDecl *EscapeField) override {
+    S.Diag(ParmWithNoescape->getBeginLoc(),
+           diag::warn_lifetime_safety_noescape_escapes)
+        << ParmWithNoescape->getSourceRange();
+
+    S.Diag(EscapeField->getLocation(),
+           diag::note_lifetime_safety_escapes_to_field_here)
+        << EscapeField->getEndLoc();
+  }
+
   void addLifetimeBoundToImplicitThis(const CXXMethodDecl *MD) override {
     S.addLifetimeBoundToImplicitThis(const_cast<CXXMethodDecl *>(MD));
   }
@@ -2979,6 +2998,7 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU,
     AnalysisDeclContext AC(nullptr, FD);
     AC.getCFGBuildOptions().PruneTriviallyFalseEdges = false;
     AC.getCFGBuildOptions().AddLifetime = true;
+    AC.getCFGBuildOptions().AddParameterLifetimes = true;
     AC.getCFGBuildOptions().AddImplicitDtors = true;
     AC.getCFGBuildOptions().AddTemporaryDtors = true;
     AC.getCFGBuildOptions().setAllAlwaysAdd();
@@ -3087,6 +3107,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
   AC.getCFGBuildOptions().AddEHEdges = false;
   AC.getCFGBuildOptions().AddInitializers = true;
   AC.getCFGBuildOptions().AddImplicitDtors = true;
+  AC.getCFGBuildOptions().AddParameterLifetimes = 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 29554f5a98029..c82cf41b07361 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -1,5 +1,7 @@
 // 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 -Wlifetime-safety -Wno-dangling 
-verify=cfg,cfg-field %s
+
+// FIXME: cfg-field should be detected in end-of-TU analysis but it doesn't 
work for constructors!
 // 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 +82,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-field-note 3 {{this field dangles}}
+  MyLongPointerFromConversion p2; // expected-note {{pointer member declared 
here}} \
+                                  // cfg-field-note 2 {{this field dangles}}
+
+  DanglingGslPtrField(int i) : p(&i) {} // cfg-field-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-field-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-field-warning 
{{address of stack memory escapes to a field}}
+  DanglingGslPtrField(MyIntOwner io) : p(io) {} // cfg-field-warning {{address 
of stack memory escapes to a field}}
+  DanglingGslPtrField(MyLongOwnerWithConversion lo) : p2(lo) {} // 
cfg-field-warning {{address of stack memory escapes to a field}}
 };
 
 MyIntPointer danglingGslPtrFromLocal() {
@@ -1099,10 +1108,11 @@ struct Foo2 {
 };
 
 struct Test {
-  Test(Foo2 foo) : bar(foo.bar.get()), // OK
+  Test(Foo2 foo) : bar(foo.bar.get()), // OK \
+      // FIXME: cfg-field-warning {{address of stack memory escapes to a 
field}}
       storage(std::move(foo.bar)) {};
 
-  Bar* bar;
+  Bar* bar; // cfg-field-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..dfc43e49906f2
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
@@ -0,0 +1,175 @@
+// 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();
+  }
+};
+
+// FIXME: Detect escape to field of field.
+struct IndirectEscape{
+  struct {
+    const char *p;
+  } b;
+  
+  void foo() {
+    std::string s;
+    b.p = s.data();
+  }
+};
+
+// FIXME: Detect escape to field of field.
+struct IndirectEscape2 {
+  struct {
+    const char *p;
+  };
+
+  void foo() {
+    std::string s;
+    p = s.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++)

diff  --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index 91edd2e33edf8..ee661add0acc8 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -120,13 +120,12 @@ void escape_through_global_var(const MyObj& in 
[[clang::noescape]]) {
   global_view = in;
 }
 
-// FIXME: Escaping through a member variable is not detected.
 struct ObjConsumer {
-  void escape_through_member(const MyObj& in [[clang::noescape]]) {
+  void escape_through_member(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
     member_view = in;
   }
 
-  View member_view;
+  View member_view; // expected-note {{escapes to this field}}
 };
 
 // FIXME: Escaping through another param is not detected.


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

Reply via email to