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

>From caac182506230e982e99474cb9a01a722021a4f1 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <[email protected]>
Date: Sun, 14 Sep 2025 14:46:45 +0000
Subject: [PATCH] lifetime-analysis-lifetimebound

---
 .../Analysis/Analyses/LifetimeAnnotations.h   |  35 +++
 .../clang/Analysis/Analyses/LifetimeSafety.h  |  11 +-
 clang/lib/Analysis/CMakeLists.txt             |   1 +
 clang/lib/Analysis/LifetimeAnnotations.cpp    |  73 +++++
 clang/lib/Analysis/LifetimeSafety.cpp         | 173 ++++++++----
 clang/lib/Sema/CheckExprLifetime.cpp          |  80 +-----
 clang/lib/Sema/CheckExprLifetime.h            |   2 -
 clang/lib/Sema/SemaAPINotes.cpp               |   4 +-
 .../test/Analysis/LifetimeSafety/benchmark.py |   2 +-
 .../Sema/warn-lifetime-safety-dataflow.cpp    | 155 +++++------
 clang/test/Sema/warn-lifetime-safety.cpp      | 149 ++++++++++
 .../unittests/Analysis/LifetimeSafetyTest.cpp | 257 +++++++++++++++++-
 12 files changed, 736 insertions(+), 206 deletions(-)
 create mode 100644 clang/include/clang/Analysis/Analyses/LifetimeAnnotations.h
 create mode 100644 clang/lib/Analysis/LifetimeAnnotations.cpp

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeAnnotations.h 
b/clang/include/clang/Analysis/Analyses/LifetimeAnnotations.h
new file mode 100644
index 0000000000000..dec61ad8b5e56
--- /dev/null
+++ b/clang/include/clang/Analysis/Analyses/LifetimeAnnotations.h
@@ -0,0 +1,35 @@
+//===- LifetimeAnnotations.h -  -*--------------- 
C++--------------------*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// Helper functions to inspect and infer lifetime annotations.
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H
+#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H
+
+#include "clang/AST/DeclCXX.h"
+
+namespace clang {
+namespace lifetimes {
+
+const FunctionDecl *getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl 
*FD);
+
+const CXXMethodDecl *
+getDeclWithMergedLifetimeBoundAttrs(const CXXMethodDecl *CMD);
+
+// Return true if this is an "normal" assignment operator.
+// We assume that a normal assignment operator always returns *this, that is,
+// an lvalue reference that is the same type as the implicit object parameter
+// (or the LHS for a non-member operator$=).
+bool isNormalAssignmentOperator(const FunctionDecl *FD);
+
+bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD);
+
+bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
+} // namespace lifetimes
+} // namespace clang
+
+#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H
\ No newline at end of file
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
index 7e1bfc903083e..512cb76cd6349 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
@@ -75,13 +75,14 @@ template <typename Tag> struct ID {
   }
 };
 
-template <typename Tag>
-inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
-  return OS << ID.Value;
-}
-
 using LoanID = ID<struct LoanTag>;
 using OriginID = ID<struct OriginTag>;
+inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) {
+  return OS << ID.Value;
+}
+inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) {
+  return OS << ID.Value;
+}
 
 // Using LLVM's immutable collections is efficient for dataflow analysis
 // as it avoids deep copies during state transitions.
diff --git a/clang/lib/Analysis/CMakeLists.txt 
b/clang/lib/Analysis/CMakeLists.txt
index 0523d92480cb3..5a26f3eeea418 100644
--- a/clang/lib/Analysis/CMakeLists.txt
+++ b/clang/lib/Analysis/CMakeLists.txt
@@ -21,6 +21,7 @@ add_clang_library(clangAnalysis
   FixitUtil.cpp
   IntervalPartition.cpp
   IssueHash.cpp
+  LifetimeAnnotations.cpp
   LifetimeSafety.cpp
   LiveVariables.cpp
   MacroExpansionContext.cpp
diff --git a/clang/lib/Analysis/LifetimeAnnotations.cpp 
b/clang/lib/Analysis/LifetimeAnnotations.cpp
new file mode 100644
index 0000000000000..41d5684b2114d
--- /dev/null
+++ b/clang/lib/Analysis/LifetimeAnnotations.cpp
@@ -0,0 +1,73 @@
+#include "clang/Analysis/Analyses/LifetimeAnnotations.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Attr.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/Type.h"
+#include "clang/AST/TypeLoc.h"
+
+namespace clang {
+namespace lifetimes {
+
+const FunctionDecl *
+getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) {
+  return FD != nullptr ? FD->getMostRecentDecl() : nullptr;
+}
+
+const CXXMethodDecl *
+getDeclWithMergedLifetimeBoundAttrs(const CXXMethodDecl *CMD) {
+  const FunctionDecl *FD = CMD;
+  return cast_if_present<CXXMethodDecl>(
+      getDeclWithMergedLifetimeBoundAttrs(FD));
+}
+
+// Return true if this is an "normal" assignment operator.
+// We assume that a normal assignment operator always returns *this, that is,
+// an lvalue reference that is the same type as the implicit object parameter
+// (or the LHS for a non-member operator$=).
+bool isNormalAssignmentOperator(const FunctionDecl *FD) {
+  OverloadedOperatorKind OO = FD->getDeclName().getCXXOverloadedOperator();
+  if (OO == OO_Equal || isCompoundAssignmentOperator(OO)) {
+    QualType RetT = FD->getReturnType();
+    if (RetT->isLValueReferenceType()) {
+      ASTContext &Ctx = FD->getASTContext();
+      QualType LHST;
+      auto *MD = dyn_cast<CXXMethodDecl>(FD);
+      if (MD && MD->isCXXInstanceMember())
+        LHST = 
Ctx.getLValueReferenceType(MD->getFunctionObjectParameterType());
+      else
+        LHST = FD->getParamDecl(0)->getType();
+      if (Ctx.hasSameType(RetT, LHST))
+        return true;
+    }
+  }
+  return false;
+}
+
+bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD) {
+  CMD = lifetimes::getDeclWithMergedLifetimeBoundAttrs(CMD);
+  return CMD && isNormalAssignmentOperator(CMD) && CMD->param_size() == 1 &&
+         CMD->getParamDecl(0)->hasAttr<clang::LifetimeBoundAttr>();
+}
+
+bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
+  FD = getDeclWithMergedLifetimeBoundAttrs(FD);
+  const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+  if (!TSI)
+    return false;
+  // Don't declare this variable in the second operand of the for-statement;
+  // GCC miscompiles that by ending its lifetime before evaluating the
+  // third operand. See gcc.gnu.org/PR86769.
+  AttributedTypeLoc ATL;
+  for (TypeLoc TL = TSI->getTypeLoc();
+       (ATL = TL.getAsAdjusted<AttributedTypeLoc>());
+       TL = ATL.getModifiedLoc()) {
+    if (ATL.getAttrAs<clang::LifetimeBoundAttr>())
+      return true;
+  }
+
+  return isNormalAssignmentOperator(FD);
+}
+
+} // namespace lifetimes
+} // namespace clang
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp 
b/clang/lib/Analysis/LifetimeSafety.cpp
index c22268a590791..4068df31f1147 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -10,6 +10,7 @@
 #include "clang/AST/Expr.h"
 #include "clang/AST/StmtVisitor.h"
 #include "clang/AST/Type.h"
+#include "clang/Analysis/Analyses/LifetimeAnnotations.h"
 #include "clang/Analysis/Analyses/PostOrderCFGView.h"
 #include "clang/Analysis/AnalysisDeclContext.h"
 #include "clang/Analysis/CFG.h"
@@ -212,8 +213,10 @@ class Fact {
     /// A loan expires as its underlying storage is freed (e.g., variable goes
     /// out of scope).
     Expire,
+    /// The loan set of an origin is cleared.
+    KillOrigin,
     /// An origin is propagated from a source to a destination (e.g., p = q).
-    AssignOrigin,
+    OriginFlow,
     /// An origin escapes the function by flowing into the return value.
     ReturnOfOrigin,
     /// An origin is used (eg. dereferencing a pointer).
@@ -285,22 +288,24 @@ class ExpireFact : public Fact {
   }
 };
 
