[llvm-branch-commits] [clang] [LifetimeSafety] Track per-field origins for record types (PR #195603)

Zhijie Wang via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Jun 4 08:46:09 PDT 2026


https://github.com/aeft updated https://github.com/llvm/llvm-project/pull/195603

>From f9a408c6c93750d865ac73b076c791b7f1d07a28 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Wed, 3 Jun 2026 21:45:13 -0700
Subject: [PATCH] [LifetimeSafety] Track per-field origins for record types

---
 .../Analyses/LifetimeSafety/FactsGenerator.h  |   1 -
 .../Analyses/LifetimeSafety/Origins.h         |  17 +
 .../LifetimeSafety/FactsGenerator.cpp         | 105 ++++--
 clang/lib/Analysis/LifetimeSafety/Origins.cpp |  97 +++++-
 .../warn-lifetime-safety-dangling-field.cpp   |  10 +-
 clang/test/Sema/warn-lifetime-safety.cpp      | 320 +++++++++++++++++-
 6 files changed, 495 insertions(+), 55 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index e97322cf04604..b050463c8242c 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -141,7 +141,6 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   // (e.g. on the left-hand side of an assignment in the case of a DeclRefExpr).
   void handleUse(const Expr *E);
 
-  void markUseAsWrite(const DeclRefExpr *DRE);
   /// Walks the full subtree so origins on the pointee chain and on field
   /// children both escape with the returned value.
   void emitReturnEscapes(OriginNode *N, const Expr *RetExpr);
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
index 14309b549a3f3..39bd28f9433ee 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
@@ -221,9 +221,19 @@ class OriginManager {
   OriginNode *createNode(const Expr *E, QualType QT);
 
   void attachPointeeChild(OriginNode *Parent, OriginNode *Pointee);
+  void attachChildren(OriginNode *Parent,
+                      llvm::ArrayRef<OriginNode::Edge> Children);
 
   template <typename T>
   OriginNode *buildNodeForType(QualType QT, const T *Node);
+  template <typename T>
+  OriginNode *buildNodeForTypeImpl(QualType QT, const T *Node,
+                                   llvm::SmallPtrSet<const Type *, 4> &Visited,
+                                   unsigned FieldDepth);
+
+  /// Whether a record field participates in origin tracking. Plain records
+  /// only track public fields; lambdas track all fields.
+  bool isTrackedField(const CXXRecordDecl *RD, const FieldDecl *FD) const;
 
   void initializeThisOrigins(const Decl *D);
 
@@ -252,6 +262,13 @@ class OriginManager {
   /// Fields accessed in the function body (or constructor init lists).
   /// Fields outside this set are excluded from origin tracking.
   llvm::SmallPtrSet<const FieldDecl *, 8> AccessedFields;
+
+  /// Field-edge depth limit when building origin trees for record types:
+  ///   - `std::nullopt`: no limit (full field tree).
+  ///   - `0`: disable field tracking (records become single-origin).
+  ///   - `N > 0`: track up to N levels of field edges.
+  /// Pointee edges are not subject to this limit.
+  std::optional<size_t> MaxFieldDepth = std::nullopt;
 };
 } // namespace clang::lifetimes::internal
 
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index f6d556a04672f..02a24e1333ca0 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -272,12 +272,16 @@ void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
 }
 
 void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) {
-  auto *MD = ME->getMemberDecl();
-  if (isa<FieldDecl>(MD) && doesDeclHaveStorage(MD)) {
-    assert(ME->isGLValue() && "Field member should be GL value");
-    OriginNode *Dst = getOriginNode(*ME);
-    assert(Dst && "Field member should have an origin list as it is GL value");
-    OriginNode *Src = getOriginNode(*ME->getBase());
+  auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl());
+  if (!FD)
+    return;
+
+  assert(ME->isGLValue() && "Field member should be GL value");
+  OriginNode *Dst = getOriginNode(*ME);
+  assert(Dst && "Field member should have an origin list as it is GL value");
+
+  OriginNode *Src = getOriginNode(*ME->getBase());
+  if (doesDeclHaveStorage(FD)) {
     assert(Src && "Base expression should be a pointer/reference type");
     // The field's glvalue (outermost origin) holds the same loans as the base
     // expression.
@@ -285,6 +289,28 @@ void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) {
         Dst->getOriginID(), Src->getOriginID(),
         /*Kill=*/true));
   }
