- 
                Notifications
    You must be signed in to change notification settings 
- Fork 15k
[LifetimeSafety] Implement support for lifetimebound attribute #158489
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| ✅ With the latest revision this PR passed the C/C++ code formatter. | 
29f3f38    to
    076766a      
    Compare
  
    076766a    to
    1c079e3      
    Compare
  
    1c079e3    to
    031309f      
    Compare
  
    | @llvm/pr-subscribers-llvm-adt @llvm/pr-subscribers-clang-temporal-safety Author: Utkarsh Saxena (usx95) ChangesImplemented support for  This change replaces the single  
 For assignments, the analysis now uses both operations in sequence ( Patch is 54.83 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/158489.diff 6 Files Affected: 
 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/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index da4af42853e55..a8bbeb6af78e2 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -212,8 +212,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 +287,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 +357,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 +474,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 +515,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);
     }
-    // FIXME: A more general VisitCallExpr could also be used here.
+    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);
+    }
+  }
+
+  void VisitCallExpr(const CallExpr *CE) {
+    handleFunctionCall(CE, CE->getDirectCallee(),
+                       {CE->getArgs(), CE->getNumArgs()});
   }
 
   void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N) {
@@ -508,7 +545,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 +559,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 +579,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 +596,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 +605,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 +613,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 +668,68 @@ 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);
+  }
+  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));
+  }
+  static 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 false;
+  }
   /// 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 +746,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 +786,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 +965,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 +982,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; }
@@ -1049,14 +1135,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/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_O...
[truncated]
 | 
| @llvm/pr-subscribers-clang-analysis Author: Utkarsh Saxena (usx95) ChangesImplemented support for  This change replaces the single  
 For assignments, the analysis now uses both operations in sequence ( Patch is 54.83 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/158489.diff 6 Files Affected: 
 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/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index da4af42853e55..a8bbeb6af78e2 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -212,8 +212,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 +287,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 +357,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 +474,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 +515,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);
     }
-    // FIXME: A more general VisitCallExpr could also be used here.
+    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);
+    }
+  }
+
+  void VisitCallExpr(const CallExpr *CE) {
+    handleFunctionCall(CE, CE->getDirectCallee(),
+                       {CE->getArgs(), CE->getNumArgs()});
   }
 
   void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N) {
@@ -508,7 +545,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 +559,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 +579,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 +596,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 +605,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 +613,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 +668,68 @@ 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);
+  }
+  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));
+  }
+  static 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 false;
+  }
   /// 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 +746,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 +786,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 +965,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 +982,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; }
@@ -1049,14 +1135,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/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_O...
[truncated]
 | 
0775d9e    to
    53adfd8      
    Compare
  
    4e63e10    to
    271e174      
    Compare
  
    271e174    to
    caac182      
    Compare
  
    caac182    to
    082d8a2      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, some minor nits inline. Thanks!
082d8a2    to
    1c92d1c      
    Compare
  
    382322b    to
    335b2b5      
    Compare
  
    | LLVM Buildbot has detected a new failure on builder  Full details are available at: https://lab.llvm.org/buildbot/#/builders/168/builds/16348 Here is the relevant piece of the build log for the reference | 
| LLVM Buildbot has detected a new failure on builder  Full details are available at: https://lab.llvm.org/buildbot/#/builders/72/builds/15226 Here is the relevant piece of the build log for the reference | 
…158489) Add support for `lifetimebound` attributes in the lifetime safety analysis to track loans from function parameters to return values. Implemented support for `lifetimebound` attributes on function parameters This change replaces the single `AssignOriginFact` with two separate operations: `OriginFlowFact` and `KillOriginFact`. The key difference is in semantics: * Old `AssignOriginFact`: Replaced the destination origin's loans entirely with the source origin's loans. * New `OriginFlowFact`: Can now optionally merge the source origin's loans to the destination's existing loans. * New `KillOriginFact`: Clears all loans from an origin. For function calls with `lifetimebound` parameters, we kill the the return value' origin first then use `OriginFlowFact` to accumulate loans from multiple parameters into the return value's origin - enabling tracking multiple lifetimebound arguments. - Added a new `LifetimeAnnotations.h/cpp` to provide helper functions for inspecting and inferring lifetime annotations - Moved several functions from `CheckExprLifetime.cpp` to the new file to make them reusable The `lifetimebound` attribute is a key mechanism for expressing lifetime dependencies between function parameters and return values. This change enables the lifetime safety analysis to properly track these dependencies, allowing it to detect more potential dangling reference issues.

Add support for
lifetimeboundattributes in the lifetime safety analysis to track loans from function parameters to return values.Implemented support for
lifetimeboundattributes on function parametersThis change replaces the single
AssignOriginFactwith two separate operations:OriginFlowFactandKillOriginFact. The key difference is in semantics:AssignOriginFact: Replaced the destination origin's loans entirely with the source origin's loans.OriginFlowFact: Can now optionally merge the source origin's loans to the destination's existing loans.KillOriginFact: Clears all loans from an origin.For function calls with
lifetimeboundparameters, we kill the the return value' origin first then useOriginFlowFactto accumulate loans from multiple parameters into the return value's origin - enabling tracking multiple lifetimebound arguments.LifetimeAnnotations.h/cppto provide helper functions for inspecting and inferring lifetime annotationsCheckExprLifetime.cppto the new file to make them reusableThe
lifetimeboundattribute is a key mechanism for expressing lifetime dependencies between function parameters and return values. This change enables the lifetime safety analysis to properly track these dependencies, allowing it to detect more potential dangling reference issues.