-class AssignOriginFact : public Fact {
+class OriginFlowFact : public Fact {
   OriginID OIDDest;
   OriginID OIDSrc;
 
 public:
   static bool classof(const Fact *F) {
-    return F->getKind() == Kind::AssignOrigin;
+    return F->getKind() == Kind::OriginFlow;
   }
 
-  AssignOriginFact(OriginID OIDDest, OriginID OIDSrc)
-      : Fact(Kind::AssignOrigin), OIDDest(OIDDest), OIDSrc(OIDSrc) {}
+  OriginFlowFact(OriginID OIDDest, OriginID OIDSrc)
+      : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc) {}
+
   OriginID getDestOriginID() const { return OIDDest; }
   OriginID getSrcOriginID() const { return OIDSrc; }
+
   void dump(llvm::raw_ostream &OS, const LoanManager &,
             const OriginManager &OM) const override {
-    OS << "AssignOrigin (Dest: ";
+    OS << "OriginFlow (Dest: ";
     OM.dump(getDestOriginID(), OS);
     OS << ", Src: ";
     OM.dump(getSrcOriginID(), OS);
@@ -353,6 +358,23 @@ class UseFact : public Fact {
   }
 };
 
+class KillOriginFact : public Fact {
+  OriginID OID;
+
+public:
+  static bool classof(const Fact *F) {
+    return F->getKind() == Kind::KillOrigin;
+  }
+  KillOriginFact(OriginID OID) : Fact(Kind::KillOrigin), OID(OID) {}
+  OriginID getOriginID() const { return OID; }
+
+  void dump(llvm::raw_ostream &OS, const LoanManager &,
+            const OriginManager &OM) const override {
+    OS << "KillOrigin (";
+    OM.dump(getOriginID(), OS);
+    OS << ")\n";
+  }
+};
 /// A dummy-fact used to mark a specific point in the code for testing.
 /// It is generated by recognizing a `void("__lifetime_test_point_...")` cast.
 class TestPointFact : public Fact {
@@ -453,8 +475,10 @@ class FactGenerator : public 
ConstStmtVisitor<FactGenerator> {
     for (const Decl *D : DS->decls())
       if (const auto *VD = dyn_cast<VarDecl>(D))
         if (hasOrigin(VD))
-          if (const Expr *InitExpr = VD->getInit())
-            addAssignOriginFact(*VD, *InitExpr);
+          if (const Expr *InitExpr = VD->getInit()) {
+            killOrigin(VD);
+            addOriginFlowFact(*VD, *InitExpr);
+          }
   }
 
   void VisitDeclRefExpr(const DeclRefExpr *DRE) {
@@ -492,9 +516,23 @@ class FactGenerator : public 
ConstStmtVisitor<FactGenerator> {
         isa<CXXConversionDecl>(MCE->getCalleeDecl())) {
       // The argument is the implicit object itself.
       handleFunctionCall(MCE, MCE->getMethodDecl(),
-                         {MCE->getImplicitObjectArgument()});
+                         {MCE->getImplicitObjectArgument()},
+                         /*IsGslConstruction=*/true);
+    }
+    if (const CXXMethodDecl *Method = MCE->getMethodDecl()) {
+      // Construct the argument list, with the implicit 'this' object as the
+      // first argument.
+      llvm::SmallVector<const Expr *, 4> Args;
+      Args.push_back(MCE->getImplicitObjectArgument());
+      Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs());
+
+      handleFunctionCall(MCE, Method, Args, /*IsGslConstruction=*/false);
     }
-    // FIXME: A more general VisitCallExpr could also be used here.
+  }
+
+  void VisitCallExpr(const CallExpr *CE) {
+    handleFunctionCall(CE, CE->getDirectCallee(),
+                       {CE->getArgs(), CE->getNumArgs()});
   }
 
   void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N) {
@@ -508,7 +546,7 @@ class FactGenerator : public 
ConstStmtVisitor<FactGenerator> {
       return;
     // An ImplicitCastExpr node itself gets an origin, which flows from the
     // origin of its sub-expression (after stripping its own parens/casts).
-    addAssignOriginFact(*ICE, *ICE->getSubExpr());
+    addOriginFlowFact(*ICE, *ICE->getSubExpr());
   }
 
   void VisitUnaryOperator(const UnaryOperator *UO) {
@@ -522,7 +560,7 @@ class FactGenerator : public 
ConstStmtVisitor<FactGenerator> {
       // its sub-expression (x). This fact will cause the dataflow analysis
       // to propagate any loans held by the sub-expression's origin to the
       // origin of this UnaryOperator expression.
-      addAssignOriginFact(*UO, *SubExpr);
+      addOriginFlowFact(*UO, *SubExpr);
     }
   }
 
@@ -542,8 +580,15 @@ class FactGenerator : public 
ConstStmtVisitor<FactGenerator> {
   }
 
   void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
-    if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2)
+    // Assignment operators have special "kill-then-propagate" semantics
+    // and are handled separately.
+    if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) {
       handleAssignment(OCE->getArg(0), OCE->getArg(1));
+      return;
+    }
+    handleFunctionCall(OCE, OCE->getDirectCallee(),
+                       {OCE->getArgs(), OCE->getNumArgs()},
+                       /*IsGslConstruction=*/false);
   }
 
   void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE) {
@@ -552,7 +597,7 @@ class FactGenerator : public 
ConstStmtVisitor<FactGenerator> {
     if (handleTestPoint(FCE))
       return;
     if (isGslPointerType(FCE->getType()))
-      addAssignOriginFact(*FCE, *FCE->getSubExpr());
+      addOriginFlowFact(*FCE, *FCE->getSubExpr());
   }
 
   void VisitInitListExpr(const InitListExpr *ILE) {
@@ -561,7 +606,7 @@ class FactGenerator : public 
ConstStmtVisitor<FactGenerator> {
     // 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)
-      addAssignOriginFact(*ILE, *ILE->getInit(0));
+      addOriginFlowFact(*ILE, *ILE->getInit(0));
   }
 
   void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE) {
@@ -569,7 +614,7 @@ class FactGenerator : public 
ConstStmtVisitor<FactGenerator> {
       return;
     // A temporary object's origin is the same as the origin of the
     // expression that initializes it.
-    addAssignOriginFact(*MTE, *MTE->getSubExpr());
+    addOriginFlowFact(*MTE, *MTE->getSubExpr());
   }
 
   void handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
@@ -624,34 +669,41 @@ class FactGenerator : public 
ConstStmtVisitor<FactGenerator> {
     if (CCE->getNumArgs() != 1)
       return;
     if (hasOrigin(CCE->getArg(0)))
-      addAssignOriginFact(*CCE, *CCE->getArg(0));
+      addOriginFlowFact(*CCE, *CCE->getArg(0));
     else
       // This could be a new borrow.
       handleFunctionCall(CCE, CCE->getConstructor(),
-                         {CCE->getArgs(), CCE->getNumArgs()});
+                         {CCE->getArgs(), CCE->getNumArgs()},
+                         /*IsGslConstruction=*/true);
   }
 
   /// Checks if a call-like expression creates a borrow by passing a value to a
   /// reference parameter, creating an IssueFact if it does.
   void handleFunctionCall(const Expr *Call, const FunctionDecl *FD,
-                          ArrayRef<const Expr *> Args) {
-    if (!FD)
+                          ArrayRef<const Expr *> Args,
+                          bool IsGslConstruction = false) {
+    // Ignore functions returning values with no origin.
+    if (!FD || !hasOrigin(Call))
       return;
-    // TODO: Handle more than one arguments.
-    for (unsigned I = 0; I <= 0 /*Args.size()*/; ++I) {
-      const Expr *ArgExpr = Args[I];
-
-      // Propagate origins for CXX this.
-      if (FD->isCXXClassMember() && I == 0) {
-        addAssignOriginFact(*Call, *ArgExpr);
-        continue;
-      }
-      // The parameter is a pointer, reference, or gsl::Pointer.
-      // This is a borrow. We propagate the origin from the argument expression
-      // at the call site to the parameter declaration in the callee.
-      if (hasOrigin(ArgExpr))
-        addAssignOriginFact(*Call, *ArgExpr);
-    }
+    auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
+      const ParmVarDecl *PVD = nullptr;
+      if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
+          Method && Method->isInstance()) {
+        if (I == 0)
+          // For the 'this' argument, the attribute is on the method itself.
+          return implicitObjectParamIsLifetimeBound(Method);
+        if ((I - 1) < Method->getNumParams())
+          // For explicit arguments, find the corresponding parameter
+          // declaration.
+          PVD = Method->getParamDecl(I - 1);
+      } else if (I < FD->getNumParams())
+        // For free functions or static methods.
+        PVD = FD->getParamDecl(I);
+      return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false;
+    };
+    for (unsigned I = 0; I < Args.size(); ++I)
+      if (IsGslConstruction || IsArgLifetimeBound(I))
+        addOriginFlowFact(*Call, *Args[I]);
   }
 
   /// Creates a loan for the storage path of a given declaration reference.