+
+  // Only narrow when the field is in the base's tree; otherwise the
+  // MemberExpr resolved to an orphan disconnected from the base (e.g., a
+  // `[[gsl::Pointer]]` base whose fields aren't expanded), and narrowing
+  // would drop the base's root liveness, so a loan deposited on the root
+  // via `lifetime_capture_by(this)` would be missed.
+  if (Src && Src->getFieldChildInChain(FD)) {
+    // Narrow the UseFact's liveness coverage to the accessed field's
+    // subtree.
+    //
+    // E.g., for `(void)s.inner`, without narrowing, the UseFact at `s`
+    // would keep `s.v`'s subtree live and falsely flag a UAF when a loan
+    // held by `s.v` has already expired.
+    if (UseFact *UF = UseFacts.lookup(ME->getBase())) {
+      assert(!UseFacts.contains(ME) && "ME already has a UseFact");
+      OriginNode *NewUsedOrigins =
+          doesDeclHaveStorage(FD) ? Dst->getPointeeChild() : Dst;
+      UF->setUsedOrigins(NewUsedOrigins);
+      UseFacts[ME] = UF;
+      UseFacts.erase(ME->getBase());
+    }
+  }
 }
 
 void FactsGenerator::VisitCallExpr(const CallExpr *CE) {
@@ -395,18 +421,24 @@ void FactsGenerator::handleAssignment(const Expr *TargetExpr,
                                       const Expr *RHSExpr) {
   LHSExpr = LHSExpr->IgnoreParenImpCasts();
   OriginNode *LHSNode = nullptr;
+  QualType LHSType;
+  UseFact *LHSUseFact = nullptr;
 
   if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
     LHSNode = getOriginNode(*DRE_LHS);
     assert(LHSNode && "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)) {
+    LHSType = DRE_LHS->getDecl()->getType();
+    LHSUseFact = UseFacts.lookup(DRE_LHS);
+  } else if (const auto *ME_LHS = dyn_cast<MemberExpr>(LHSExpr)) {
+    // 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.
     LHSNode = getOriginNode(*ME_LHS);
     assert(LHSNode && "LHS is a MemberExpr and should have an origin list");
+    LHSType = ME_LHS->getMemberDecl()->getType();
+    LHSUseFact = UseFacts.lookup(ME_LHS);
   }
+
   if (!LHSNode)
     return;
   OriginNode *RHSNode = getOriginNode(*RHSExpr);
@@ -417,28 +449,26 @@ void FactsGenerator::handleAssignment(const Expr *TargetExpr,
   // assigned.
   RHSNode = getRValueOrigins(RHSExpr, RHSNode);
 
-  if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
-    QualType QT = DRE_LHS->getDecl()->getType();
-    if (QT->isReferenceType()) {
-      if (hasOrigins(QT->getPointeeType())) {
+  if (LHSUseFact) {
+    if (LHSType->isReferenceType()) {
+      if (hasOrigins(LHSType->getPointeeType())) {
         // Writing through a reference uses the binding but overwrites the
         // pointee. Model this as a Read of the outer origin (keeping the
         // binding live) and a Write of the inner origins (killing the pointee's
         // liveness).
-        if (UseFact *UF = UseFacts.lookup(DRE_LHS)) {
-          const OriginNode *FullNode = UF->getUsedOrigins();
-          assert(FullNode);
-          UF->setUsedOrigins(FactMgr.getOriginMgr().createSingleOriginNode(
-              FullNode->getOriginID()));
-          if (const OriginNode *InnerNode = FullNode->getPointeeChild()) {
-            UseFact *WriteUF = FactMgr.createFact<UseFact>(DRE_LHS, InnerNode);
-            WriteUF->markAsWritten();
-            CurrentBlockFacts.push_back(WriteUF);
-          }
+        const OriginNode *FullNode = LHSUseFact->getUsedOrigins();
+        assert(FullNode);
+        LHSUseFact->setUsedOrigins(
+            FactMgr.getOriginMgr().createSingleOriginNode(
+                FullNode->getOriginID()));
+        if (const OriginNode *InnerNode = FullNode->getPointeeChild()) {
+          UseFact *WriteUF = FactMgr.createFact<UseFact>(LHSExpr, InnerNode);
+          WriteUF->markAsWritten();
+          CurrentBlockFacts.push_back(WriteUF);
         }
       }
     } else
-      markUseAsWrite(DRE_LHS);
+      LHSUseFact->markAsWritten();
   }
   if (!RHSNode) {
     // RHS has no tracked origins (e.g., assigning a callable without origins
@@ -581,9 +611,14 @@ void FactsGenerator::VisitCXXFunctionalCastExpr(
 void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) {
   if (!hasOrigins(ILE))
     return;
-  // For list initialization with a single element, like `View{...}`, the
-  // origin of the list itself is the origin of its single element.
-  if (ILE->getNumInits() == 1)
+  // For list initialization with a single element of the same type, like
+  // `View{other}`, the origin of the list itself is the origin of its single
+  // element.
+  //
+  // TODO: Handle aggregate (record/array) list initialization.
+  if (ILE->getNumInits() == 1 &&
+      ILE->getType().getCanonicalType() ==
+          ILE->getInit(0)->getType().getCanonicalType())
     killAndFlowOrigin(*ILE, *ILE->getInit(0));
 }
 
@@ -882,9 +917,10 @@ void FactsGenerator::handleImplicitObjectFieldUses(const Expr *Call,
 
   const auto UseFields = [&](const CXXRecordDecl *RD) {
     for (const auto *Field : RD->fields())
-      if (auto *FieldNode = getOriginNode(*Field))
-        CurrentBlockFacts.push_back(
-            FactMgr.createFact<UseFact>(Call, FieldNode));
+      if (FactMgr.getOriginMgr().isAccessedField(Field))
+        if (auto *FieldNode = getOriginNode(*Field))
+          CurrentBlockFacts.push_back(
+              FactMgr.createFact<UseFact>(Call, FieldNode));
   };
 
   UseFields(ClassDecl);
@@ -1106,11 +1142,6 @@ void FactsGenerator::handleUse(const Expr *E) {
   }
 }
 
-void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) {
-  if (UseFacts.contains(DRE))
-    UseFacts[DRE]->markAsWritten();
-}
-
 // Creates an IssueFact for a new placeholder loan for each pointer or reference
 // parameter at the function's entry.
 llvm::SmallVector<Fact *> FactsGenerator::issuePlaceholderLoans() {
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 510232f5dc983..afab984390158 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -122,16 +122,29 @@ bool OriginManager::hasOrigins(QualType QT) const {
   // stored lambda's origins.
   if (isStdCallableWrapperType(RD))
     return true;
-  // TODO: Limit to lambdas for now. This will be extended to user-defined
-  // structs with pointer-like fields.
-  if (!RD->isLambda())
+  // A lambda has origins when any capture has a tracked type; the lambda
+  // itself is tracked as a single origin.
+  if (RD->isLambda()) {
+    for (const auto *FD : RD->fields())
+      if (hasOrigins(FD->getType()))
+        return true;
+    return false;
+  }
+  // TODO: Unions are not tracked.
+  if (RD->isUnion())
     return false;
   for (const auto *FD : RD->fields())
-    if (hasOrigins(FD->getType()))
+    if (FD->getAccess() == AS_public && hasOrigins(FD->getType()))
       return true;
   return false;
 }
 
+bool OriginManager::isTrackedField(const CXXRecordDecl *RD,
+                                   const FieldDecl *FD) const {
+  return FD->getAccess() == AS_public && hasOrigins(FD->getType()) &&
+         isAccessedField(FD);
+}
+
 /// Determines if an expression has origins that need to be tracked.
 ///
 /// An expression has origins if:
@@ -208,9 +221,37 @@ void OriginManager::attachPointeeChild(OriginNode *Parent,
   Parent->setChildren({E, 1});
 }
 
+void OriginManager::attachChildren(OriginNode *Parent,
+                                   llvm::ArrayRef<OriginNode::Edge> Children) {
+  Parent->setChildren(Children.copy(Allocator));
+}
+
 template <typename T>
 OriginNode *OriginManager::buildNodeForType(QualType QT, const T *Node) {
-  assert(hasOrigins(QT) && "buildNodeForType called for non-pointer type");
+  llvm::SmallPtrSet<const Type *, 4> Visited;
+  return buildNodeForTypeImpl(QT, Node, Visited, 0);
+}
+
+template <typename T>
+OriginNode *
+OriginManager::buildNodeForTypeImpl(QualType QT, const T *Node,
+                                    llvm::SmallPtrSet<const Type *, 4> &Visited,
+                                    unsigned FieldDepth) {
+  assert(hasOrigins(QT) && "buildNodeForType called for type without origins");
+
+  const auto *RD = QT->getAsCXXRecordDecl();
+  const Type *Canonical = QT.getCanonicalType().getTypePtr();
+  // Cycle cut: only records enter Visited; re-entering one returns a
+  // leaf to stop descending further. Loans landing on the cut leaf are
+  // dropped (e.g., through `n->next->next`).
+  //
+  // Pointer/reference types stay transparent: including them in Visited
+  // would make the same record's shape depend on the entry path. E.g.,
+  // Node's Sub_next would have length 2 from a Node start but length 1
+  // from a Node* start, breaking flow's length assertion.
+  if (RD && !Visited.insert(Canonical).second)
+    return createNode(Node, QT);
+
   OriginNode *Head = createNode(Node, QT);
 
   if (QT->isPointerOrReferenceType()) {
@@ -218,8 +259,28 @@ OriginNode *OriginManager::buildNodeForType(QualType QT, const T *Node) {
     // We recurse if the pointee type is pointer-like, to build the next
     // level in the origin tree. E.g., for T*& / View&.
     if (hasOrigins(PointeeTy))
-      attachPointeeChild(Head, buildNodeForType(PointeeTy, Node));
+      attachPointeeChild(
+          Head, buildNodeForTypeImpl(PointeeTy, Node, Visited, FieldDepth));
+  } else if (RD) {
+    bool WithinFieldDepthLimit = !MaxFieldDepth || FieldDepth < *MaxFieldDepth;
+    bool shouldExpandFields =
+        !(isGslPointerType(QT) || isStdCallableWrapperType(RD) ||
+          LifetimeAnnotatedOriginTypes.contains(Canonical) || RD->isLambda()) &&
+        WithinFieldDepthLimit;
+    if (shouldExpandFields) {
+      SmallVector<OriginNode::Edge, 4> FieldChildren;
+      for (const FieldDecl *F : RD->fields())
+        if (isTrackedField(RD, F)) {
+          OriginNode *Sub =
+              buildNodeForTypeImpl(F->getType(), Node, Visited, FieldDepth + 1);
+          FieldChildren.push_back({F, Sub});
+        }
+      attachChildren(Head, FieldChildren);
+    }
   }
+
+  if (RD)
+    Visited.erase(Canonical);
   return Head;
 }
 
@@ -284,6 +345,30 @@ OriginNode *OriginManager::getOrCreateNode(const Expr *E) {
     return ExprToNode[E] = Head;
   }
 
+  // For a MemberExpr whose base is not `this` (handled above), look up the
+  // field child in the base's per-instance origin tree. This makes loans
+  // flowing into one occurrence of `s.v` visible at later occurrences.
+  if (auto *ME = dyn_cast<MemberExpr>(E))
+    if (auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl()))
+      if (OriginNode *Base =
+              getOrCreateNode(ME->getBase()->IgnoreParenImpCasts()))
+        if (OriginNode *Sub = Base->getFieldChildInChain(FD)) {
+          // For non-reference fields (e.g., `View v;` in a record), the
+          // MemberExpr `s.v` is an lvalue (addressable) that can be
+          // borrowed, so we create an outer origin for the lvalue itself,
+          // with the pointee being the field's shared subtree. `&s.v` borrows
+          // the storage of the v-slot in s, not what v refers to.
+          if (doesDeclHaveStorage(FD)) {
+            OriginNode *Outer = createNode(E, QualType{});
+            attachPointeeChild(Outer, Sub);
+            return ExprToNode[E] = Outer;
+          }
+          // For reference-typed fields (e.g., `int& r;` in a record) which
+          // have no storage, the MemberExpr `s.r` directly reuses the
+          // field's subtree.
+          return ExprToNode[E] = Sub;
+        }
+
   // If E is an lvalue , it refers to storage. We model this storage as the
   // first level of origin list, as if it were a reference, because l-values are
   // addressable.
diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
index cb13f51b41355..af6d933bbed97 100644
--- a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
@@ -162,27 +162,25 @@ struct MemberSetters {
   }
 };
 
-// FIXME: Detect escape to field of field.
 struct IndirectEscape{
   struct {
     const char *p;
-  } b;
+  } b; // expected-note {{this field dangles}}
   
   void foo() {
     std::string s;
-    b.p = s.data();
+    b.p = s.data(); // expected-warning {{stack memory associated with local variable 's' escapes to the field 'b' which will dangle}}
   }
 };
 
-// FIXME: Detect escape to field of field.
 struct IndirectEscape2 {
-  struct {
+  struct { // expected-note {{this field dangles}}
     const char *p;
   };
 
   void foo() {
     std::string s;
-    p = s.data();
+    p = s.data(); // expected-warning {{stack memory associated with local variable 's' escapes to the field}}
   }
 };
 
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 5aaa389b63ccd..6378027e410b4 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -3190,14 +3190,13 @@ struct T {
   }
 };
 
-// FIXME: false-negative
 void foo() {
   S s;
   {
     int num;
-    s.p_ = # // does not warn
-  }
-  s.bar();
+    s.p_ = # // expected-warning {{local variable 'num' does not live long enough}}
+  }              // expected-note {{destroyed here}}
+  s.bar();       // expected-note {{later used here}}
   s.p_ = &GLOBAL_INT;
 }
 
@@ -3594,4 +3593,315 @@ void capturing_multiple_locals() {
         setCaptureBy(v, local2);    // expected-warning{{local variable 'local2' does not live long enough}}
     }                               // expected-note 2 {{destroyed here}} 
     (void)v;                        // expected-note 2 {{later used here}}
-}
\ No newline at end of file
+}
+
+namespace tree_origin {
+
+struct Inner {
+  View v;
+  View w;
+};
+
+struct S {
+  View v;
+  Inner inner;
+  int x;
+};
+
+void struct_field() {
+  S s;
+  {
+    MyObj obj;
+    s.v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+  }            // expected-note {{destroyed here}}
+  s.v.use();   // expected-note {{later used here}}
+}
+
+void struct_field2() {
+  S s;
+  {
+    MyObj obj;
+    s.inner.v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+  }                  // expected-note {{destroyed here}}
+  (void)s.inner;     // expected-note {{later used here}}
+}
+
+void struct_field3() {
+  S s;
+  {
+    MyObj obj;
+    s.inner.v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+  }                  // expected-note {{destroyed here}}
+  s.inner.v.use();   // expected-note {{later used here}}
+}
+
+void struct_field_safe() {
+  S s;
+  {
+    MyObj obj;
+    s.v = obj;
+  }
+  (void)s.inner;
+}
+
+void struct_field_safe2() {
+  S s;
+  {
+    MyObj obj;
+    s.inner.v = obj;
+  }
+  (void)s.inner.w;
+}
+
+S return_struct() {
+  S s;
+  MyObj local;
+  s.v = local; // expected-warning {{stack memory associated with local variable 'local' is returned}}
+  return s;    // expected-note {{returned here}}
+}
+
+S return_struct_assign() {
+  S s, s2;
+  MyObj local;
+  s.v = local; // expected-warning {{stack memory associated with local variable 'local' is returned}}
+  s2 = s;
+  return s2; // expected-note {{returned here}}
+}
+
+View *return_field_addr() {
+  S s;
+  View* v = &s.v; // expected-warning {{stack memory associated with local variable 's' is returned}}
+  return v; // expected-note {{returned here}}
+}
+
+void struct_field_in_loop(int n) {
+  S s;
+  for (int i = 0; i < n; i++) {
+    MyObj obj;
+    s.v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+  }            // expected-note {{destroyed here}}
+  (void)s;     // expected-note {{later used here}}
+}
+
+void struct_field_in_loop_safe(int n) {
+  for (int i = 0; i < n; i++) {
+    MyObj obj;
+    S s;
+    s.v = obj;
+  }
+}
+
+void struct_field_in_loop_safe2(int n) {
+  S s;
+  for (int i = 0; i < n; i++) {
+    MyObj obj;
+    s.v = obj;
+  }
+}
+
+void struct_field_read_in_loop_safe(int n) {
+  for (int i = 0; i < n; i++) {
+    S s;
+    (void)s.v;
+  }
+}
+
+void struct_int_field_read_in_loop_safe(int n) {
+  for (int i = 0; i < n; i++) {
+    S s;
+    (void)s.x;
+  }
+}
+
+void field_pointee_propagate_in_loop_safe(int n) {
+  View outer;
+  MyObj obj;
+  for (int i = 0; i < n; i++) {
+    S local;
+    local.v = obj;
+    outer = local.v;
+  }
+  outer.use();
+}
+
+struct PtrRef {
+  MyObj *&ref;
+};
+
+void ref_field_dangle() {
+  MyObj *p;
+  PtrRef h{p};
+  {
+    MyObj temp;
+    h.ref = &temp; // expected-warning {{local variable 'temp' does not live long enough}}
+  }                // expected-note {{destroyed here}}
+  (void)*h.ref;    // expected-note {{later used here}}
+}
+
+void ref_field_dangle_then_rescue() {
+  MyObj safe;
+  MyObj *p;
+  PtrRef h{p};
+  {
+    MyObj temp;
+    h.ref = &temp;
+  }
+  h.ref = &safe;
+  (void)*h.ref;
+}
+
+struct Node {
+  Node *next;
+  View v;
+};
+
+void self_referential_struct_field() {
+  Node n;
+  {
+    MyObj obj;
+    n.v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+  }            // expected-note {{destroyed here}}
+  n.v.use();   // expected-note {{later used here}}
+}
+
+void self_referential_pointer_assign() {
+  Node n;
+  {
+    Node child;
+    n.next = &child; // expected-warning {{local variable 'child' does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)n; // expected-note {{later used here}}
+}
+
+struct SelfRefMultiPtr {
+  SelfRefMultiPtr *p1;
+  SelfRefMultiPtr **p2;
+};
+
+void self_referential_mixed_pointer_depth() {
+  SelfRefMultiPtr s;
+  {
+    SelfRefMultiPtr *p;
+    s.p2 = &p; // expected-warning {{local variable 'p' does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)s; // expected-note {{later used here}}
+}
+
+// FIXME: False negative. The cycle-cut leaf at `n.next`'s pointee has
+// no field edges, so the loan on `n.next->v` lands on an orphan tree
+// disconnected from n.
+void self_referential_pointer_field_write() {
+  Node n, child;
+  n.next = &child;
+  {
+    MyObj local;
+    n.next->v = local;
+  }
+  (void)n; // Should warn.
+}
+
+struct DerivedView : S {
+  View v2;
+};
+
+void derived_to_base_upcast() {
+  DerivedView d;
+  // CK_DerivedToBase: Src and Dst children sets diverge in flow (Base and
+  // Derived have different direct fields), so flow must skip unmatched
+  // fields without crashing.
+  S& b = d;
+  (void)b;
+}
+
+// FIXME: False positive.
+void derived_field_safe() {
+  DerivedView d;
+  {
+    MyObj obj;
+    d.v2 = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+  }             // expected-note {{destroyed here}}
+  (void)d.v;    // expected-note {{later used here}}
+}
+
+class PrivateHolder {
+private:
+  View v;
+  friend PrivateHolder return_private_holder();
+};
+
+// FIXME: False negative. Don't track origins for private fields for now.
+PrivateHolder return_private_holder() {
+  PrivateHolder h;
+  MyObj local;
+  h.v = local;
+  return h; // Should warn.
+}
+
+struct UnionStruct {
+  union {
+    int* p;
+    float f;
+  };
+};
+
+// FIXME: False negative. Don't track origins for union for now.
+UnionStruct union_field() {
+  UnionStruct u;
+  int x;
+  u.p = &x;
+  return u; // Should warn.
+}
+
+struct StoreS {
+  View v;
+  void store(const MyObj& obj) { v = obj; }
+};
+
+// FIXME: False negative. Two pieces missing:
+// 1. Infer `lifetime_capture_by(this)` on non-ctor methods that
+//    capture a parameter into a field.
+// 2. Consume the attribute in `handleFunctionCall` and flow the
+//    captured argument's origin into the corresponding target at
+//    the call site.
+StoreS return_after_this_set() {
+  StoreS s;
+  MyObj local;
+  s.store(local);
+  return s; // Should warn.
+}
+
+struct BaseS { View v; };
+struct DerivedS : BaseS {};
+
+// FIXME: False negative. `d.v` accesses an inherited field. Only track
+// direct fields for now, so DerivedS's tree has no edge for `v` and the
+// loan does not propagate.
+DerivedS inherited_field() {
+  DerivedS d;
+  MyObj local;
+  d.v = local;
+  return d; // Should warn.
+}
+
+// FIXME: False negative. `p->v1 = local` deposits into p's origin tree,
+// which is independent of s's tree after the initial `p = &s` flow.
+// Requires alias analysis.
+S field_write_via_pointer() {
+  S s;
+  MyObj local;
+  S *p = &s;
+  p->v = local;
+  return s; // Should warn.
+}
+
+struct ViewPtrHolder { View *p; };
+
+// FIXME: False negative. Aggregate (record/array) list initialization is
+// not handled, so the loan from `&v` does not flow into `h.p`.
+View *struct_init_single_field() {
+  View v;
+  ViewPtrHolder h{&v};
+  return h.p; // Should warn.
+}
+
+} // namespace tree_origin



More information about the llvm-branch-commits mailing list