@@ -668,11 +720,16 @@ class FactGenerator : public 
ConstStmtVisitor<FactGenerator> {
   }
 
   template <typename Destination, typename Source>
-  void addAssignOriginFact(const Destination &D, const Source &S) {
+  void addOriginFlowFact(const Destination &D, const Source &S) {
     OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
     OriginID SrcOID = FactMgr.getOriginMgr().get(S);
     CurrentBlockFacts.push_back(
-        FactMgr.createFact<AssignOriginFact>(DestOID, SrcOID));
+        FactMgr.createFact<OriginFlowFact>(DestOID, SrcOID));
+  }
+
+  void killOrigin(const ValueDecl *D) {
+    OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(*D);
+    CurrentBlockFacts.push_back(FactMgr.createFact<KillOriginFact>(DestOID));
   }
 
   /// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
@@ -703,12 +760,12 @@ class FactGenerator : public 
ConstStmtVisitor<FactGenerator> {
     if (const auto *DRE_LHS =
             dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
       markUseAsWrite(DRE_LHS);
-      if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl()))
-        // We are interested in assignments like `ptr1 = ptr2` or `ptr = &var`.
-        // LHS must be a pointer/reference type that can be an origin. RHS must
-        // also represent an origin (either another pointer/ref or an
-        // address-of).
-        addAssignOriginFact(*VD_LHS, *RHSExpr);
+      if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl())) {
+        // Kill the old loans of the destination origin and flow the new loans
+        // from the source origin.
+        killOrigin(VD_LHS);
+        addOriginFlowFact(*VD_LHS, *RHSExpr);
+      }
     }
   }
 
@@ -882,8 +939,10 @@ class DataflowAnalysis {
       return D->transfer(In, *F->getAs<IssueFact>());
     case Fact::Kind::Expire:
       return D->transfer(In, *F->getAs<ExpireFact>());
-    case Fact::Kind::AssignOrigin:
-      return D->transfer(In, *F->getAs<AssignOriginFact>());
+    case Fact::Kind::OriginFlow:
+      return D->transfer(In, *F->getAs<OriginFlowFact>());
+    case Fact::Kind::KillOrigin:
+      return D->transfer(In, *F->getAs<KillOriginFact>());
     case Fact::Kind::ReturnOfOrigin:
       return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
     case Fact::Kind::Use:
@@ -897,7 +956,8 @@ class DataflowAnalysis {
 public:
   Lattice transfer(Lattice In, const IssueFact &) { return In; }
   Lattice transfer(Lattice In, const ExpireFact &) { return In; }
-  Lattice transfer(Lattice In, const AssignOriginFact &) { return In; }
+  Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
+  Lattice transfer(Lattice In, const KillOriginFact &) { return In; }
   Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
   Lattice transfer(Lattice In, const UseFact &) { return In; }
   Lattice transfer(Lattice In, const TestPointFact &) { return In; }
@@ -1047,14 +1107,27 @@ class LoanPropagationAnalysis
         LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID)));
   }
 
-  /// The destination origin's loan set is replaced by the source's.
-  /// This implicitly "resets" the old loans of the destination.
-  Lattice transfer(Lattice In, const AssignOriginFact &F) {
+  /// A flow from source to destination adds the source's loans to the
+  /// destination's, without clearing the destination's existing loans.
+  Lattice transfer(Lattice In, const OriginFlowFact &F) {
     OriginID DestOID = F.getDestOriginID();
     OriginID SrcOID = F.getSrcOriginID();
+
+    LoanSet DestLoans = getLoans(In, DestOID);
     LoanSet SrcLoans = getLoans(In, SrcOID);
+    LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory);
+
     return LoanPropagationLattice(
-        OriginLoanMapFactory.add(In.Origins, DestOID, SrcLoans));
+        OriginLoanMapFactory.add(In.Origins, DestOID, MergedLoans));
+  }
+
+  /// Clears the loan set of the specified origin. This is used on the
+  /// left-hand side of an assignment to invalidate the variable's old 
lifetime.
+  Lattice transfer(Lattice In, const KillOriginFact &F) {
+    OriginID OID = F.getOriginID();
+    // Replace the origin's loan set with an empty set.
+    return LoanPropagationLattice(OriginLoanMapFactory.add(
+        In.Origins, OID, LoanSetFactory.getEmptySet()));
   }
 
   LoanSet getLoans(OriginID OID, ProgramPoint P) {
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp 
b/clang/lib/Sema/CheckExprLifetime.cpp
index e02e00231e58e..e8a7ad3bd355a 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -10,6 +10,7 @@
 #include "clang/AST/Decl.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/Type.h"
+#include "clang/Analysis/Analyses/LifetimeAnnotations.h"
 #include "clang/Basic/DiagnosticSema.h"
 #include "clang/Sema/Initialization.h"
 #include "clang/Sema/Sema.h"
@@ -503,60 +504,6 @@ shouldTrackFirstArgumentForConstructor(const 
CXXConstructExpr *Ctor) {
   return true;
 }
 
-// Return true if this is an "normal" assignment operator.
-// We assume that a normal assignment operator always returns *this, that is,
-// an lvalue reference that is the same type as the implicit object parameter
-// (or the LHS for a non-member operator$=).
-static bool isNormalAssignmentOperator(const FunctionDecl *FD) {
-  OverloadedOperatorKind OO = FD->getDeclName().getCXXOverloadedOperator();
-  if (OO == OO_Equal || isCompoundAssignmentOperator(OO)) {
-    QualType RetT = FD->getReturnType();
-    if (RetT->isLValueReferenceType()) {
-      ASTContext &Ctx = FD->getASTContext();
-      QualType LHST;
-      auto *MD = dyn_cast<CXXMethodDecl>(FD);
-      if (MD && MD->isCXXInstanceMember())
-        LHST = 
Ctx.getLValueReferenceType(MD->getFunctionObjectParameterType());
-      else
-        LHST = FD->getParamDecl(0)->getType();
-      if (Ctx.hasSameType(RetT, LHST))
-        return true;
-    }
-  }
-  return false;
-}
-
-static const FunctionDecl *
-getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) {
-  return FD != nullptr ? FD->getMostRecentDecl() : nullptr;
-}
-
-static const CXXMethodDecl *
-getDeclWithMergedLifetimeBoundAttrs(const CXXMethodDecl *CMD) {
-  const FunctionDecl *FD = CMD;
-  return cast_if_present<CXXMethodDecl>(
-      getDeclWithMergedLifetimeBoundAttrs(FD));
-}
-
-bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
-  FD = getDeclWithMergedLifetimeBoundAttrs(FD);
-  const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
-  if (!TSI)
-    return false;
-  // Don't declare this variable in the second operand of the for-statement;
-  // GCC miscompiles that by ending its lifetime before evaluating the
-  // third operand. See gcc.gnu.org/PR86769.
-  AttributedTypeLoc ATL;
-  for (TypeLoc TL = TSI->getTypeLoc();
-       (ATL = TL.getAsAdjusted<AttributedTypeLoc>());
-       TL = ATL.getModifiedLoc()) {
-    if (ATL.getAttrAs<LifetimeBoundAttr>())
-      return true;
-  }
-
-  return isNormalAssignmentOperator(FD);
-}
-
 // Visit lifetimebound or gsl-pointer arguments.
 static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
                                        LocalVisitor Visit) {
@@ -639,7 +586,8 @@ static void visitFunctionCallArguments(IndirectLocalPath 
&Path, Expr *Call,
     // lifetimebound.
     if (Sema::CanBeGetReturnObject(Callee))
       CheckCoroObjArg = false;
-    if (implicitObjectParamIsLifetimeBound(Callee) || CheckCoroObjArg)
+    if (lifetimes::implicitObjectParamIsLifetimeBound(Callee) ||
+        CheckCoroObjArg)
       VisitLifetimeBoundArg(Callee, ObjectArg);
     else if (EnableGSLAnalysis) {
       if (auto *CME = dyn_cast<CXXMethodDecl>(Callee);
@@ -648,7 +596,8 @@ static void visitFunctionCallArguments(IndirectLocalPath 
&Path, Expr *Call,
     }
   }
 
-  const FunctionDecl *CanonCallee = 
getDeclWithMergedLifetimeBoundAttrs(Callee);
+  const FunctionDecl *CanonCallee =
+      lifetimes::getDeclWithMergedLifetimeBoundAttrs(Callee);
   unsigned NP = std::min(Callee->getNumParams(), CanonCallee->getNumParams());
   for (unsigned I = 0, N = std::min<unsigned>(NP, Args.size()); I != N; ++I) {
     Expr *Arg = Args[I];
@@ -1276,19 +1225,14 @@ static AnalysisResult analyzePathForGSLPointer(const 
IndirectLocalPath &Path,
   return Report;
 }
 
-static bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD) {
-  CMD = getDeclWithMergedLifetimeBoundAttrs(CMD);
-  return CMD && isNormalAssignmentOperator(CMD) && CMD->param_size() == 1 &&
-         CMD->getParamDecl(0)->hasAttr<LifetimeBoundAttr>();
-}
-
 static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef,
                                            const AssignedEntity &Entity) {
   bool EnableGSLAssignmentWarnings = !SemaRef.getDiagnostics().isIgnored(
       diag::warn_dangling_lifetime_pointer_assignment, SourceLocation());
   return (EnableGSLAssignmentWarnings &&
           (isRecordWithAttr<PointerAttr>(Entity.LHS->getType()) ||
-           isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator)));
+           lifetimes::isAssignmentOperatorLifetimeBound(
+               Entity.AssignmentOperator)));
 }
 
 static void
@@ -1610,11 +1554,11 @@ checkExprLifetimeImpl(Sema &SemaRef, const 
InitializedEntity *InitEntity,
   switch (LK) {
   case LK_Assignment: {
     if (shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity))
-      Path.push_back(
-          {isAssignmentOperatorLifetimeBound(AEntity->AssignmentOperator)
-               ? IndirectLocalPathEntry::LifetimeBoundCall
-               : IndirectLocalPathEntry::GslPointerAssignment,
-           Init});
+      Path.push_back({lifetimes::isAssignmentOperatorLifetimeBound(
+                          AEntity->AssignmentOperator)
+                          ? IndirectLocalPathEntry::LifetimeBoundCall
+                          : IndirectLocalPathEntry::GslPointerAssignment,
+                      Init});
     break;
   }
   case LK_LifetimeCapture: {
diff --git a/clang/lib/Sema/CheckExprLifetime.h 
b/clang/lib/Sema/CheckExprLifetime.h
index 6351e52a362f1..16595d0ca1b36 100644
--- a/clang/lib/Sema/CheckExprLifetime.h
+++ b/clang/lib/Sema/CheckExprLifetime.h
@@ -60,8 +60,6 @@ void checkCaptureByLifetime(Sema &SemaRef, const 
CapturingEntity &Entity,
 void checkExprLifetimeMustTailArg(Sema &SemaRef,
                                   const InitializedEntity &Entity, Expr *Init);
 
-bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
-
 } // namespace clang::sema
 
 #endif // LLVM_CLANG_SEMA_CHECK_EXPR_LIFETIME_H
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 99a29add8211d..35cdfbf8bf390 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -10,7 +10,6 @@
 //
 
//===----------------------------------------------------------------------===//
 
-#include "CheckExprLifetime.h"
 #include "TypeLocBuilder.h"
 #include "clang/APINotes/APINotesReader.h"
 #include "clang/APINotes/Types.h"
@@ -18,6 +17,7 @@
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/TypeLoc.h"
+#include "clang/Analysis/Analyses/LifetimeAnnotations.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Lex/Lexer.h"
 #include "clang/Sema/SemaObjC.h"
@@ -654,7 +654,7 @@ static void ProcessAPINotes(Sema &S, CXXMethodDecl *Method,
                             const api_notes::CXXMethodInfo &Info,
                             VersionedInfoMetadata Metadata) {
   if (Info.This && Info.This->isLifetimebound() &&
-      !sema::implicitObjectParamIsLifetimeBound(Method)) {
+      !lifetimes::implicitObjectParamIsLifetimeBound(Method)) {
     auto MethodType = Method->getType();
     auto *attr = ::new (S.Context)
         LifetimeBoundAttr(S.Context, getPlaceholderAttrInfo());
diff --git a/clang/test/Analysis/LifetimeSafety/benchmark.py 
b/clang/test/Analysis/LifetimeSafety/benchmark.py
index 2373f9984eecd..d2e5f0b2122a3 100644
--- a/clang/test/Analysis/LifetimeSafety/benchmark.py
+++ b/clang/test/Analysis/LifetimeSafety/benchmark.py
@@ -340,7 +340,7 @@ def run_single_test(
             "name": "cycle",
             "title": "Pointer Cycle in Loop",
             "generator_func": generate_cpp_cycle_test,
-            "n_values": [25, 50, 75, 100],
+            "n_values": [50, 75, 100, 200, 300],
         },
         {
             "name": "merge",
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp 
b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index 7dac27506fb6b..910b2df73b2d5 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -12,12 +12,12 @@ MyObj* return_local_addr() {
   MyObj x {10};
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_X]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_X]] (Expr: DeclRefExpr))
   MyObj* p = &x;
-// CHECK:   AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_X]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_X]] 
(Expr: UnaryOperator))
   return p;
 // CHECK:   Use ([[O_P]] (Decl: p), Read)
-// CHECK:   AssignOrigin (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_P]] (Decl: p))
+// CHECK:   OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_P]] (Decl: p))
 // CHECK:   ReturnOfOrigin ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
 // CHECK:   Expire ([[L_X]] (Path: x))
 }
@@ -29,26 +29,26 @@ MyObj* assign_and_return_local_addr() {
   MyObj y{20};
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Issue ([[L_Y:[0-9]+]] (Path: y), ToOrigin: [[O_DRE_Y:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_Y]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_Y]] (Expr: DeclRefExpr))
   MyObj* ptr1 = &y;
-// CHECK:   AssignOrigin (Dest: [[O_PTR1:[0-9]+]] (Decl: ptr1), Src: 
[[O_ADDR_Y]] (Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_PTR1:[0-9]+]] (Decl: ptr1), Src: 
[[O_ADDR_Y]] (Expr: UnaryOperator))
   MyObj* ptr2 = ptr1;
 // CHECK:   Use ([[O_PTR1]] (Decl: ptr1), Read)
-// CHECK:   AssignOrigin (Dest: [[O_PTR1_RVAL:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1))
-// CHECK:   AssignOrigin (Dest: [[O_PTR2:[0-9]+]] (Decl: ptr2), Src: 
[[O_PTR1_RVAL]] (Expr: ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_PTR1_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_PTR1]] (Decl: ptr1))
+// CHECK:   OriginFlow (Dest: [[O_PTR2:[0-9]+]] (Decl: ptr2), Src: 
[[O_PTR1_RVAL]] (Expr: ImplicitCastExpr))
   ptr2 = ptr1;
 // CHECK:   Use ([[O_PTR1]] (Decl: ptr1), Read)
-// CHECK:   AssignOrigin (Dest: [[O_PTR1_RVAL_2:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1))
+// CHECK:   OriginFlow (Dest: [[O_PTR1_RVAL_2:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1))
 // CHECK:   Use ({{[0-9]+}} (Decl: ptr2), Write)
-// CHECK:   AssignOrigin (Dest: [[O_PTR2]] (Decl: ptr2), Src: 
[[O_PTR1_RVAL_2]] (Expr: ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR1_RVAL_2]] 
(Expr: ImplicitCastExpr))
   ptr2 = ptr2; // Self assignment.
 // CHECK:   Use ([[O_PTR2]] (Decl: ptr2), Read)
-// CHECK:   AssignOrigin (Dest: [[O_PTR2_RVAL:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2))
+// CHECK:   OriginFlow (Dest: [[O_PTR2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_PTR2]] (Decl: ptr2))
 // CHECK:   Use ([[O_PTR2]] (Decl: ptr2), Write)
-// CHECK:   AssignOrigin (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR2_RVAL]] 
(Expr: ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR2_RVAL]] 
(Expr: ImplicitCastExpr))
   return ptr2;
 // CHECK:   Use ([[O_PTR2]] (Decl: ptr2), Read)
-// CHECK:   AssignOrigin (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_PTR2]] (Decl: ptr2))
+// CHECK:   OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_PTR2]] (Decl: ptr2))
 // CHECK:   ReturnOfOrigin ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
 // CHECK:   Expire ([[L_Y]] (Path: y))
 }
@@ -70,9 +70,9 @@ void loan_expires_cpp() {
   MyObj obj{1};
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Issue ([[L_OBJ:[0-9]+]] (Path: obj), ToOrigin: 
[[O_DRE_OBJ:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_OBJ:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_OBJ]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_OBJ:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_OBJ]] (Expr: DeclRefExpr))
   MyObj* pObj = &obj;
-// CHECK:   AssignOrigin (Dest: {{[0-9]+}} (Decl: pObj), Src: [[O_ADDR_OBJ]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: {{[0-9]+}} (Decl: pObj), Src: [[O_ADDR_OBJ]] 
(Expr: UnaryOperator))
 // CHECK:   Expire ([[L_OBJ]] (Path: obj))
 }
 
@@ -83,9 +83,9 @@ void loan_expires_trivial() {
   int trivial_obj = 1;
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Issue ([[L_TRIVIAL_OBJ:[0-9]+]] (Path: trivial_obj), ToOrigin: 
[[O_DRE_TRIVIAL:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: 
UnaryOperator), Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: 
UnaryOperator), Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr))
   int* pTrivialObj = &trivial_obj;
-// CHECK:   AssignOrigin (Dest: {{[0-9]+}} (Decl: pTrivialObj), Src: 
[[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: {{[0-9]+}} (Decl: pTrivialObj), Src: 
[[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator))
 // CHECK-NOT: Expire
 // CHECK-NEXT: End of Block
   // FIXME: Add check for Expire once trivial destructors are handled for 
expiration.
@@ -100,20 +100,20 @@ void conditional(bool condition) {
   if (condition)
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Issue ([[L_A:[0-9]+]] (Path: a), ToOrigin: [[O_DRE_A:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_A]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_A]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] 
(Expr: UnaryOperator))
     p = &a;
   else
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Issue ([[L_B:[0-9]+]] (Path: b), ToOrigin: [[O_DRE_B:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_B:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_B]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_B]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_B:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_B]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_B]] 
(Expr: UnaryOperator))
     p = &b;
 // CHECK: Block B{{[0-9]+}}:
   int *q = p;
 // CHECK:   Use ([[O_P]] (Decl: p), Read)
-// CHECK:   AssignOrigin (Dest: [[O_P_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_P]] (Decl: p))
-// CHECK:   AssignOrigin (Dest: [[O_Q:[0-9]+]] (Decl: q), Src: [[O_P_RVAL]] 
(Expr: ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_P_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_P]] (Decl: p))
+// CHECK:   OriginFlow (Dest: [[O_Q:[0-9]+]] (Decl: q), Src: [[O_P_RVAL]] 
(Expr: ImplicitCastExpr))
 }
 
 
@@ -128,36 +128,36 @@ void pointers_in_a_cycle(bool condition) {
   MyObj* p3 = &v3;
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Issue ([[L_V1:[0-9]+]] (Path: v1), ToOrigin: [[O_DRE_V1:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_V1:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_V1]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P1:[0-9]+]] (Decl: p1), Src: [[O_ADDR_V1]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_V1:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_V1]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_P1:[0-9]+]] (Decl: p1), Src: [[O_ADDR_V1]] 
(Expr: UnaryOperator))
 // CHECK:   Issue ([[L_V2:[0-9]+]] (Path: v2), ToOrigin: [[O_DRE_V2:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_V2:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_V2]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P2:[0-9]+]] (Decl: p2), Src: [[O_ADDR_V2]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_V2:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_V2]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_P2:[0-9]+]] (Decl: p2), Src: [[O_ADDR_V2]] 
(Expr: UnaryOperator))
 // CHECK:   Issue ([[L_V3:[0-9]+]] (Path: v3), ToOrigin: [[O_DRE_V3:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_V3:[0-g]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_V3]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P3:[0-9]+]] (Decl: p3), Src: [[O_ADDR_V3]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_V3:[0-g]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_V3]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_P3:[0-9]+]] (Decl: p3), Src: [[O_ADDR_V3]] 
(Expr: UnaryOperator))
 
   while (condition) {
 // CHECK: Block B{{[0-9]+}}:
     MyObj* temp = p1;
 // CHECK:   Use ([[O_P1]] (Decl: p1), Read)
-// CHECK:   AssignOrigin (Dest: [[O_P1_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_P1]] (Decl: p1))
-// CHECK:   AssignOrigin (Dest: [[O_TEMP:[0-9]+]] (Decl: temp), Src: 
[[O_P1_RVAL]] (Expr: ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_P1_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_P1]] (Decl: p1))
+// CHECK:   OriginFlow (Dest: [[O_TEMP:[0-9]+]] (Decl: temp), Src: 
[[O_P1_RVAL]] (Expr: ImplicitCastExpr))
     p1 = p2;
 // CHECK:   Use ([[O_P2:[0-9]+]] (Decl: p2), Read)
-// CHECK:   AssignOrigin (Dest: [[O_P2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_P2]] (Decl: p2))
+// CHECK:   OriginFlow (Dest: [[O_P2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_P2]] (Decl: p2))
 // CHECK:   Use ({{[0-9]+}} (Decl: p1), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P1]] (Decl: p1), Src: [[O_P2_RVAL]] (Expr: 
ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_P1]] (Decl: p1), Src: [[O_P2_RVAL]] (Expr: 
ImplicitCastExpr))
     p2 = p3;
 // CHECK:   Use ([[O_P3:[0-9]+]] (Decl: p3), Read)
-// CHECK:   AssignOrigin (Dest: [[O_P3_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_P3]] (Decl: p3))
+// CHECK:   OriginFlow (Dest: [[O_P3_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_P3]] (Decl: p3))
 // CHECK:   Use ({{[0-9]+}} (Decl: p2), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P2]] (Decl: p2), Src: [[O_P3_RVAL]] (Expr: 
ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_P2]] (Decl: p2), Src: [[O_P3_RVAL]] (Expr: 
ImplicitCastExpr))
     p3 = temp;
 // CHECK:   Use ([[O_TEMP]] (Decl: temp), Read)
-// CHECK:   AssignOrigin (Dest: [[O_TEMP_RVAL:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: [[O_TEMP]] (Decl: temp))
+// CHECK:   OriginFlow (Dest: [[O_TEMP_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), 
Src: [[O_TEMP]] (Decl: temp))
 // CHECK:   Use ({{[0-9]+}} (Decl: p3), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P3]] (Decl: p3), Src: [[O_TEMP_RVAL]] 
(Expr: ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_P3]] (Decl: p3), Src: [[O_TEMP_RVAL]] (Expr: 
ImplicitCastExpr))
   }
 }
 
@@ -168,13 +168,13 @@ void overwrite_origin() {
 // CHECK: Block B{{[0-9]+}}:
   MyObj* p = &s1;
 // CHECK:   Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_S1]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] 
(Expr: UnaryOperator))
   p = &s2;
 // CHECK:   Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_S2]] (Expr: DeclRefExpr))
 // CHECK:   Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: 
UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: 
UnaryOperator))
 // CHECK:   Expire ([[L_S2]] (Path: s2))
 // CHECK:   Expire ([[L_S1]] (Path: s1))
 }
@@ -185,12 +185,12 @@ void reassign_to_null() {
 // CHECK: Block B{{[0-9]+}}:
   MyObj* p = &s1;
 // CHECK:   Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_S1]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] 
(Expr: UnaryOperator))
   p = nullptr;
-// CHECK:   AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: {{[0-9]+}} (Expr: CXXNullPtrLiteralExpr))
+// CHECK:   OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: {{[0-9]+}} (Expr: CXXNullPtrLiteralExpr))
 // CHECK:   Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_NULLPTR_CAST]] 
(Expr: ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_NULLPTR_CAST]] 
(Expr: ImplicitCastExpr))
 // CHECK:   Expire ([[L_S1]] (Path: s1))
 }
 // FIXME: Have a better representation for nullptr than just an empty origin. 
@@ -204,15 +204,15 @@ void reassign_in_if(bool condition) {
   MyObj* p = &s1;
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_S1]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] 
(Expr: UnaryOperator))
   if (condition) {
 // CHECK: Block B{{[0-9]+}}:
     p = &s2;
 // CHECK:   Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_S2]] (Expr: DeclRefExpr))
 // CHECK:   Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: 
UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: 
UnaryOperator))
   }
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Expire ([[L_S2]] (Path: s2))
@@ -227,32 +227,32 @@ void assign_in_switch(int mode) {
   MyObj s3;
   MyObj* p = nullptr;
 // CHECK: Block B{{[0-9]+}}:
-// CHECK:   AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: 
[[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
+// CHECK:   OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: 
[[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
   switch (mode) {
     case 1:
 // CHECK-DAG: Block B{{[0-9]+}}:
       p = &s1;
 // CHECK-DAG:   Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: 
[[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK-DAG:   AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: 
UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
+// CHECK-DAG:   OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
 // CHECK-DAG:   Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK-DAG:   AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S1]] 
(Expr: UnaryOperator))
+// CHECK-DAG:   OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: 
UnaryOperator))
       break;
     case 2:
 // CHECK-DAG: Block B{{[0-9]+}}:
       p = &s2;
 // CHECK-DAG:   Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: 
[[O_DRE_S2:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK-DAG:   AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: 
UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
+// CHECK-DAG:   OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
 // CHECK-DAG:   Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK-DAG:   AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] 
(Expr: UnaryOperator))
+// CHECK-DAG:   OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: 
UnaryOperator))
       break;
     default:
 // CHECK: Block B{{[0-9]+}}:
       p = &s3;
 // CHECK:   Issue ([[L_S3:[0-9]+]] (Path: s3), ToOrigin: [[O_DRE_S3:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_S3:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_S3]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_S3:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_S3]] (Expr: DeclRefExpr))
 // CHECK:   Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S3]] (Expr: 
UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S3]] (Expr: 
UnaryOperator))
       break;
   }
 // CHECK: Block B{{[0-9]+}}:
@@ -265,16 +265,16 @@ void assign_in_switch(int mode) {
 void loan_in_loop(bool condition) {
   MyObj* p = nullptr;
 // CHECK: Block B{{[0-9]+}}:
-// CHECK:   AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: 
[[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
+// CHECK:   OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: 
[[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
   while (condition) {
     MyObj inner;
 // CHECK: Block B{{[0-9]+}}:
     p = &inner;
 // CHECK:   Issue ([[L_INNER:[0-9]+]] (Path: inner), ToOrigin: 
[[O_DRE_INNER:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_INNER]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_INNER]] (Expr: DeclRefExpr))
 // CHECK:   Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: 
UnaryOperator))
 // CHECK:   Expire ([[L_INNER]] (Path: inner))
   }
 }
@@ -286,16 +286,17 @@ void loop_with_break(int count) {
   MyObj* p = &s1;
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_S1]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] 
(Expr: UnaryOperator))
   for (int i = 0; i < count; ++i) {
     if (i == 5) {
 // CHECK: Block B{{[0-9]+}}:
       p = &s2;
 // CHECK:   Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_S2]] (Expr: DeclRefExpr))
 // CHECK:   Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: 
UnaryOperator))
+// CHECK:   KillOrigin ([[O_P]] (Decl: p))
+// CHECK:   OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: 
UnaryOperator))
       break;
     }
   }
@@ -308,22 +309,24 @@ void loop_with_break(int count) {
 void nested_scopes() {
   MyObj* p = nullptr;
 // CHECK: Block B{{[0-9]+}}:
-// CHECK:   AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: 
[[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: 
ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
+// CHECK:   OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: 
[[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
   {
     MyObj outer;
     p = &outer;
 // CHECK:   Issue ([[L_OUTER:[0-9]+]] (Path: outer), ToOrigin: 
[[O_DRE_OUTER:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_OUTER:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_OUTER]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_OUTER:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_OUTER]] (Expr: DeclRefExpr))
 // CHECK:   Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_OUTER]] 
(Expr: UnaryOperator))
+// CHECK:   KillOrigin ([[O_P]] (Decl: p))
+// CHECK:   OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_OUTER]] (Expr: 
UnaryOperator))
     {
       MyObj inner;
       p = &inner;
 // CHECK:   Issue ([[L_INNER:[0-9]+]] (Path: inner), ToOrigin: 
[[O_DRE_INNER:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_INNER]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_INNER]] (Expr: DeclRefExpr))
 // CHECK:   Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] 
(Expr: UnaryOperator))
+// CHECK:   KillOrigin ([[O_P]] (Decl: p))
+// CHECK:   OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: 
UnaryOperator))
     }
 // CHECK:   Expire ([[L_INNER]] (Path: inner))
   }
@@ -336,14 +339,14 @@ void pointer_indirection() {
   int *p = &a;
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Issue ([[L_A:[0-9]+]] (Path: a), ToOrigin: [[O_DRE_A:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_A]] (Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] 
(Expr: UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_A]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] 
(Expr: UnaryOperator))
   int **pp = &p;
 // Note: No facts are generated for &p because the subexpression is a pointer 
type,
 // which is not yet supported by the origin model. This is expected.
   int *q = *pp;
 // CHECK:   Use ([[O_PP:[0-9]+]] (Decl: pp), Read)
-// CHECK:   AssignOrigin (Dest: {{[0-9]+}} (Decl: q), Src: {{[0-9]+}} (Expr: 
ImplicitCastExpr))
+// CHECK:   OriginFlow (Dest: {{[0-9]+}} (Decl: q), Src: {{[0-9]+}} (Expr: 
ImplicitCastExpr))
 }
 
 // CHECK-LABEL: Function: ternary_operator
@@ -360,7 +363,7 @@ void ternary_operator() {
 
 // CHECK: Block B{{[0-9]+}}:
 // CHECK:   Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK:   AssignOrigin (Dest: {{[0-9]+}} (Decl: p), Src: {{[0-9]+}} (Expr: 
ConditionalOperator))
+// CHECK:   OriginFlow (Dest: {{[0-9]+}} (Decl: p), Src: {{[0-9]+}} (Expr: 
ConditionalOperator))
 }
 
 // CHECK-LABEL: Function: test_use_facts
@@ -371,9 +374,9 @@ void test_use_facts() {
 // CHECK: Block B{{[0-9]+}}:
   p = &x;
 // CHECK:   Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] 
(Expr: DeclRefExpr))
-// CHECK:   AssignOrigin (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), 
Src: [[O_DRE_X]] (Expr: DeclRefExpr))
+// CHECK:   OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: 
[[O_DRE_X]] (Expr: DeclRefExpr))
 // CHECK:   Use ([[O_P:[0-9]+]] (Decl: p), Write)
-// CHECK:   AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_X]] (Expr: 
UnaryOperator))
+// CHECK:   OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_X]] (Expr: 
UnaryOperator))
   (void)*p;
 // CHECK:   Use ([[O_P]] (Decl: p), Read)
   usePointer(p);
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index bc8a5f3f7150f..0cfd69b68ec55 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -371,3 +371,152 @@ void no_error_if_dangle_then_rescue_gsl() {
   v = safe;    // 'v' is "rescued" before use by reassigning to a valid object.
   v.use();     // This is safe.
 }
+
+
+//===----------------------------------------------------------------------===//
+// Lifetimebound Attribute Tests
+//===----------------------------------------------------------------------===//
+
+View Identity(View v [[clang::lifetimebound]]);
+View Choose(bool cond, View a [[clang::lifetimebound]], View b 
[[clang::lifetimebound]]);
+MyObj* GetPointer(const MyObj& obj [[clang::lifetimebound]]);
+
+struct [[gsl::Pointer()]] LifetimeBoundView {
+  LifetimeBoundView();
+  LifetimeBoundView(const MyObj& obj [[clang::lifetimebound]]);
+  LifetimeBoundView pass() [[clang::lifetimebound]] { return *this; }
+  operator View() const [[clang::lifetimebound]];
+};
+
+void lifetimebound_simple_function() {
+  View v;
+  {
+    MyObj obj;
+    v = Identity(obj); // expected-warning {{object whose reference is 
captured does not live long enough}}
+  }                    // expected-note {{destroyed here}}
+  v.use();             // expected-note {{later used here}}
+}
+
+void lifetimebound_multiple_args_definite() {
+  View v;
+  {
+    MyObj obj1, obj2;
+    v = Choose(true,
+               obj1,  // expected-warning {{object whose reference is captured 
does not live long enough}}
+               obj2); // expected-warning {{object whose reference is captured 
does not live long enough}}
+  }                              // expected-note 2 {{destroyed here}}
+  v.use();                       // expected-note 2 {{later used here}}
+}
+
+void lifetimebound_multiple_args_potential(bool cond) {
+  MyObj safe;
+  View v = safe;
+  {
+    MyObj obj1;
+    if (cond) {
+      MyObj obj2;
+      v = Choose(true, 
+                 obj1,             // expected-warning {{object whose 
reference is captured may not live long enough}}
+                 obj2);            // expected-warning {{object whose 
reference is captured may not live long enough}}
+    }                              // expected-note {{destroyed here}}
+  }                                // expected-note {{destroyed here}}
+  v.use();                         // expected-note 2 {{later used here}}
+}
+
+View SelectFirst(View a [[clang::lifetimebound]], View b);
+void lifetimebound_mixed_args() {
+  View v;
+  {
+    MyObj obj1, obj2;
+    v = SelectFirst(obj1,        // expected-warning {{object whose reference 
is captured does not live long enough}}
+                    obj2);
+  }                              // expected-note {{destroyed here}}
+  v.use();                       // expected-note {{later used here}}
+}
+
+void lifetimebound_member_function() {
+  LifetimeBoundView lbv, lbv2;
+  {
+    MyObj obj;
+    lbv = obj;        // expected-warning {{object whose reference is captured 
does not live long enough}}
+    lbv2 = lbv.pass();
+  }                   // expected-note {{destroyed here}}
+  View v = lbv2;      // expected-note {{later used here}}
+  v.use();
+}
+
+void lifetimebound_conversion_operator() {
+  View v;
+  {
+    MyObj obj;
+    LifetimeBoundView lbv = obj; // expected-warning {{object whose reference 
is captured does not live long enough}}
+    v = lbv;                     // Conversion operator is lifetimebound
+  }                              // expected-note {{destroyed here}}
+  v.use();                       // expected-note {{later used here}}
+}
+
+void lifetimebound_chained_calls() {
+  View v;
+  {
+    MyObj obj;
+    v = Identity(Identity(Identity(obj))); // expected-warning {{object whose 
reference is captured does not live long enough}}
+  }                                        // expected-note {{destroyed here}}
+  v.use();                                 // expected-note {{later used here}}
+}
+
+void lifetimebound_with_pointers() {
+  MyObj* ptr;
+  {
+    MyObj obj;
+    ptr = GetPointer(obj); // expected-warning {{object whose reference is 
captured does not live long enough}}
+  }                        // expected-note {{destroyed here}}
+  (void)*ptr;              // expected-note {{later used here}}
+}
+
+void lifetimebound_no_error_safe_usage() {
+  MyObj obj;
+  View v1 = Identity(obj);      // No warning - obj lives long enough
+  View v2 = Choose(true, v1, Identity(obj)); // No warning - all args are safe
+  v2.use();                     // Safe usage
+}
+
+void lifetimebound_partial_safety(bool cond) {
+  MyObj safe_obj;
+  View v = safe_obj;
+  
+  if (cond) {
+    MyObj temp_obj;
+    v = Choose(true, 
+               safe_obj,
+               temp_obj); // expected-warning {{object whose reference is 
captured may not live long enough}}
+  }                       // expected-note {{destroyed here}}
+  v.use();                // expected-note {{later used here}}
+}
+
+// FIXME: Creating reference from lifetimebound call doesn't propagate loans.
+const MyObj& GetObject(View v [[clang::lifetimebound]]);
+void lifetimebound_return_reference() {
+  View v;
+  const MyObj* ptr;
+  {
+    MyObj obj;
+    View temp_v = obj;
+    const MyObj& ref = GetObject(temp_v);
+    ptr = &ref;
+  }
+  (void)*ptr;
+}
+
+// FIXME: No warning for non gsl::Pointer types. Origin tracking is only 
supported for pointer types.
+struct LifetimeBoundCtor {
+  LifetimeBoundCtor();
+  LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
+};
+void lifetimebound_ctor() {
+  LifetimeBoundCtor v;
+  {
+    MyObj obj;
+    v = obj;
+  }
+  (void)v;
+}
\ No newline at end of file
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp 
b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index bff5378c0a8a9..3821015f07fb1 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -68,6 +68,7 @@ class LifetimeTestRunner {
 
   LifetimeSafetyAnalysis &getAnalysis() { return *Analysis; }
   ASTContext &getASTContext() { return *ASTCtx; }
+  AnalysisDeclContext &getAnalysisContext() { return *AnalysisCtx; }
 
   ProgramPoint getProgramPoint(llvm::StringRef Annotation) {
     auto It = AnnotationToPointMap.find(Annotation);
@@ -106,7 +107,7 @@ class LifetimeTestHelper {
   std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) {
     auto *VD = findDecl<VarDecl>(VarName);
     if (!VD) {
-      ADD_FAILURE() << "No VarDecl found for '" << VarName << "'";
+      ADD_FAILURE() << "Failed to find VarDecl for '" << VarName << "'";
       return {};
     }
     std::vector<LoanID> LID = Analysis.getLoanIDForVar(VD);
@@ -136,11 +137,20 @@ class LifetimeTestHelper {
 private:
   template <typename DeclT> DeclT *findDecl(llvm::StringRef Name) {
     auto &Ctx = Runner.getASTContext();
-    auto Results = match(valueDecl(hasName(Name)).bind("v"), Ctx);
+    const auto *TargetFunc = Runner.getAnalysisContext().getDecl();
+    auto Results =
+        match(valueDecl(hasName(Name),
+                        hasAncestor(functionDecl(equalsNode(TargetFunc))))
+                  .bind("v"),
+              Ctx);
     if (Results.empty()) {
       ADD_FAILURE() << "Declaration '" << Name << "' not found in AST.";
       return nullptr;
     }
+    if (Results.size() > 1) {
+      ADD_FAILURE() << "Multiple declarations found for '" << Name << "'";
+      return nullptr;
+    }
     return const_cast<DeclT *>(selectFirst<DeclT>("v", Results));
   }
 
@@ -208,6 +218,19 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
     ExpectedLoans.insert(ExpectedLoans.end(), ExpectedLIDs.begin(),
                          ExpectedLIDs.end());
   }
+  std::sort(ExpectedLoans.begin(), ExpectedLoans.end());
+  std::sort(ActualLoans.begin(), ActualLoans.end());
+  if (ExpectedLoans != ActualLoans) {
+    *result_listener << "Expected: ";
+    for (const auto &LoanID : ExpectedLoans) {
+      *result_listener << LoanID.Value << ", ";
+    }
+    *result_listener << "Actual: ";
+    for (const auto &LoanID : ActualLoans) {
+      *result_listener << LoanID.Value << ", ";
+    }
+    return false;
+  }
 
   return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans),
                             ActualLoans, result_listener);
@@ -921,5 +944,235 @@ TEST_F(LifetimeAnalysisTest, 
GslPointerConversionOperator) {
   EXPECT_THAT(Origin("y"), HasLoansTo({"yl"}, "p1"));
 }
 
+TEST_F(LifetimeAnalysisTest, LifetimeboundSimple) {
+  SetupTest(R"(
+    View Identity(View v [[clang::lifetimebound]]);
+    void target() {
+      MyObj a, b;
+      View v1 = a;
+      POINT(p1);
+
+      View v2 = Identity(v1);
+      View v3 = Identity(b);
+      POINT(p2);
+    }
+  )");
+  EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
+  // The origin of v2 should now contain the loan to 'o' from v1.
+  EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2"));
+  EXPECT_THAT(Origin("v3"), HasLoansTo({"b"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundMemberFunction) {
+  SetupTest(R"(
+    struct [[gsl::Pointer()]] MyView {
+      MyView(const MyObj& o) {}
+      MyView pass() [[clang::lifetimebound]] { return *this; }
+    };
+    void target() {
+      MyObj o;
+      MyView v1 = o;
+      POINT(p1);
+      MyView v2 = v1.pass();
+      POINT(p2);
+    }
+  )");
+  EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1"));
+  // The call v1.pass() is bound to 'v1'. The origin of v2 should get the loans
+  // from v1.
+  EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundMultipleArgs) {
+  SetupTest(R"(
+    View Choose(bool cond, View a [[clang::lifetimebound]], View b 
[[clang::lifetimebound]]);
+    void target() {
+      MyObj o1, o2;
+      View v1 = o1;
+      View v2 = o2;
+      POINT(p1);
+
+      View v3 = Choose(true, v1, v2);
+      POINT(p2);
+    }
+  )");
+  EXPECT_THAT(Origin("v1"), HasLoansTo({"o1"}, "p1"));
+  EXPECT_THAT(Origin("v2"), HasLoansTo({"o2"}, "p2"));
+  // v3 should have loans from both v1 and v2, demonstrating the union of
+  // loans.
+  EXPECT_THAT(Origin("v3"), HasLoansTo({"o1", "o2"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundMixedArgs) {
+  SetupTest(R"(
+    View Choose(bool cond, View a [[clang::lifetimebound]], View b);
+    void target() {
+      MyObj o1, o2;
+      View v1 = o1;
+      View v2 = o2;
+      POINT(p1);
+
+      View v3 = Choose(true, v1, v2);
+      POINT(p2);
+    }
+  )");
+  EXPECT_THAT(Origin("v1"), HasLoansTo({"o1"}, "p1"));
+  EXPECT_THAT(Origin("v2"), HasLoansTo({"o2"}, "p1"));
+  // v3 should only have loans from v1, as v2 is not lifetimebound.
+  EXPECT_THAT(Origin("v3"), HasLoansTo({"o1"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundChainOfViews) {
+  SetupTest(R"(
+    View Identity(View v [[clang::lifetimebound]]);
+    View DoubleIdentity(View v [[clang::lifetimebound]]);
+
+    void target() {
+      MyObj obj;
+      View v1 = obj;
+      POINT(p1);
+      View v2 = DoubleIdentity(Identity(v1));
+      POINT(p2);
+    }
+  )");
+  EXPECT_THAT(Origin("v1"), HasLoansTo({"obj"}, "p1"));
+  // v2 should inherit the loan from v1 through the chain of calls.
+  EXPECT_THAT(Origin("v2"), HasLoansTo({"obj"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundRawPointerParameter) {
+  SetupTest(R"(
+    View ViewFromPtr(const MyObj* p [[clang::lifetimebound]]);
+    MyObj* PtrFromPtr(const MyObj* p [[clang::lifetimebound]]);
+    MyObj* PtrFromView(View v [[clang::lifetimebound]]);
+
+    void target() {
+      MyObj a;
+      View v = ViewFromPtr(&a);
+      POINT(p1);
+
+      MyObj b;
+      MyObj* ptr1 = PtrFromPtr(&b);
+      MyObj* ptr2 = PtrFromPtr(PtrFromPtr(PtrFromPtr(ptr1)));
+      POINT(p2);
+
+      MyObj c;
+      View v2 = ViewFromPtr(PtrFromView(c));
+      POINT(p3);
+    }
+  )");
+  EXPECT_THAT(Origin("v"), HasLoansTo({"a"}, "p1"));
+  EXPECT_THAT(Origin("ptr1"), HasLoansTo({"b"}, "p2"));
+  EXPECT_THAT(Origin("ptr2"), HasLoansTo({"b"}, "p2"));
+  EXPECT_THAT(Origin("v2"), HasLoansTo({"c"}, "p3"));
+}
+
+// FIXME: This can be controversial and may be revisited in the future.
+TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefViewParameter) {
+  SetupTest(R"(
+    View Identity(const View& v [[clang::lifetimebound]]);
+    void target() {
+      MyObj o;
+      View v1 = o;
+      View v2 = Identity(v1);
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefObjParam) {
+  SetupTest(R"(
+    View Identity(const MyObj& o [[clang::lifetimebound]]);
+    void target() {
+      MyObj a;
+      View v1 = Identity(a);
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundReturnReference) {
+  SetupTest(R"(
+    const MyObj& Identity(View v [[clang::lifetimebound]]);
+    void target() {
+      MyObj a;
+      View v1 = a;
+      POINT(p1);
+
+      View v2 = Identity(v1);
+      
+      const MyObj& b = Identity(v1);
+      View v3 = Identity(b);
+      POINT(p2);
+
+      MyObj c;
+      View v4 = Identity(c);
+      POINT(p3);
+    }
+  )");
+  EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
+  EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2"));
+
+  // FIXME: Handle reference types. 'v3' should have loan to 'a' instead of 
'b'.
+  EXPECT_THAT(Origin("v3"), HasLoansTo({"b"}, "p2"));
+
+  EXPECT_THAT(Origin("v4"), HasLoansTo({"c"}, "p3"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateFunction) {
+  SetupTest(R"(
+    template <typename T>
+    const T& Identity(T&& v [[clang::lifetimebound]]);
+    void target() {
+      MyObj a;
+      View v1 = Identity(a);
+      POINT(p1);
+
+      View v2 = Identity(v1);
+      const View& v3 = Identity(v1);
+      POINT(p2);
+    }
+  )");
+  EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
+  EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2"));
+  EXPECT_THAT(Origin("v3"), HasLoansTo({"a"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateClass) {
+  SetupTest(R"(
+    template<typename T>
+    struct [[gsl::Pointer()]] MyTemplateView {
+      MyTemplateView(const T& o) {}
+      MyTemplateView pass() [[clang::lifetimebound]] { return *this; }
+    };
+    void target() {
+      MyObj o;
+      MyTemplateView<MyObj> v1 = o;
+      POINT(p1);
+      MyTemplateView<MyObj> v2 = v1.pass();
+      POINT(p2);
+    }
+  )");
+  EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1"));
+  EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundConversionOperator) {
+  SetupTest(R"(
+    struct MyOwner {
+      MyObj o;
+      operator View() const [[clang::lifetimebound]];
+    };
+
+    void target() {
+      MyOwner owner;
+      View v = owner;
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("v"), HasLoansTo({"owner"}, "p1"));
+}
 } // anonymous namespace
 } // namespace clang::lifetimes::internal

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

Reply via email to