[clang] [LifetimeSafety] Add support for `new`/`delete` (PR #193776)

via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 23 08:28:55 PDT 2026


https://github.com/NeKon69 created https://github.com/llvm/llvm-project/pull/193776

This PR extends LifetimeSafety to also support heap allocations via `new`/`delete`.

# Contents


* Adds a new warning that is emitted on use-after-free.

* Renames `reportUseAfterFree` to `reportUseAfterScope` due to the old name being misleading (warnings are still called `use_after_scope`). 

* Adds a new `AccessPath::Kind` value `NewAllocation` that is used for loans issued from `new` allocations.

* Adds `VisitCXXNewExpr` and `VisitCXXDeleteExpr`, which handle loan issuance / origin propagation for `new` / `delete`.

* Comes with extensive testing of the new features (including new use-after-free tests, and use-after-scope coverage for `new`).

Completes part of #164963

Assisted-by: GPT-5.4 for writing most of the tests (the remaining tests, and everything else in this PR, were written by me).

>From 91892037592d54ead8ea32302daf260ad0b95554 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 13 Apr 2026 11:40:31 +0300
Subject: [PATCH 01/32] add declaration for DestroyOriginFact

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 6be8f6e455bc2..00b3a3e7ba27f 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -55,6 +55,8 @@ class Fact {
     OriginEscapes,
     /// An origin is invalidated (e.g. vector resized).
     InvalidateOrigin,
+    // An origin is manually destroyed (e.g. `delete`, manual destructor call).
+    DestroyOrigin,
   };
 
 private:
@@ -299,6 +301,24 @@ class MovedOriginFact : public Fact {
             const OriginManager &OM) const override;
 };
 
+class DestroyOriginFact : public Fact {
+  OriginID OID;
+  const Expr *DestroyExpr;
+
+public:
+  static bool classof(const Fact *F) {
+    return F->getKind() == Kind::DestroyOrigin;
+  }
+
+  DestroyOriginFact(OriginID OID, const Expr *DestroyExpr)
+      : Fact(Kind::DestroyOrigin), OID(OID), DestroyExpr(DestroyExpr) {}
+
+  OriginID getDestroyedOrigin() const { return OID; }
+  const Expr *getDestroyExpr() const { return DestroyExpr; }
+  void dump(llvm::raw_ostream &OS, const LoanManager &,
+            const OriginManager &OM) const override;
+};
+
 /// 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 {

>From 2934b47540ad102cb9cf8c5d4b1030083f4821e4 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 13 Apr 2026 11:43:22 +0300
Subject: [PATCH 02/32] add new fact definition

---
 clang/lib/Analysis/LifetimeSafety/Facts.cpp | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 1bc0521a72359..0f7f234dc7860 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -98,6 +98,13 @@ void InvalidateOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
   OS << ")\n";
 }
 
+void DestroyOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+                             const OriginManager &OM) const {
+  OS << "DestroyOrigin (";
+  OM.dump(getDestroyedOrigin(), OS);
+  OS << ")\n";
+}
+
 void TestPointFact::dump(llvm::raw_ostream &OS, const LoanManager &,
                          const OriginManager &) const {
   OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";

>From d7450dacbb8fdc252c380d71c3c0791bca0c7831 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Tue, 14 Apr 2026 21:56:50 +0300
Subject: [PATCH 03/32] Support `new`

---
 .../Analyses/LifetimeSafety/FactsGenerator.h  |  1 +
 .../LifetimeSafety/FactsGenerator.cpp         | 35 +++++++++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 2dbadb27981a7..d735175f1f00f 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -52,6 +52,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
   void VisitLambdaExpr(const LambdaExpr *LE);
   void VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE);
+  void VisitCXXNewExpr(const CXXNewExpr *NE);
 
 private:
   OriginList *getOriginsList(const ValueDecl &D);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 82b890b57817e..1f110d35af3d4 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -586,6 +586,41 @@ void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
       Dst->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true));
 }
 
+void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
+  NE->dumpColor();
+
+  OriginList *NList = getOriginsList(*NE)->peelOuterOrigin();
+  const auto FlowOrigins = [&](const auto &T) {
+    if (OriginList *ArgList = getOriginsList(*T); ArgList && NList)
+      CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+          NList->getOuterOriginID(), ArgList->getOuterOriginID(),
+          /*Kill=*/true));
+  };
+
+  if (auto *CE = NE->getConstructExpr()) {
+    VisitCXXConstructExpr(CE);
+    FlowOrigins(CE);
+    return;
+  }
+
+  if (auto *E = NE->getInitializer()) {
+    if (!NE->isArray()) {
+      FlowOrigins(E);
+      return;
+    }
+    if (const auto *ILE = dyn_cast<InitListExpr>(E); ILE) {
+      // FIXME: Right now this still overwrites the other origins. Probably will
+      // be fixed once OriginTree is in.
+      // We traverse the Init list in reverse order to prefer origins from the
+      // beginning.
+      for (unsigned i = ILE->getNumInits(); i > 0; i--) {
+        FlowOrigins(ILE->getInit(i - 1));
+      }
+      return;
+    }
+  }
+}
+
 bool FactsGenerator::escapesViaReturn(OriginID OID) const {
   return llvm::any_of(EscapesInCurrentBlock, [OID](const Fact *F) {
     if (const auto *EF = F->getAs<ReturnEscapeFact>())

>From e009d230ee54f4338ae5c15b01581be4912f492f Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Thu, 16 Apr 2026 18:20:55 +0300
Subject: [PATCH 04/32] finish c++ new expression

---
 clang/lib/Analysis/LifetimeSafety/Dataflow.h  |  3 ++
 .../LifetimeSafety/FactsGenerator.cpp         | 53 +++++++++----------
 .../LifetimeSafety/LoanPropagation.cpp        |  1 +
 3 files changed, 30 insertions(+), 27 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
index 0f64ac8a36ef7..48f112521d27f 100644
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
@@ -180,6 +180,8 @@ class DataflowAnalysis {
       return D->transfer(In, *F->getAs<TestPointFact>());
     case Fact::Kind::InvalidateOrigin:
       return D->transfer(In, *F->getAs<InvalidateOriginFact>());
+    case Fact::Kind::DestroyOrigin:
+      return D->transfer(In, *F->getAs<DestroyOriginFact>());
     }
     llvm_unreachable("Unknown fact kind");
   }
@@ -193,6 +195,7 @@ class DataflowAnalysis {
   Lattice transfer(Lattice In, const UseFact &) { return In; }
   Lattice transfer(Lattice In, const TestPointFact &) { return In; }
   Lattice transfer(Lattice In, const InvalidateOriginFact &) { return In; }
+  Lattice transfer(Lattice In, const DestroyOriginFact &) { return In; }
 };
 } // namespace clang::lifetimes::internal
 #endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 1f110d35af3d4..8550b5e5d9283 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -587,36 +587,35 @@ void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
 }
 
 void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
-  NE->dumpColor();
-
-  OriginList *NList = getOriginsList(*NE)->peelOuterOrigin();
-  const auto FlowOrigins = [&](const auto &T) {
-    if (OriginList *ArgList = getOriginsList(*T); ArgList && NList)
+  OriginList *NewList = getOriginsList(*NE);
+
+  // Check if we have a placement new where the second argument is void*, to
+  // avoid flowing from std::nothrow and the placement parameter amount is 1,
+  // that is to mostly limit to standard library placement new
+  if (NE->getNumPlacementArgs() == 1) {
+    if (const auto *Arg = NE->getOperatorNew()
+                              ->getParamDecl(1)
+                              ->getType()
+                              ->getAs<PointerType>();
+        Arg && Arg->isVoidPointerType()) {
+      OriginList *PlacementList = getOriginsList(*NE->getPlacementArg(0));
       CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
-          NList->getOuterOriginID(), ArgList->getOuterOriginID(),
-          /*Kill=*/true));
-  };
-
-  if (auto *CE = NE->getConstructExpr()) {
-    VisitCXXConstructExpr(CE);
-    FlowOrigins(CE);
-    return;
+          NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
+          true));
+    }
   }
 
-  if (auto *E = NE->getInitializer()) {
-    if (!NE->isArray()) {
-      FlowOrigins(E);
-      return;
-    }
-    if (const auto *ILE = dyn_cast<InitListExpr>(E); ILE) {
-      // FIXME: Right now this still overwrites the other origins. Probably will
-      // be fixed once OriginTree is in.
-      // We traverse the Init list in reverse order to prefer origins from the
-      // beginning.
-      for (unsigned i = ILE->getNumInits(); i > 0; i--) {
-        FlowOrigins(ILE->getInit(i - 1));
-      }
-      return;
+  NewList = NewList->peelOuterOrigin();
+
+  if (auto *CE = NE->getConstructExpr(); CE) {
+    if (OriginList *ArgList = getOriginsList(*CE); ArgList && NewList)
+      flow(NewList, ArgList, true);
+  } else if (const Expr *E = NE->getInitializer(); E) {
+    if (const auto *ILE = dyn_cast<InitListExpr>(E); NE->isArray() && ILE) {
+      if (OriginList *InitList = getOriginsList(*ILE); InitList && NewList)
+        flow(NewList, InitList, true);
+    } else if (OriginList *ArgList = getOriginsList(*E); ArgList && NewList) {
+      flow(NewList, ArgList, true);
     }
   }
 }
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index e437fb7d41268..fdde0121c4e5b 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -68,6 +68,7 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
       case Fact::Kind::Expire:
       case Fact::Kind::TestPoint:
       case Fact::Kind::InvalidateOrigin:
+      case Fact::Kind::DestroyOrigin:
         break;
       }
     }

>From 775dbdf5ea8743661236a9d0270c9ed4c4ec2304 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Thu, 16 Apr 2026 21:17:23 +0300
Subject: [PATCH 05/32] add fact handling, introduce `delete` visitor

---
 .../Analyses/LifetimeSafety/FactsGenerator.h  |  1 +
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 31 ++++++++++++++++++-
 .../LifetimeSafety/FactsGenerator.cpp         | 18 +++++++++--
 3 files changed, 46 insertions(+), 4 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index d735175f1f00f..a88c51e4cf6ef 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -53,6 +53,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   void VisitLambdaExpr(const LambdaExpr *LE);
   void VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE);
   void VisitCXXNewExpr(const CXXNewExpr *NE);
+  void VisitCXXDeleteExpr(const CXXDeleteExpr *DE);
 
 private:
   OriginList *getOriginsList(const ValueDecl &D);
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 36477c6f67b52..004158143f5b7 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -98,6 +98,8 @@ class LifetimeChecker {
           checkInvalidation(IOF);
         else if (const auto *OEF = F->getAs<OriginEscapesFact>())
           checkAnnotations(OEF);
+        else if (const auto *DOF = F->getAs<DestroyOriginFact>())
+          checkDestroyed(DOF);
     issuePendingWarnings();
     suggestAnnotations();
     reportNoescapeViolations();
@@ -219,6 +221,32 @@ class LifetimeChecker {
     }
   }
 
+  void checkDestroyed(const DestroyOriginFact *DOF) {
+    OriginID DestroyedOrigin = DOF->getDestroyedOrigin();
+    LoanSet DirectlyDestroyedLoans =
+        LoanPropagation.getLoans(DestroyedOrigin, DOF);
+    LivenessMap Origins = LiveOrigins.getLiveOriginsAt(DOF);
+    for (auto &[OID, LiveInfo] : Origins) {
+      LoanSet HeldLoans = LoanPropagation.getLoans(OID, DOF);
+      for (LoanID DestroyedLoanID : HeldLoans) {
+        if (!DirectlyDestroyedLoans.contains(DestroyedLoanID))
+          continue;
+
+        bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
+        bool LastDomination =
+            FinalWarningsMap.lookup(DestroyedLoanID).CausingFactDominatesExpiry;
+        if (!LastDomination) {
+          FinalWarningsMap[DestroyedLoanID] = {
+              /*ExpiryLoc=*/{},
+              /*CausingFact=*/LiveInfo.CausingFact,
+              /*MovedExpr=*/nullptr,
+              /*InvalidatedByExpr=*/DOF->getDestroyExpr(),
+              /*CausingFactDominatesExpiry=*/CurDomination};
+        }
+      }
+    }
+  }
+
   void issuePendingWarnings() {
     if (!SemaHelper)
       return;
@@ -269,7 +297,8 @@ class LifetimeChecker {
   }
 
   /// Returns the declaration of a function that is visible across translation
-  /// units, if such a declaration exists and is different from the definition.
+  /// units, if such a declaration exists and is different from the
+  /// definition.
   static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
                                             SourceManager &SM) {
     if (!FD.isExternallyVisible())
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 8550b5e5d9283..8f7a563c21b81 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -607,19 +607,31 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
 
   NewList = NewList->peelOuterOrigin();
 
+  if (!NewList)
+    return;
+
   if (auto *CE = NE->getConstructExpr(); CE) {
-    if (OriginList *ArgList = getOriginsList(*CE); ArgList && NewList)
+    if (OriginList *ArgList = getOriginsList(*CE); ArgList)
       flow(NewList, ArgList, true);
   } else if (const Expr *E = NE->getInitializer(); E) {
     if (const auto *ILE = dyn_cast<InitListExpr>(E); NE->isArray() && ILE) {
-      if (OriginList *InitList = getOriginsList(*ILE); InitList && NewList)
+      if (OriginList *InitList = getOriginsList(*ILE); InitList)
         flow(NewList, InitList, true);
-    } else if (OriginList *ArgList = getOriginsList(*E); ArgList && NewList) {
+    } else if (OriginList *ArgList = getOriginsList(*E); ArgList) {
       flow(NewList, ArgList, true);
     }
   }
 }
 
+void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
+  OriginList *List = getOriginsList(*DE->getArgument())->peelOuterOrigin();
+  while (List) {
+    CurrentBlockFacts.push_back(
+        FactMgr.createFact<DestroyOriginFact>(List->getOuterOriginID(), DE));
+    List = List->peelOuterOrigin();
+  }
+}
+
 bool FactsGenerator::escapesViaReturn(OriginID OID) const {
   return llvm::any_of(EscapesInCurrentBlock, [OID](const Fact *F) {
     if (const auto *EF = F->getAs<ReturnEscapeFact>())

>From 3f190e9c35ce873dae633f01f3da1534382b6737 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Thu, 16 Apr 2026 21:21:01 +0300
Subject: [PATCH 06/32] revert comment formatting change

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 004158143f5b7..c3945df5c88de 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -297,8 +297,7 @@ class LifetimeChecker {
   }
 
   /// Returns the declaration of a function that is visible across translation
-  /// units, if such a declaration exists and is different from the
-  /// definition.
+  /// units, if such a declaration exists and is different from the definition.
   static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
                                             SourceManager &SM) {
     if (!FD.isExternallyVisible())

>From eb9e40c757fb49241cc1006a55303e24d608e02c Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 18 Apr 2026 18:38:21 +0300
Subject: [PATCH 07/32] [LifetimeSafety] finish support for new/delete

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  |  1 +
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |  9 +++++---
 .../Analysis/Analyses/LifetimeSafety/Loans.h  | 14 ++++++++----
 clang/include/clang/Basic/DiagnosticGroups.td | 12 ++++++++--
 .../clang/Basic/DiagnosticSemaKinds.td        |  5 +++++
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  9 +++++---
 .../LifetimeSafety/FactsGenerator.cpp         | 22 ++++++++++++++-----
 clang/lib/Analysis/LifetimeSafety/Loans.cpp   |  4 ++++
 clang/lib/Sema/SemaLifetimeSafety.h           | 16 +++++++++++---
 9 files changed, 71 insertions(+), 21 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 00b3a3e7ba27f..7dc31c0eebbb9 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -301,6 +301,7 @@ class MovedOriginFact : public Fact {
             const OriginManager &OM) const override;
 };
 
+// Inner origin has been destroyed, e.g. via `delete`, manaul destructor call.
 class DestroyOriginFact : public Fact {
   OriginID OID;
   const Expr *DestroyExpr;
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 08038dd096685..e8eecf1bd7c3d 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -60,15 +60,18 @@ class LifetimeSafetySemaHelper {
   LifetimeSafetySemaHelper() = default;
   virtual ~LifetimeSafetySemaHelper() = default;
 
-  virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
-                                  const Expr *MovedExpr,
-                                  SourceLocation FreeLoc) {}
+  virtual void reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr,
+                                   const Expr *MovedExpr,
+                                   SourceLocation FreeLoc) {}
 
   virtual void reportUseAfterReturn(const Expr *IssueExpr,
                                     const Expr *ReturnExpr,
                                     const Expr *MovedExpr,
                                     SourceLocation ExpiryLoc) {}
 
+  virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
+                                  const Expr *FreedExpr) {}
+
   virtual void reportDanglingField(const Expr *IssueExpr,
                                    const FieldDecl *Field,
                                    const Expr *MovedExpr,
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index aee6bf9eb69c9..ff942d9425a89 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -47,20 +47,22 @@ class AccessPath {
     ValueDecl,
     MaterializeTemporary,
     PlaceholderParam,
-    PlaceholderThis
+    PlaceholderThis,
+    HeapAllocation,
   };
 
 private:
   Kind K;
-  const llvm::PointerUnion<const clang::ValueDecl *,
-                           const clang::MaterializeTemporaryExpr *,
-                           const ParmVarDecl *, const CXXMethodDecl *>
+  const llvm::PointerUnion<
+      const clang::ValueDecl *, const clang::MaterializeTemporaryExpr *,
+      const ParmVarDecl *, const CXXMethodDecl *, const CXXNewExpr *>
       Root;
 
 public:
   AccessPath(const clang::ValueDecl *D) : K(Kind::ValueDecl), Root(D) {}
   AccessPath(const clang::MaterializeTemporaryExpr *MTE)
       : K(Kind::MaterializeTemporary), Root(MTE) {}
+  AccessPath(const CXXNewExpr *New) : K(Kind::HeapAllocation), Root(New) {}
   static AccessPath Placeholder(const ParmVarDecl *PVD) {
     return AccessPath(Kind::PlaceholderParam, PVD);
   }
@@ -88,6 +90,10 @@ class AccessPath {
     return K == Kind::PlaceholderThis ? Root.dyn_cast<const CXXMethodDecl *>()
                                       : nullptr;
   }
+  const CXXNewExpr *getAsHeapAllocation() const {
+    return K == Kind::HeapAllocation ? Root.dyn_cast<const CXXNewExpr *>()
+                                     : nullptr;
+  }
 
   bool operator==(const AccessPath &RHS) const {
     return K == RHS.K && Root == RHS.Root;
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 98902b3d9caa8..408aae53eb0c2 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -586,6 +586,12 @@ This may contain false-positives, e.g. when the borrowed storage is potentially
   }];
 }
 
+def LifetimeSafetyUseAfterFree : DiagGroup<"lifetime-safety-use-after-free"> {
+    code Documentation = [{
+Warning to detect use-after-free, introduced by freeing an object and later using it.
+    }];
+}
+
 // Dangling-Field (aka Escape-To-Field)
 def LifetimeSafetyDanglingField : DiagGroup<"lifetime-safety-dangling-field"> {
   code Documentation = [{
@@ -622,14 +628,16 @@ def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive",
                                          [LifetimeSafetyUseAfterScope,
                                          LifetimeSafetyReturnStackAddr,
                                          LifetimeSafetyDanglingField,
-                                         LifetimeSafetyDanglingGlobal]>;
+                                         LifetimeSafetyDanglingGlobal,
+                                         LifetimeSafetyUseAfterFree]>;
 
 def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict",
                                     [LifetimeSafetyUseAfterScopeMoved,
                                     LifetimeSafetyReturnStackAddrMoved,
                                     LifetimeSafetyDanglingFieldMoved,
                                     LifetimeSafetyDanglingGlobal,
-                                    LifetimeSafetyInvalidation]>;
+                                    LifetimeSafetyInvalidation,
+                                    LifetimeSafetyUseAfterFree]>;
 
 def LifetimeSafety : DiagGroup<"lifetime-safety",
                                [LifetimeSafetyPermissive, LifetimeSafetyStrict]> {
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 6d2fae551566f..9466d7a68ff15 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10962,6 +10962,10 @@ def warn_lifetime_safety_use_after_scope_moved : Warning<
    "This could be false positive as the storage may have been moved later">,
    InGroup<LifetimeSafetyUseAfterScopeMoved>, DefaultIgnore;
 
+def warn_lifetime_safety_use_after_free : Warning<
+    "allocated object does not live long enough">,
+    InGroup<LifetimeSafetyUseAfterFree>, DefaultIgnore;
+
 def warn_lifetime_safety_return_stack_addr
     : Warning<"address of stack memory is returned later">,
       InGroup<LifetimeSafetyReturnStackAddr>,
@@ -11002,6 +11006,7 @@ def warn_lifetime_safety_dangling_global_moved
 def note_lifetime_safety_used_here : Note<"later used here">;
 def note_lifetime_safety_invalidated_here : Note<"invalidated here">;
 def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
+def note_lifetime_safety_freed_here : Note<"freed here">;
 def note_lifetime_safety_returned_here : Note<"returned here">;
 def note_lifetime_safety_moved_here : Note<"potentially moved here">;
 def note_lifetime_safety_dangling_field_here: Note<"this field dangles">;
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index c3945df5c88de..ec973ca7be9cb 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -262,7 +262,10 @@ class LifetimeChecker {
 
       if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) {
         if (Warning.InvalidatedByExpr) {
-          if (IssueExpr)
+          if (const CXXNewExpr *NE = dyn_cast<CXXNewExpr>(IssueExpr); NE)
+            SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(),
+                                           Warning.InvalidatedByExpr);
+          else if (IssueExpr)
             // Use-after-invalidation of an object on stack.
             SemaHelper->reportUseAfterInvalidation(IssueExpr, UF->getUseExpr(),
                                                    Warning.InvalidatedByExpr);
@@ -273,8 +276,8 @@ class LifetimeChecker {
 
         } else
           // Scope-based expiry (use-after-scope).
-          SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), MovedExpr,
-                                         ExpiryLoc);
+          SemaHelper->reportUseAfterScope(IssueExpr, UF->getUseExpr(),
+                                          MovedExpr, ExpiryLoc);
       } else if (const auto *OEF =
                      CausingFact.dyn_cast<const OriginEscapesFact *>()) {
         if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 8f7a563c21b81..a05c7624d10b8 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -94,6 +94,14 @@ static const Loan *createLoan(FactManager &FactMgr,
   return FactMgr.getLoanMgr().createLoan(Path, MTE);
 }
 
+/// Creates a loan for the heap allocation
+/// \param NE The CXXNewExpr that represents the allocation
+/// \return The new Loan on success, nullptr otherwise
+static const Loan *createLoan(FactManager &FactMgr, const CXXNewExpr *NE) {
+  AccessPath Path(NE);
+  return FactMgr.getLoanMgr().createLoan(Path, NE);
+}
+
 void FactsGenerator::run() {
   llvm::TimeTraceScope TimeProfile("FactGenerator");
   const CFG &Cfg = *AC.getCFG();
@@ -605,6 +613,10 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
     }
   }
 
+  const Loan *L = createLoan(FactMgr, NE);
+  CurrentBlockFacts.push_back(
+      FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
+
   NewList = NewList->peelOuterOrigin();
 
   if (!NewList)
@@ -624,12 +636,10 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
 }
 
 void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
-  OriginList *List = getOriginsList(*DE->getArgument())->peelOuterOrigin();
-  while (List) {
-    CurrentBlockFacts.push_back(
-        FactMgr.createFact<DestroyOriginFact>(List->getOuterOriginID(), DE));
-    List = List->peelOuterOrigin();
-  }
+  OriginList *List =
+      getOriginsList(*DE->getArgument()->IgnoreImpCasts())->peelOuterOrigin();
+  CurrentBlockFacts.push_back(
+      FactMgr.createFact<DestroyOriginFact>(List->getOuterOriginID(), DE));
 }
 
 bool FactsGenerator::escapesViaReturn(OriginID OID) const {
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index 336331b8f5a27..09da7467af9eb 100644
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
@@ -28,6 +28,10 @@ void AccessPath::dump(llvm::raw_ostream &OS) const {
   case Kind::PlaceholderThis:
     OS << "$this";
     break;
+  case Kind::HeapAllocation:
+    if (const auto *E = getAsHeapAllocation())
+      OS << "HeapAllocation at " << E;
+    break;
   }
 }
 
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index e6f7e3d929f61..46504bd77e0b5 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -43,9 +43,9 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
 public:
   LifetimeSafetySemaHelperImpl(Sema &S) : S(S) {}
 
-  void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
-                          const Expr *MovedExpr,
-                          SourceLocation FreeLoc) override {
+  void reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr,
+                           const Expr *MovedExpr,
+                           SourceLocation FreeLoc) override {
     S.Diag(IssueExpr->getExprLoc(),
            MovedExpr ? diag::warn_lifetime_safety_use_after_scope_moved
                      : diag::warn_lifetime_safety_use_after_scope)
@@ -58,6 +58,16 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
         << UseExpr->getSourceRange();
   }
 
+  void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
+                          const Expr *FreedExpr) override {
+    S.Diag(IssueExpr->getExprLoc(), diag::warn_lifetime_safety_use_after_free)
+        << IssueExpr->getSourceRange();
+    S.Diag(FreedExpr->getExprLoc(), diag::note_lifetime_safety_freed_here)
+        << FreedExpr->getSourceRange();
+    S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
+        << UseExpr->getSourceRange();
+  }
+
   void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr,
                             const Expr *MovedExpr,
                             SourceLocation ExpiryLoc) override {

>From a8e57bc36a621e65b8f7622c795d6e9d1b41640e Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 18 Apr 2026 20:58:17 +0300
Subject: [PATCH 08/32] a few bug fixes and added tests

---
 .../LifetimeSafety/FactsGenerator.cpp         |  19 +-
 clang/test/Sema/Inputs/lifetime-analysis.h    |   3 +
 clang/test/Sema/warn-lifetime-safety.cpp      | 254 ++++++++++++++++++
 3 files changed, 270 insertions(+), 6 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index a05c7624d10b8..4d57bf3d484d1 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -319,6 +319,12 @@ void FactsGenerator::VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
   case CK_BuiltinFnToFnPtr:
     // Ignore function-to-pointer decays.
     return;
+  case CK_BitCast:
+    // Only flow if the shapes are the same (e.g. casting from int** to void*
+    // will not flow here)
+    if (Src && Dest && Dest->getLength() == Src->getLength())
+      flow(Dest, Src, /*Kill=*/true);
+    return;
   default:
     return;
   }
@@ -611,12 +617,12 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
           NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
           true));
     }
+  } else {
+    const Loan *L = createLoan(FactMgr, NE);
+    CurrentBlockFacts.push_back(
+        FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
   }
 
-  const Loan *L = createLoan(FactMgr, NE);
-  CurrentBlockFacts.push_back(
-      FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
-
   NewList = NewList->peelOuterOrigin();
 
   if (!NewList)
@@ -636,8 +642,9 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
 }
 
 void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
-  OriginList *List =
-      getOriginsList(*DE->getArgument()->IgnoreImpCasts())->peelOuterOrigin();
+  OriginList *List = getOriginsList(*DE->getArgument()->IgnoreImpCasts());
+  if (List = List->peelOuterOrigin(); !List)
+    return;
   CurrentBlockFacts.push_back(
       FactMgr.createFact<DestroyOriginFact>(List->getOuterOriginID(), DE));
 }
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index d1e847d20cc50..2f38041f68c80 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -269,3 +269,6 @@ template<class T> struct is_pointer : false_type {};
 template<class T> struct is_pointer<T*> : true_type {};
 template<class T> struct is_pointer<T* const> : true_type {};
 }
+
+void *operator new(unsigned long, void *) noexcept;
+void *operator new[](unsigned long, void *) noexcept;
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 77d8e3370676d..5288291bd4411 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2531,3 +2531,257 @@ int *noreturn_dead_nested(bool cond, bool cond2, int *num) {
 }
 
 } // namespace conditional_operator_control_flow
+
+namespace heap_allocation {
+
+//===----------------------------------------------------------------------===//
+// new
+//===----------------------------------------------------------------------===//
+
+void new_view_from_dead_scope() {
+  View *p;
+  {
+    MyObj obj;
+    p = new View(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+  }                    // expected-note {{destroyed here}}
+  p->use();            // expected-note {{later used here}}
+}
+
+void new_int_basic() {
+  int *p = new int; // expected-warning {{allocated object does not live long enough}}
+  delete p;         // expected-note {{freed here}}
+  (void)*p;         // expected-note {{later used here}}
+}
+
+void new_int_parens() {
+  int *p = new int(); // expected-warning {{allocated object does not live long enough}}
+  delete p;           // expected-note {{freed here}}
+  (void)*p;           // expected-note {{later used here}}
+}
+
+void new_int_braces() {
+  int *p = new int{}; // expected-warning {{allocated object does not live long enough}}
+  delete p;           // expected-note {{freed here}}
+  (void)*p;           // expected-note {{later used here}}
+}
+
+void new_pointer_from_pointer() {
+  MyObj **p;
+  {
+    MyObj obj;
+    MyObj *q = &obj;    // expected-warning {{object whose reference is captured does not live long enough}}
+    p = new MyObj *(q); 
+  }                     // expected-note {{destroyed here}}
+  (void)**p;            // expected-note {{later used here}}
+}
+
+void new_pointer_from_dead_object() {
+  MyObj **p;
+  {
+    MyObj obj;
+    p = new MyObj *(&obj); // expected-warning {{object whose reference is captured does not live long enough}}
+  }                        // expected-note {{destroyed here}}
+  (void)**p;               // expected-note {{later used here}}
+}
+
+void new_array_basic() {
+  int *p = new int[2]; // expected-warning {{allocated object does not live long enough}}
+  delete[] p;          // expected-note {{freed here}}
+  (void)p[0];          // expected-note {{later used here}}
+}
+
+void new_array_parens() {
+  int *p = new int[2](); // expected-warning {{allocated object does not live long enough}}
+  delete[] p;            // expected-note {{freed here}}
+  (void)p[0];            // expected-note {{later used here}}
+}
+
+void new_array_braces() {
+  int *p = new int[2]{}; // expected-warning {{allocated object does not live long enough}}
+  delete[] p;            // expected-note {{freed here}}
+  (void)p[0];            // expected-note {{later used here}}
+}
+
+// FIXME: https://github.com/llvm/llvm-project/issues/187471
+void new_pointer_array_from_dead_objects() {
+  MyObj **arr;
+  {
+    MyObj a, b;
+    arr = new MyObj *[2]{&a, &b};
+  }
+  (void)arr[0]->id;
+  (void)arr[1]->id;
+}
+
+struct PointerArrayFieldHolder {
+  MyObj **Ptrs;
+};
+
+// FIXME: https://github.com/llvm/llvm-project/issues/187471
+void pointer_array_field_sensitivity() {
+  PointerArrayFieldHolder h;
+  {
+    MyObj a, b;
+    h.Ptrs = new MyObj *[2]{&a, &b};
+  }
+  (void)h.Ptrs[0]->id;
+}
+
+//===----------------------------------------------------------------------===//
+// placement new
+//===----------------------------------------------------------------------===//
+
+void placement_new_int_basic() {
+  int *p;
+  {
+    int storage;
+    p = new (&storage) int; // expected-warning {{object whose reference is captured does not live long enough}}
+  }                         // expected-note {{destroyed here}}
+  (void)*p;                 // expected-note {{later used here}}
+}
+
+void placement_new_int_parens() {
+  int *p;
+  {
+    int storage;
+    p = new (&storage) int(); // expected-warning {{object whose reference is captured does not live long enough}}
+  }                           // expected-note {{destroyed here}}
+  (void)*p;                   // expected-note {{later used here}}
+}
+
+void placement_new_int_braces() {
+  int *p;
+  {
+    int storage;
+    p = new (&storage) int{}; // expected-warning {{object whose reference is captured does not live long enough}}
+  }                           // expected-note {{destroyed here}}
+  (void)*p;                   // expected-note {{later used here}}
+}
+
+void placement_new_view_from_dead_scope() {
+  View storage;
+  View *p = &storage;
+  {
+    MyObj obj;
+    p = new (&storage) View(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+  }                               // expected-note {{destroyed here}}
+  p->use();                       // expected-note {{later used here}}
+}
+
+void placement_new_pointer_from_dead_object() {
+  MyObj *slot = nullptr;
+  MyObj **p = &slot;
+  {
+    MyObj obj;
+    p = new (&slot) MyObj *(&obj); // expected-warning {{object whose reference is captured does not live long enough}}
+  }                                // expected-note {{destroyed here}}
+  (void)**p;                       // expected-note {{later used here}}
+}
+
+void placement_new_array_basic() {
+  int *p;
+  {
+    int storage[2];
+    p = new (&storage) int[2]; // expected-warning {{object whose reference is captured does not live long enough}}
+  }                            // expected-note {{destroyed here}}
+  (void)p[0];                  // expected-note {{later used here}}
+}
+
+void placement_new_array_parens() {
+  int *p;
+  {
+    int storage[2];
+    p = new (&storage) int[2](); // expected-warning {{object whose reference is captured does not live long enough}}
+  }                              // expected-note {{destroyed here}}
+  (void)p[0];                    // expected-note {{later used here}}
+}
+
+void placement_new_array_braces() {
+  int *p;
+  {
+    int storage[2];
+    p = new (&storage) int[2]{}; // expected-warning {{object whose reference is captured does not live long enough}}
+  }                              // expected-note {{destroyed here}}
+  (void)p[0];                    // expected-note {{later used here}}
+}
+
+//===----------------------------------------------------------------------===//
+// delete
+//===----------------------------------------------------------------------===//
+
+void delete_direct_use_after_free() {
+  MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
+  delete p;             // expected-note {{freed here}}
+  (void)p->id;          // expected-note {{later used here}}
+}
+
+void delete_alias_use_after_free() {
+  MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
+  MyObj *q = p;
+  delete p;             // expected-note {{freed here}}
+  (void)q->id;          // expected-note {{later used here}}
+}
+
+void delete_pointer_propagation_use_after_free() {
+  MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
+  MyObj **pp = &p;
+  delete p;             // expected-note {{freed here}}
+  (void)(*pp)->id;      // expected-note {{later used here}}
+}
+
+void delete_array_use_after_free() {
+  int *p = new int[2]; // expected-warning {{allocated object does not live long enough}}
+  delete[] p;          // expected-note {{freed here}}
+  (void)p[1];          // expected-note {{later used here}}
+}
+
+void delete_nullptr_no_warning() {
+  int *p = nullptr;
+  delete p;
+}
+
+void delete_array_nullptr_no_warning() {
+  int *p = nullptr;
+  delete[] p;
+}
+
+struct ClassSpecificDelete {
+  int X;
+  static void operator delete(void *);
+};
+
+void class_specific_operator_delete_use_after_free() {
+  ClassSpecificDelete *p = new ClassSpecificDelete; // expected-warning {{allocated object does not live long enough}}
+  delete p;                                         // expected-note {{freed here}}
+  (void)p->X;                                       // expected-note {{later used here}}
+}
+
+struct PointerFieldHolder {
+  MyObj *Ptr;
+};
+
+// FIXME: https://github.com/llvm/llvm-project/issues/184344
+void placement_new_pointer_field_use_after_scope() {
+  PointerFieldHolder h;
+  PointerFieldHolder *p = &h;
+  {
+    MyObj obj;
+    p = new (&h) PointerFieldHolder{&obj};
+  }
+  (void)p->Ptr->id;
+}
+
+// FIXME: https://github.com/llvm/llvm-project/issues/184344
+void delete_through_pointer_field() {
+  PointerFieldHolder h{new MyObj};
+  delete h.Ptr;
+  (void)h.Ptr->id;
+}
+
+void delete_stack_object() {
+  MyObj obj;
+  delete &obj;
+  (void)obj.id;
+}
+
+} // namespace heap_allocation

>From df5235980e19977ce09b76a303179e6c6a3a9e72 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 18 Apr 2026 21:27:22 +0300
Subject: [PATCH 09/32] change wording in comment

---
 clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 7dc31c0eebbb9..9a36bdaf41b37 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -55,7 +55,7 @@ class Fact {
     OriginEscapes,
     /// An origin is invalidated (e.g. vector resized).
     InvalidateOrigin,
-    // An origin is manually destroyed (e.g. `delete`, manual destructor call).
+    // An origin is manually destroyed (e.g. via `delete`).
     DestroyOrigin,
   };
 
@@ -301,7 +301,7 @@ class MovedOriginFact : public Fact {
             const OriginManager &OM) const override;
 };
 
-// Inner origin has been destroyed, e.g. via `delete`, manaul destructor call.
+// Inner origin has been destroyed, e.g. via `delete`.
 class DestroyOriginFact : public Fact {
   OriginID OID;
   const Expr *DestroyExpr;

>From 576b37aeeb901491020c246ec96e6426f372879a Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 18 Apr 2026 23:34:38 +0300
Subject: [PATCH 10/32] fix windows test errors

---
 clang/test/Sema/Inputs/lifetime-analysis.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 2ca0ad759e578..2836ec00e97cb 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -290,5 +290,5 @@ class function<R(Args...)> {
 
 }
 
-void *operator new(unsigned long, void *) noexcept;
-void *operator new[](unsigned long, void *) noexcept;
+void *operator new(std::size_t, void *) noexcept;
+void *operator new[](std::size_t, void *) noexcept;

>From 1df6b221d1ac2ae06d4fb8e2f20aeacea05f01c8 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 17:31:44 +0300
Subject: [PATCH 11/32] address review comments, add a few more tests

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  |  2 +-
 .../Analysis/Analyses/LifetimeSafety/Loans.h  | 11 +++++-----
 .../LifetimeSafety/FactsGenerator.cpp         |  4 ++--
 clang/lib/Analysis/LifetimeSafety/Loans.cpp   |  2 +-
 clang/test/Sema/warn-lifetime-safety.cpp      | 22 +++++++++++++++++++
 5 files changed, 32 insertions(+), 9 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 71556b5880914..400590656a35e 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -55,7 +55,7 @@ class Fact {
     OriginEscapes,
     /// An origin is invalidated (e.g. vector resized).
     InvalidateOrigin,
-    // An origin is manually destroyed (e.g. via `delete`).
+    // An origin is explicitly destroyed (e.g. via `delete`).
     DestroyOrigin,
     /// All loans of an origin are cleared.
     KillOrigin,
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index ff942d9425a89..f1415d8753bb0 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -35,12 +35,13 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) {
 ///   - MaterializeTemporaryExpr: a temporary object
 ///   - ParmVarDecl: a function parameter (placeholder)
 ///   - CXXMethodDecl: the implicit 'this' object (placeholder)
+///   - CXXNewExpr: a heap allocation made by `new`
 ///
 /// Placeholder paths never expire within the function scope, as they represent
 /// storage from the caller's scope.
 ///
 /// TODO: Model access paths of other types, e.g. field, array subscript, heap
-/// and globals.
+/// allocation not through `new`, and globals.
 class AccessPath {
 public:
   enum class Kind : uint8_t {
@@ -48,7 +49,7 @@ class AccessPath {
     MaterializeTemporary,
     PlaceholderParam,
     PlaceholderThis,
-    HeapAllocation,
+    NewAllocation,
   };
 
 private:
@@ -62,7 +63,7 @@ class AccessPath {
   AccessPath(const clang::ValueDecl *D) : K(Kind::ValueDecl), Root(D) {}
   AccessPath(const clang::MaterializeTemporaryExpr *MTE)
       : K(Kind::MaterializeTemporary), Root(MTE) {}
-  AccessPath(const CXXNewExpr *New) : K(Kind::HeapAllocation), Root(New) {}
+  AccessPath(const CXXNewExpr *New) : K(Kind::NewAllocation), Root(New) {}
   static AccessPath Placeholder(const ParmVarDecl *PVD) {
     return AccessPath(Kind::PlaceholderParam, PVD);
   }
@@ -91,8 +92,8 @@ class AccessPath {
                                       : nullptr;
   }
   const CXXNewExpr *getAsHeapAllocation() const {
-    return K == Kind::HeapAllocation ? Root.dyn_cast<const CXXNewExpr *>()
-                                     : nullptr;
+    return K == Kind::NewAllocation ? Root.dyn_cast<const CXXNewExpr *>()
+                                    : nullptr;
   }
 
   bool operator==(const AccessPath &RHS) const {
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 8536d62ddac1f..3c7a01c99522d 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -334,7 +334,7 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
     return;
   case CK_BitCast:
     // Only flow if the shapes are the same (e.g. casting from int** to void*
-    // will not flow here)
+    // will not flow here).
     if (Src && Dest && Dest->getLength() == Src->getLength())
       flow(Dest, Src, /*Kill=*/true);
     return;
@@ -634,7 +634,7 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
 
   // Check if we have a placement new where the second argument is void*, to
   // avoid flowing from std::nothrow and the placement parameter amount is 1,
-  // that is to mostly limit to standard library placement new
+  // that is to mostly limit to standard library placement new.
   if (NE->getNumPlacementArgs() == 1) {
     if (const auto *Arg = NE->getOperatorNew()
                               ->getParamDecl(1)
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index 09da7467af9eb..e771995277ff0 100644
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
@@ -28,7 +28,7 @@ void AccessPath::dump(llvm::raw_ostream &OS) const {
   case Kind::PlaceholderThis:
     OS << "$this";
     break;
-  case Kind::HeapAllocation:
+  case Kind::NewAllocation:
     if (const auto *E = getAsHeapAllocation())
       OS << "HeapAllocation at " << E;
     break;
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 83633c3a5629d..f5d833ac77c5c 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2696,6 +2696,13 @@ void new_int_braces() {
   (void)*p;           // expected-note {{later used here}}
 }
 
+void conditional_delete(bool cond) {
+  int *p1 = new int;       // expected-warning {{allocated object does not live long enough}}
+  int *p2 = new int;
+  delete (cond ? p1 : p2); // expected-note {{freed here}}
+  (void)*p1;               // expected-note {{later used here}}
+}
+
 void new_pointer_from_pointer() {
   MyObj **p;
   {
@@ -2715,6 +2722,20 @@ void new_pointer_from_dead_object() {
   (void)**p;               // expected-note {{later used here}}
 }
 
+struct MultiView {
+  MultiView(MyObj& a [[clang::lifetimebound]], MyObj& b [[clang::lifetimebound]]);
+};
+
+void new_multiview_from_mixed_scope() {
+  MyObj obj1;
+  MultiView *p;
+  {
+    MyObj obj2;
+    p = new MultiView(obj1, obj2); // expected-warning {{object whose reference is captured does not live long enough}}
+  }                                        // expected-note {{destroyed here}}
+  (void)p;                                 // expected-note {{later used here}}
+}
+
 void new_array_basic() {
   int *p = new int[2]; // expected-warning {{allocated object does not live long enough}}
   delete[] p;          // expected-note {{freed here}}
@@ -2916,6 +2937,7 @@ void delete_stack_object() {
 }
 
 } // namespace heap_allocation
+
 namespace method_call_uses_field_origins {
 int GLOBAL_INT;
 std::string GLOBAL_STRING{"123"};

>From 2bfc98f7f24f2fcbc5b00d5c540857b10c3889f0 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 17:39:53 +0300
Subject: [PATCH 12/32] add new test case

---
 clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp |  4 +---
 clang/test/Sema/warn-lifetime-safety.cpp             | 10 ++++++++++
 2 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 3c7a01c99522d..4c7146ff1fbe0 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -671,9 +671,7 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
 }
 
 void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
-  OriginList *List = getOriginsList(*DE->getArgument()->IgnoreImpCasts());
-  if (List = List->peelOuterOrigin(); !List)
-    return;
+  OriginList *List = getOriginsList(*DE->getArgument());
   CurrentBlockFacts.push_back(
       FactMgr.createFact<DestroyOriginFact>(List->getOuterOriginID(), DE));
 }
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index f5d833ac77c5c..1de413c490bce 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2703,6 +2703,16 @@ void conditional_delete(bool cond) {
   (void)*p1;               // expected-note {{later used here}}
 }
 
+int* foo(int* x [[clang::lifetimebound]], int* y [[clang::lifetimebound]]);
+
+void delete_returned_from_call() {
+  int* x = new int(1); // expected-warning {{allocated object does not live long enough}}
+  int* y = new int(2); // expected-warning {{allocated object does not live long enough}}
+  delete foo(x, y);    // expected-note 2 {{freed here}}
+  (void)x;             // expected-note {{later used here}}
+  (void)y;             // expected-note {{later used here}}
+}
+
 void new_pointer_from_pointer() {
   MyObj **p;
   {

>From c5548a494b681cac8c117f021ed767766e9d12f6 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 17:41:15 +0300
Subject: [PATCH 13/32] update the comment

---
 clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 400590656a35e..3bdb9d977d8c9 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -303,7 +303,7 @@ class MovedOriginFact : public Fact {
             const OriginManager &OM) const override;
 };
 
-// Inner origin has been destroyed, e.g. via `delete`.
+// Origin has been destroyed, e.g. via `delete`.
 class DestroyOriginFact : public Fact {
   OriginID OID;
   const Expr *DestroyExpr;

>From 534eb5097cca7c276914ae53378fd7473ac4e09a Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 18:05:59 +0300
Subject: [PATCH 14/32] refactor logic for recording warnings

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 78 ++++++++++---------
 1 file changed, 41 insertions(+), 37 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index ce7fa385c996f..f363eecc5153b 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -187,6 +187,32 @@ class LifetimeChecker {
     }
   }
 
+  template <typename OriginFact, typename Predicate>
+  void recordWarningsForMatchingLoans(const Expr *InvalidationExpr,
+                                      LoanSet &DirectlyAffectedLoans,
+                                      LivenessMap &Origins, OriginFact OF,
+                                      Predicate Pred) {
+    for (auto &[OID, LiveInfo] : Origins) {
+      LoanSet HeldLoans = LoanPropagation.getLoans(OID, OF);
+      for (LoanID DestroyedLoanID : HeldLoans) {
+        if (!Pred(DestroyedLoanID))
+          continue;
+
+        bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
+        bool LastDomination =
+            FinalWarningsMap.lookup(DestroyedLoanID).CausingFactDominatesExpiry;
+        if (!LastDomination) {
+          FinalWarningsMap[DestroyedLoanID] = {
+              /*ExpiryLoc=*/{},
+              /*CausingFact=*/LiveInfo.CausingFact,
+              /*MovedExpr=*/nullptr,
+              /*InvalidatedByExpr=*/InvalidationExpr,
+              /*CausingFactDominatesExpiry=*/CurDomination};
+        }
+      }
+    }
+  }
+
   /// Checks for use-after-invalidation errors when a container is modified.
   ///
   /// This method identifies origins that are live at the point of invalidation
@@ -197,9 +223,11 @@ class LifetimeChecker {
     /// Get loans directly pointing to the invalidated container
     LoanSet DirectlyInvalidatedLoans =
         LoanPropagation.getLoans(InvalidatedOrigin, IOF);
-    auto IsInvalidated = [&](const Loan *L) {
+    auto IsInvalidated = [&](const LoanID &LID) {
       for (LoanID InvalidID : DirectlyInvalidatedLoans) {
         const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
+        const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
+
         if (InvalidL->getAccessPath() == L->getAccessPath())
           return true;
       }
@@ -207,23 +235,9 @@ class LifetimeChecker {
     };
     // For each live origin, check if it holds an invalidated loan and report.
     LivenessMap Origins = LiveOrigins.getLiveOriginsAt(IOF);
-    for (auto &[OID, LiveInfo] : Origins) {
-      LoanSet HeldLoans = LoanPropagation.getLoans(OID, IOF);
-      for (LoanID LiveLoanID : HeldLoans)
-        if (IsInvalidated(FactMgr.getLoanMgr().getLoan(LiveLoanID))) {
-          bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
-          bool LastDomination =
-              FinalWarningsMap.lookup(LiveLoanID).CausingFactDominatesExpiry;
-          if (!LastDomination) {
-            FinalWarningsMap[LiveLoanID] = {
-                /*ExpiryLoc=*/{},
-                /*CausingFact=*/LiveInfo.CausingFact,
-                /*MovedExpr=*/nullptr,
-                /*InvalidatedByExpr=*/IOF->getInvalidationExpr(),
-                /*CausingFactDominatesExpiry=*/CurDomination};
-          }
-        }
-    }
+    recordWarningsForMatchingLoans(IOF->getInvalidationExpr(),
+                                   DirectlyInvalidatedLoans, Origins, IOF,
+                                   IsInvalidated);
   }
 
   void checkDestroyed(const DestroyOriginFact *DOF) {
@@ -231,25 +245,14 @@ class LifetimeChecker {
     LoanSet DirectlyDestroyedLoans =
         LoanPropagation.getLoans(DestroyedOrigin, DOF);
     LivenessMap Origins = LiveOrigins.getLiveOriginsAt(DOF);
-    for (auto &[OID, LiveInfo] : Origins) {
-      LoanSet HeldLoans = LoanPropagation.getLoans(OID, DOF);
-      for (LoanID DestroyedLoanID : HeldLoans) {
-        if (!DirectlyDestroyedLoans.contains(DestroyedLoanID))
-          continue;
 
-        bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
-        bool LastDomination =
-            FinalWarningsMap.lookup(DestroyedLoanID).CausingFactDominatesExpiry;
-        if (!LastDomination) {
-          FinalWarningsMap[DestroyedLoanID] = {
-              /*ExpiryLoc=*/{},
-              /*CausingFact=*/LiveInfo.CausingFact,
-              /*MovedExpr=*/nullptr,
-              /*InvalidatedByExpr=*/DOF->getDestroyExpr(),
-              /*CausingFactDominatesExpiry=*/CurDomination};
-        }
-      }
-    }
+    auto IsDestroyed = [&DirectlyDestroyedLoans](const LoanID &LID) {
+      return DirectlyDestroyedLoans.contains(LID);
+    };
+
+    recordWarningsForMatchingLoans(DOF->getDestroyExpr(),
+                                   DirectlyDestroyedLoans, Origins, DOF,
+                                   IsDestroyed);
   }
 
   void issuePendingWarnings() {
@@ -305,7 +308,8 @@ class LifetimeChecker {
   }
 
   /// Returns the declaration of a function that is visible across translation
-  /// units, if such a declaration exists and is different from the definition.
+  /// units, if such a declaration exists and is different from the
+  /// definition.
   static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
                                             SourceManager &SM) {
     if (!FD.isExternallyVisible())

>From cb9700e9829ec69d4f812bd33291da5a5e254c22 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 18:09:20 +0300
Subject: [PATCH 15/32] rename variables

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index f363eecc5153b..6ea469525c049 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -188,25 +188,25 @@ class LifetimeChecker {
   }
 
   template <typename OriginFact, typename Predicate>
-  void recordWarningsForMatchingLoans(const Expr *InvalidationExpr,
+  void recordWarningsForMatchingLoans(const Expr *InvalidatingExpr,
                                       LoanSet &DirectlyAffectedLoans,
                                       LivenessMap &Origins, OriginFact OF,
                                       Predicate Pred) {
     for (auto &[OID, LiveInfo] : Origins) {
       LoanSet HeldLoans = LoanPropagation.getLoans(OID, OF);
-      for (LoanID DestroyedLoanID : HeldLoans) {
-        if (!Pred(DestroyedLoanID))
+      for (LoanID HeldLoan : HeldLoans) {
+        if (!Pred(HeldLoan))
           continue;
 
         bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
         bool LastDomination =
-            FinalWarningsMap.lookup(DestroyedLoanID).CausingFactDominatesExpiry;
+            FinalWarningsMap.lookup(HeldLoan).CausingFactDominatesExpiry;
         if (!LastDomination) {
-          FinalWarningsMap[DestroyedLoanID] = {
+          FinalWarningsMap[HeldLoan] = {
               /*ExpiryLoc=*/{},
               /*CausingFact=*/LiveInfo.CausingFact,
               /*MovedExpr=*/nullptr,
-              /*InvalidatedByExpr=*/InvalidationExpr,
+              /*InvalidatedByExpr=*/InvalidatingExpr,
               /*CausingFactDominatesExpiry=*/CurDomination};
         }
       }

>From 7bcc47f20f168a83a2cb59156b926ff96296f7e4 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 18:13:10 +0300
Subject: [PATCH 16/32] change function signature

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 6ea469525c049..6c58ed8bc67cb 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -189,10 +189,8 @@ class LifetimeChecker {
 
   template <typename OriginFact, typename Predicate>
   void recordWarningsForMatchingLoans(const Expr *InvalidatingExpr,
-                                      LoanSet &DirectlyAffectedLoans,
-                                      LivenessMap &Origins, OriginFact OF,
-                                      Predicate Pred) {
-    for (auto &[OID, LiveInfo] : Origins) {
+                                      OriginFact OF, Predicate Pred) {
+    for (auto &[OID, LiveInfo] : LiveOrigins.getLiveOriginsAt(OF)) {
       LoanSet HeldLoans = LoanPropagation.getLoans(OID, OF);
       for (LoanID HeldLoan : HeldLoans) {
         if (!Pred(HeldLoan))
@@ -235,8 +233,7 @@ class LifetimeChecker {
     };
     // For each live origin, check if it holds an invalidated loan and report.
     LivenessMap Origins = LiveOrigins.getLiveOriginsAt(IOF);
-    recordWarningsForMatchingLoans(IOF->getInvalidationExpr(),
-                                   DirectlyInvalidatedLoans, Origins, IOF,
+    recordWarningsForMatchingLoans(IOF->getInvalidationExpr(), IOF,
                                    IsInvalidated);
   }
 
@@ -250,9 +247,7 @@ class LifetimeChecker {
       return DirectlyDestroyedLoans.contains(LID);
     };
 
-    recordWarningsForMatchingLoans(DOF->getDestroyExpr(),
-                                   DirectlyDestroyedLoans, Origins, DOF,
-                                   IsDestroyed);
+    recordWarningsForMatchingLoans(DOF->getDestroyExpr(), DOF, IsDestroyed);
   }
 
   void issuePendingWarnings() {

>From 6179c6541c69b0dbb01441f75b77db87f23e22ee Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 18:36:29 +0300
Subject: [PATCH 17/32] delete placement new stuff

---
 .../LifetimeSafety/FactsGenerator.cpp         | 22 +-----
 clang/test/Sema/warn-lifetime-safety.cpp      | 78 -------------------
 2 files changed, 3 insertions(+), 97 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 4c7146ff1fbe0..aa5acc5838603 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -632,25 +632,9 @@ void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
 void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
   OriginList *NewList = getOriginsList(*NE);
 
-  // Check if we have a placement new where the second argument is void*, to
-  // avoid flowing from std::nothrow and the placement parameter amount is 1,
-  // that is to mostly limit to standard library placement new.
-  if (NE->getNumPlacementArgs() == 1) {
-    if (const auto *Arg = NE->getOperatorNew()
-                              ->getParamDecl(1)
-                              ->getType()
-                              ->getAs<PointerType>();
-        Arg && Arg->isVoidPointerType()) {
-      OriginList *PlacementList = getOriginsList(*NE->getPlacementArg(0));
-      CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
-          NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
-          true));
-    }
-  } else {
-    const Loan *L = createLoan(FactMgr, NE);
-    CurrentBlockFacts.push_back(
-        FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
-  }
+  const Loan *L = createLoan(FactMgr, NE);
+  CurrentBlockFacts.push_back(
+      FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
 
   NewList = NewList->peelOuterOrigin();
 
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 1de413c490bce..aca9538c02d40 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2789,84 +2789,6 @@ void pointer_array_field_sensitivity() {
   (void)h.Ptrs[0]->id;
 }
 
-//===----------------------------------------------------------------------===//
-// placement new
-//===----------------------------------------------------------------------===//
-
-void placement_new_int_basic() {
-  int *p;
-  {
-    int storage;
-    p = new (&storage) int; // expected-warning {{object whose reference is captured does not live long enough}}
-  }                         // expected-note {{destroyed here}}
-  (void)*p;                 // expected-note {{later used here}}
-}
-
-void placement_new_int_parens() {
-  int *p;
-  {
-    int storage;
-    p = new (&storage) int(); // expected-warning {{object whose reference is captured does not live long enough}}
-  }                           // expected-note {{destroyed here}}
-  (void)*p;                   // expected-note {{later used here}}
-}
-
-void placement_new_int_braces() {
-  int *p;
-  {
-    int storage;
-    p = new (&storage) int{}; // expected-warning {{object whose reference is captured does not live long enough}}
-  }                           // expected-note {{destroyed here}}
-  (void)*p;                   // expected-note {{later used here}}
-}
-
-void placement_new_view_from_dead_scope() {
-  View storage;
-  View *p = &storage;
-  {
-    MyObj obj;
-    p = new (&storage) View(obj); // expected-warning {{object whose reference is captured does not live long enough}}
-  }                               // expected-note {{destroyed here}}
-  p->use();                       // expected-note {{later used here}}
-}
-
-void placement_new_pointer_from_dead_object() {
-  MyObj *slot = nullptr;
-  MyObj **p = &slot;
-  {
-    MyObj obj;
-    p = new (&slot) MyObj *(&obj); // expected-warning {{object whose reference is captured does not live long enough}}
-  }                                // expected-note {{destroyed here}}
-  (void)**p;                       // expected-note {{later used here}}
-}
-
-void placement_new_array_basic() {
-  int *p;
-  {
-    int storage[2];
-    p = new (&storage) int[2]; // expected-warning {{object whose reference is captured does not live long enough}}
-  }                            // expected-note {{destroyed here}}
-  (void)p[0];                  // expected-note {{later used here}}
-}
-
-void placement_new_array_parens() {
-  int *p;
-  {
-    int storage[2];
-    p = new (&storage) int[2](); // expected-warning {{object whose reference is captured does not live long enough}}
-  }                              // expected-note {{destroyed here}}
-  (void)p[0];                    // expected-note {{later used here}}
-}
-
-void placement_new_array_braces() {
-  int *p;
-  {
-    int storage[2];
-    p = new (&storage) int[2]{}; // expected-warning {{object whose reference is captured does not live long enough}}
-  }                              // expected-note {{destroyed here}}
-  (void)p[0];                    // expected-note {{later used here}}
-}
-
 //===----------------------------------------------------------------------===//
 // delete
 //===----------------------------------------------------------------------===//

>From 26db25a5f194462abea1a4e1b7cadc3fa8f131aa Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 18:38:44 +0300
Subject: [PATCH 18/32] remove handling for bitcast

---
 clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index aa5acc5838603..ed25b0910b041 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -332,12 +332,6 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
   case CK_BuiltinFnToFnPtr:
     // Ignore function-to-pointer decays.
     return;
-  case CK_BitCast:
-    // Only flow if the shapes are the same (e.g. casting from int** to void*
-    // will not flow here).
-    if (Src && Dest && Dest->getLength() == Src->getLength())
-      flow(Dest, Src, /*Kill=*/true);
-    return;
   default:
     return;
   }

>From 114fc353376bad4cf3cc225c6c6ec979eb9af561 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 22:31:57 +0300
Subject: [PATCH 19/32] simplify VisitCXXNewExpr

---
 .../lib/Analysis/LifetimeSafety/FactsGenerator.cpp  | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index ed25b0910b041..d1528705cecb9 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -635,17 +635,8 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
   if (!NewList)
     return;
 
-  if (auto *CE = NE->getConstructExpr(); CE) {
-    if (OriginList *ArgList = getOriginsList(*CE); ArgList)
-      flow(NewList, ArgList, true);
-  } else if (const Expr *E = NE->getInitializer(); E) {
-    if (const auto *ILE = dyn_cast<InitListExpr>(E); NE->isArray() && ILE) {
-      if (OriginList *InitList = getOriginsList(*ILE); InitList)
-        flow(NewList, InitList, true);
-    } else if (OriginList *ArgList = getOriginsList(*E); ArgList) {
-      flow(NewList, ArgList, true);
-    }
-  }
+  if (OriginList *Init = getOriginsList(*NE->getInitializer()); Init)
+    flow(NewList, Init, true);
 }
 
 void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {

>From 1b207e72817a6b86b3b1da8889a1b634aed19a22 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 22:35:04 +0300
Subject: [PATCH 20/32] add a fixme

---
 clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index d1528705cecb9..33ffb61567b07 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -635,6 +635,8 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
   if (!NewList)
     return;
 
+  // FIXME: Once arrays are handled properly, remove the check for Init is
+  // non-null.
   if (OriginList *Init = getOriginsList(*NE->getInitializer()); Init)
     flow(NewList, Init, true);
 }

>From 9f75d6428415348f5fe476757405c768165915cf Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 23:45:17 +0300
Subject: [PATCH 21/32] address review comments

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 21 ++++++---
 .../Sema/warn-lifetime-safety-suggestions.cpp | 46 +++++++++++++++++++
 clang/test/Sema/warn-lifetime-safety.cpp      | 28 +++++++----
 3 files changed, 78 insertions(+), 17 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 6c58ed8bc67cb..cb450a4d08a30 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -221,10 +221,10 @@ class LifetimeChecker {
     /// Get loans directly pointing to the invalidated container
     LoanSet DirectlyInvalidatedLoans =
         LoanPropagation.getLoans(InvalidatedOrigin, IOF);
-    auto IsInvalidated = [&](const LoanID &LID) {
+    auto IsInvalidated = [&](const LoanID LID) {
+      const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
       for (LoanID InvalidID : DirectlyInvalidatedLoans) {
         const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
-        const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
 
         if (InvalidL->getAccessPath() == L->getAccessPath())
           return true;
@@ -243,8 +243,15 @@ class LifetimeChecker {
         LoanPropagation.getLoans(DestroyedOrigin, DOF);
     LivenessMap Origins = LiveOrigins.getLiveOriginsAt(DOF);
 
-    auto IsDestroyed = [&DirectlyDestroyedLoans](const LoanID &LID) {
-      return DirectlyDestroyedLoans.contains(LID);
+    auto IsDestroyed = [&](const LoanID LID) {
+      const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
+      for (LoanID InvalidID : DirectlyDestroyedLoans) {
+        const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
+
+        if (InvalidL->getAccessPath() == L->getAccessPath())
+          return true;
+      }
+      return false;
     };
 
     recordWarningsForMatchingLoans(DOF->getDestroyExpr(), DOF, IsDestroyed);
@@ -265,7 +272,8 @@ class LifetimeChecker {
 
       if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) {
         if (Warning.InvalidatedByExpr) {
-          if (const CXXNewExpr *NE = dyn_cast<CXXNewExpr>(IssueExpr); NE)
+          if (const CXXNewExpr *NE = dyn_cast_or_null<CXXNewExpr>(IssueExpr);
+              NE)
             SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(),
                                            Warning.InvalidatedByExpr);
           else if (IssueExpr)
@@ -303,8 +311,7 @@ class LifetimeChecker {
   }
 
   /// Returns the declaration of a function that is visible across translation
-  /// units, if such a declaration exists and is different from the
-  /// definition.
+  /// units, if such a declaration exists and is different from the definition.
   static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
                                             SourceManager &SM) {
     if (!FD.isExternallyVisible())
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index 08405c4607f3c..ac86e17e9c222 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -570,3 +570,49 @@ void test_inference() {
   (void)ptr; // expected-note {{later used here}}
 }
 } // namespace make_unique_suggestion
+
+namespace new_allocation_suggestion {
+
+View* MakeView(const MyObj& in) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+  return new View(in);            // expected-note {{param returned here}} {{destroyed here}}
+}
+
+void test_new_allocation() {
+  View* v = MakeView(MyObj{}); // expected-warning {{object whose reference is captured does not live long enough}} \
+                               // expected-note {{destroyed here}}
+  (void)v;                     // expected-note {{later used here}}
+}
+
+struct LifetimeBoundCtor {
+  View v;
+  LifetimeBoundCtor();
+  LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]) : v(obj) {}
+};
+
+struct HasCtorField {
+  LifetimeBoundCtor* field;                                             // expected-note {{escapes to this field}}
+  HasCtorField(const MyObj& obj) : field(new LifetimeBoundCtor(obj)) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+};
+
+HasCtorField test_dangling_field_ctor() {
+  MyObj obj;
+  HasCtorField x(obj); // expected-warning {{address of stack memory is returned later}}
+  return x;            // expected-note {{returned here}}
+}
+
+struct HasSetterField {
+  LifetimeBoundCtor* field;
+  // FIXME: Does not currently warn (even without `new`)
+  void set(const MyObj& obj) {
+    field = new LifetimeBoundCtor(obj);
+  }
+};
+
+HasSetterField test_dangling_field_member_fn() {
+  MyObj obj;
+  HasSetterField x;
+  x.set(obj);
+  return x;
+}
+
+}
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index aca9538c02d40..8ec3f82a866b7 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2663,7 +2663,7 @@ int *noreturn_dead_nested(bool cond, bool cond2, int *num) {
 
 } // namespace conditional_operator_control_flow
 
-namespace heap_allocation {
+namespace new_allocation {
 
 //===----------------------------------------------------------------------===//
 // new
@@ -2698,9 +2698,10 @@ void new_int_braces() {
 
 void conditional_delete(bool cond) {
   int *p1 = new int;       // expected-warning {{allocated object does not live long enough}}
-  int *p2 = new int;
-  delete (cond ? p1 : p2); // expected-note {{freed here}}
+  int *p2 = new int;       // expected-warning {{allocated object does not live long enough}}
+  delete (cond ? p1 : p2); // expected-note 2 {{freed here}}
   (void)*p1;               // expected-note {{later used here}}
+  (void)*p2;               // expected-note {{later used here}}
 }
 
 int* foo(int* x [[clang::lifetimebound]], int* y [[clang::lifetimebound]]);
@@ -2742,8 +2743,8 @@ void new_multiview_from_mixed_scope() {
   {
     MyObj obj2;
     p = new MultiView(obj1, obj2); // expected-warning {{object whose reference is captured does not live long enough}}
-  }                                        // expected-note {{destroyed here}}
-  (void)p;                                 // expected-note {{later used here}}
+  }                                // expected-note {{destroyed here}}
+  (void)p;                         // expected-note {{later used here}}
 }
 
 void new_array_basic() {
@@ -2813,11 +2814,11 @@ void delete_pointer_propagation_use_after_free() {
   (void)(*pp)->id;      // expected-note {{later used here}}
 }
 
-void delete_array_use_after_free() {
-  int *p = new int[2]; // expected-warning {{allocated object does not live long enough}}
-  delete[] p;          // expected-note {{freed here}}
-  (void)p[1];          // expected-note {{later used here}}
-}
+// Currently crashes the program on dyn_cast to CXXNewExpr
+// void delete_param_pointer(int* x) {
+//   delete x;
+//   (void)x;
+// }
 
 void delete_nullptr_no_warning() {
   int *p = nullptr;
@@ -2868,6 +2869,13 @@ void delete_stack_object() {
   (void)obj.id;
 }
 
+void delete_stack_object_int() {
+  int obj;
+  int* p = &obj;
+  delete &obj;
+  (void)p;
+}
+
 } // namespace heap_allocation
 
 namespace method_call_uses_field_origins {

>From f3205f52d137c096ef434de3897c6d3209a59ffb Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 23:46:22 +0300
Subject: [PATCH 22/32] rename func

---
 clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h | 2 +-
 clang/lib/Analysis/LifetimeSafety/Loans.cpp                  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index f1415d8753bb0..d25c91333100c 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -91,7 +91,7 @@ class AccessPath {
     return K == Kind::PlaceholderThis ? Root.dyn_cast<const CXXMethodDecl *>()
                                       : nullptr;
   }
-  const CXXNewExpr *getAsHeapAllocation() const {
+  const CXXNewExpr *getAsNewAllocation() const {
     return K == Kind::NewAllocation ? Root.dyn_cast<const CXXNewExpr *>()
                                     : nullptr;
   }
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index e771995277ff0..41d38ea043294 100644
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
@@ -29,7 +29,7 @@ void AccessPath::dump(llvm::raw_ostream &OS) const {
     OS << "$this";
     break;
   case Kind::NewAllocation:
-    if (const auto *E = getAsHeapAllocation())
+    if (const auto *E = getAsNewAllocation())
       OS << "HeapAllocation at " << E;
     break;
   }

>From c1936fb761b40d5f54f8ec29893be8e21b885837 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 23:48:08 +0300
Subject: [PATCH 23/32] change comment

---
 clang/test/Sema/warn-lifetime-safety.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 8ec3f82a866b7..555e104b60dd0 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2876,7 +2876,7 @@ void delete_stack_object_int() {
   (void)p;
 }
 
-} // namespace heap_allocation
+} // namespace new_allocation
 
 namespace method_call_uses_field_origins {
 int GLOBAL_INT;

>From 0bdf8add6e4f61efd099aca3215a23d41b7bf697 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 20 Apr 2026 18:37:47 +0300
Subject: [PATCH 24/32] add doc, refactor func, fix test

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 67 ++++++++-----------
 clang/test/Sema/warn-lifetime-safety.cpp      | 11 +--
 2 files changed, 35 insertions(+), 43 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index cb450a4d08a30..a16300ea366ca 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -187,13 +187,35 @@ class LifetimeChecker {
     }
   }
 
-  template <typename OriginFact, typename Predicate>
+  /// Records warnings for live loans whose access paths match loan invalidated
+  /// by `InvalidatingExpr`.
+  ///
+  /// \param InvalidatingExpr The invalidating expression.
+  /// \param InvalidatedOrigin The origin being invalidated.
+  /// \param OF The fact where the invalidation happens.
+  template <typename OriginFact>
   void recordWarningsForMatchingLoans(const Expr *InvalidatingExpr,
-                                      OriginFact OF, Predicate Pred) {
+                                      OriginID InvalidatedOrigin,
+                                      OriginFact OF) {
+
+    LoanSet DirectlyInvalidatedLoans =
+        LoanPropagation.getLoans(InvalidatedOrigin, OF);
+
+    auto IsInvalidated = [&](const LoanID LID) {
+      const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
+      for (LoanID InvalidID : DirectlyInvalidatedLoans) {
+        const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
+
+        if (InvalidL->getAccessPath() == L->getAccessPath())
+          return true;
+      }
+      return false;
+    };
+
     for (auto &[OID, LiveInfo] : LiveOrigins.getLiveOriginsAt(OF)) {
       LoanSet HeldLoans = LoanPropagation.getLoans(OID, OF);
       for (LoanID HeldLoan : HeldLoans) {
-        if (!Pred(HeldLoan))
+        if (!IsInvalidated(HeldLoan))
           continue;
 
         bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
@@ -217,44 +239,13 @@ class LifetimeChecker {
   /// and checks if they hold loans that are invalidated by the operation
   /// (e.g., iterators into a vector that is being pushed to).
   void checkInvalidation(const InvalidateOriginFact *IOF) {
-    OriginID InvalidatedOrigin = IOF->getInvalidatedOrigin();
-    /// Get loans directly pointing to the invalidated container
-    LoanSet DirectlyInvalidatedLoans =
-        LoanPropagation.getLoans(InvalidatedOrigin, IOF);
-    auto IsInvalidated = [&](const LoanID LID) {
-      const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
-      for (LoanID InvalidID : DirectlyInvalidatedLoans) {
-        const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
-
-        if (InvalidL->getAccessPath() == L->getAccessPath())
-          return true;
-      }
-      return false;
-    };
-    // For each live origin, check if it holds an invalidated loan and report.
-    LivenessMap Origins = LiveOrigins.getLiveOriginsAt(IOF);
-    recordWarningsForMatchingLoans(IOF->getInvalidationExpr(), IOF,
-                                   IsInvalidated);
+    recordWarningsForMatchingLoans(IOF->getInvalidationExpr(),
+                                   IOF->getInvalidatedOrigin(), IOF);
   }
 
   void checkDestroyed(const DestroyOriginFact *DOF) {
-    OriginID DestroyedOrigin = DOF->getDestroyedOrigin();
-    LoanSet DirectlyDestroyedLoans =
-        LoanPropagation.getLoans(DestroyedOrigin, DOF);
-    LivenessMap Origins = LiveOrigins.getLiveOriginsAt(DOF);
-
-    auto IsDestroyed = [&](const LoanID LID) {
-      const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
-      for (LoanID InvalidID : DirectlyDestroyedLoans) {
-        const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
-
-        if (InvalidL->getAccessPath() == L->getAccessPath())
-          return true;
-      }
-      return false;
-    };
-
-    recordWarningsForMatchingLoans(DOF->getDestroyExpr(), DOF, IsDestroyed);
+    recordWarningsForMatchingLoans(DOF->getDestroyExpr(),
+                                   DOF->getDestroyedOrigin(), DOF);
   }
 
   void issuePendingWarnings() {
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 555e104b60dd0..890cecf932632 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2865,15 +2865,16 @@ void delete_through_pointer_field() {
 
 void delete_stack_object() {
   MyObj obj;
-  delete &obj;
-  (void)obj.id;
+  MyObj* p = &obj; // expected-warning {{object whose reference is captured is later invalidated}}
+  delete &obj;     // expected-note {{invalidated here}}
+  (void)p->id;     // expected-note {{later used here}}
 }
 
 void delete_stack_object_int() {
   int obj;
-  int* p = &obj;
-  delete &obj;
-  (void)p;
+  int* p = &obj;  // expected-warning {{object whose reference is captured is later invalidated}}
+  delete &obj;    // expected-note {{invalidated here}}
+  (void)*p;       // expected-note {{later used here}}
 }
 
 } // namespace new_allocation

>From 445d2d722cd700cf420977ec804ae89632d0d1b2 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 20 Apr 2026 18:51:22 +0300
Subject: [PATCH 25/32] add a few more tests

---
 clang/test/Sema/warn-lifetime-safety.cpp | 23 ++++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 890cecf932632..86d93ad616952 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2814,12 +2814,25 @@ void delete_pointer_propagation_use_after_free() {
   (void)(*pp)->id;      // expected-note {{later used here}}
 }
 
-// Currently crashes the program on dyn_cast to CXXNewExpr
-// void delete_param_pointer(int* x) {
-//   delete x;
-//   (void)x;
-// }
+void delete_param_pointer(int* x) { // expected-warning {{parameter is later invalidated}}
+  delete x;                         // expected-note {{invalidated here}}
+  (void)x;                          // expected-note {{later used here}}
+}
 
+// FIXME: false-negative
+struct S {
+  int *x;
+  void foo() {
+    delete x;
+    (void)x;
+  }
+};
+
+void use_innerr_origin_after_delete(MyObj* obj) { // expected-warning {{parameter is later invalidated}}
+    int* p = &obj->id;
+    delete obj;                                   // expected-note {{invalidated here}}
+    (void)*p;                                     // expected-note {{later used here}}
+}
 void delete_nullptr_no_warning() {
   int *p = nullptr;
   delete p;

>From d96c028c92d1c9d27e20bebc489899daa1893581 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 20 Apr 2026 23:21:43 +0300
Subject: [PATCH 26/32] address suggestions

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  | 23 +---------
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |  3 ++
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 45 +++++++++----------
 clang/lib/Analysis/LifetimeSafety/Dataflow.h  |  3 --
 clang/lib/Analysis/LifetimeSafety/Facts.cpp   |  7 ---
 .../LifetimeSafety/FactsGenerator.cpp         |  2 +-
 .../LifetimeSafety/LoanPropagation.cpp        |  1 -
 clang/lib/Analysis/LifetimeSafety/Loans.cpp   |  2 +-
 clang/lib/Sema/SemaLifetimeSafety.h           |  9 ++++
 .../Sema/warn-lifetime-safety-suggestions.cpp | 10 +++--
 clang/test/Sema/warn-lifetime-safety.cpp      | 17 +++----
 11 files changed, 51 insertions(+), 71 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 3bdb9d977d8c9..88b509e1b94df 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -53,10 +53,8 @@ class Fact {
     TestPoint,
     /// An origin that escapes the function scope (e.g., via return).
     OriginEscapes,
-    /// An origin is invalidated (e.g. vector resized).
+    /// An origin is invalidated (e.g. vector resized, `delete` called).
     InvalidateOrigin,
-    // An origin is explicitly destroyed (e.g. via `delete`).
-    DestroyOrigin,
     /// All loans of an origin are cleared.
     KillOrigin,
   };
@@ -303,25 +301,6 @@ class MovedOriginFact : public Fact {
             const OriginManager &OM) const override;
 };
 
-// Origin has been destroyed, e.g. via `delete`.
-class DestroyOriginFact : public Fact {
-  OriginID OID;
-  const Expr *DestroyExpr;
-
-public:
-  static bool classof(const Fact *F) {
-    return F->getKind() == Kind::DestroyOrigin;
-  }
-
-  DestroyOriginFact(OriginID OID, const Expr *DestroyExpr)
-      : Fact(Kind::DestroyOrigin), OID(OID), DestroyExpr(DestroyExpr) {}
-
-  OriginID getDestroyedOrigin() const { return OID; }
-  const Expr *getDestroyExpr() const { return DestroyExpr; }
-  void dump(llvm::raw_ostream &OS, const LoanManager &,
-            const OriginManager &OM) const override;
-};
-
 /// 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 {
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index f47ae248dd884..370520db0c033 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -73,6 +73,9 @@ class LifetimeSafetySemaHelper {
   virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
                                   const Expr *FreedExpr) {}
 
+  virtual void reportUseAfterFree(const ParmVarDecl *IssueExpr,
+                                  const Expr *UseExpr, const Expr *FreedExpr) {}
+
   virtual void reportDanglingField(const Expr *IssueExpr,
                                    const FieldDecl *Field,
                                    const Expr *MovedExpr,
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index a16300ea366ca..904f27d6391d2 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -98,8 +98,6 @@ class LifetimeChecker {
           checkInvalidation(IOF);
         else if (const auto *OEF = F->getAs<OriginEscapesFact>())
           checkAnnotations(OEF);
-        else if (const auto *DOF = F->getAs<DestroyOriginFact>())
-          checkDestroyed(DOF);
     issuePendingWarnings();
     suggestAnnotations();
     reportNoescapeViolations();
@@ -187,33 +185,31 @@ class LifetimeChecker {
     }
   }
 
-  /// Records warnings for live loans whose access paths match loan invalidated
-  /// by `InvalidatingExpr`.
-  ///
+  /// Records warnings for live loans whose access paths match loans directly
+  /// held by the `InvalidatedOrigin`.
   /// \param InvalidatingExpr The invalidating expression.
   /// \param InvalidatedOrigin The origin being invalidated.
-  /// \param OF The fact where the invalidation happens.
-  template <typename OriginFact>
-  void recordWarningsForMatchingLoans(const Expr *InvalidatingExpr,
-                                      OriginID InvalidatedOrigin,
-                                      OriginFact OF) {
+  /// \param PP The fact where the invalidation happens.
+  void checkLiveLoansForInvalidation(const Expr *InvalidatingExpr,
+                                     OriginID InvalidatedOrigin,
+                                     ProgramPoint PP) {
 
     LoanSet DirectlyInvalidatedLoans =
-        LoanPropagation.getLoans(InvalidatedOrigin, OF);
+        LoanPropagation.getLoans(InvalidatedOrigin, PP);
 
     auto IsInvalidated = [&](const LoanID LID) {
       const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
       for (LoanID InvalidID : DirectlyInvalidatedLoans) {
         const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
-
         if (InvalidL->getAccessPath() == L->getAccessPath())
           return true;
       }
       return false;
     };
 
-    for (auto &[OID, LiveInfo] : LiveOrigins.getLiveOriginsAt(OF)) {
-      LoanSet HeldLoans = LoanPropagation.getLoans(OID, OF);
+    // For each live origin, check if it holds an invalidated loan and report.
+    for (auto &[OID, LiveInfo] : LiveOrigins.getLiveOriginsAt(PP)) {
+      LoanSet HeldLoans = LoanPropagation.getLoans(OID, PP);
       for (LoanID HeldLoan : HeldLoans) {
         if (!IsInvalidated(HeldLoan))
           continue;
@@ -239,13 +235,8 @@ class LifetimeChecker {
   /// and checks if they hold loans that are invalidated by the operation
   /// (e.g., iterators into a vector that is being pushed to).
   void checkInvalidation(const InvalidateOriginFact *IOF) {
-    recordWarningsForMatchingLoans(IOF->getInvalidationExpr(),
-                                   IOF->getInvalidatedOrigin(), IOF);
-  }
-
-  void checkDestroyed(const DestroyOriginFact *DOF) {
-    recordWarningsForMatchingLoans(DOF->getDestroyExpr(),
-                                   DOF->getDestroyedOrigin(), DOF);
+    checkLiveLoansForInvalidation(IOF->getInvalidationExpr(),
+                                  IOF->getInvalidatedOrigin(), IOF);
   }
 
   void issuePendingWarnings() {
@@ -263,10 +254,14 @@ class LifetimeChecker {
 
       if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) {
         if (Warning.InvalidatedByExpr) {
-          if (const CXXNewExpr *NE = dyn_cast_or_null<CXXNewExpr>(IssueExpr);
-              NE)
-            SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(),
-                                           Warning.InvalidatedByExpr);
+          if (const CXXDeleteExpr *DE =
+                  dyn_cast<CXXDeleteExpr>(Warning.InvalidatedByExpr))
+            if (InvalidatedPVD)
+              SemaHelper->reportUseAfterFree(InvalidatedPVD, UF->getUseExpr(),
+                                             Warning.InvalidatedByExpr);
+            else
+              SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(),
+                                             Warning.InvalidatedByExpr);
           else if (IssueExpr)
             // Use-after-invalidation of an object on stack.
             SemaHelper->reportUseAfterInvalidation(IssueExpr, UF->getUseExpr(),
diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
index 51517ae279a4a..fc3049c8bec84 100644
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
@@ -180,8 +180,6 @@ class DataflowAnalysis {
       return D->transfer(In, *F->getAs<TestPointFact>());
     case Fact::Kind::InvalidateOrigin:
       return D->transfer(In, *F->getAs<InvalidateOriginFact>());
-    case Fact::Kind::DestroyOrigin:
-      return D->transfer(In, *F->getAs<DestroyOriginFact>());
     case Fact::Kind::KillOrigin:
       return D->transfer(In, *F->getAs<KillOriginFact>());
     }
@@ -197,7 +195,6 @@ class DataflowAnalysis {
   Lattice transfer(Lattice In, const UseFact &) { return In; }
   Lattice transfer(Lattice In, const TestPointFact &) { return In; }
   Lattice transfer(Lattice In, const InvalidateOriginFact &) { return In; }
-  Lattice transfer(Lattice In, const DestroyOriginFact &) { return In; }
   Lattice transfer(Lattice In, const KillOriginFact &) { return In; }
 };
 } // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 8381ec0a765d9..3d7fbcdacc830 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -98,13 +98,6 @@ void InvalidateOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
   OS << ")\n";
 }
 
-void DestroyOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
-                             const OriginManager &OM) const {
-  OS << "DestroyOrigin (";
-  OM.dump(getDestroyedOrigin(), OS);
-  OS << ")\n";
-}
-
 void TestPointFact::dump(llvm::raw_ostream &OS, const LoanManager &,
                          const OriginManager &) const {
   OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 33ffb61567b07..e78f257d99aa4 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -644,7 +644,7 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
 void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
   OriginList *List = getOriginsList(*DE->getArgument());
   CurrentBlockFacts.push_back(
-      FactMgr.createFact<DestroyOriginFact>(List->getOuterOriginID(), DE));
+      FactMgr.createFact<InvalidateOriginFact>(List->getOuterOriginID(), DE));
 }
 
 bool FactsGenerator::escapesViaReturn(OriginID OID) const {
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index 67c3a6c9dd5e4..adbc0458516e1 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -71,7 +71,6 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
       case Fact::Kind::Expire:
       case Fact::Kind::TestPoint:
       case Fact::Kind::InvalidateOrigin:
-      case Fact::Kind::DestroyOrigin:
         break;
       }
     }
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index 41d38ea043294..c0c8d0b283f8b 100644
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
@@ -30,7 +30,7 @@ void AccessPath::dump(llvm::raw_ostream &OS) const {
     break;
   case Kind::NewAllocation:
     if (const auto *E = getAsNewAllocation())
-      OS << "HeapAllocation at " << E;
+      OS << "NewAllocation at " << E;
     break;
   }
 }
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index b402947557749..9a1e146850d00 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -67,6 +67,15 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
     S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
         << UseExpr->getSourceRange();
   }
+  void reportUseAfterFree(const ParmVarDecl *IssueDecl, const Expr *UseExpr,
+                          const Expr *FreedExpr) override {
+    S.Diag(IssueDecl->getBeginLoc(), diag::warn_lifetime_safety_use_after_free)
+        << IssueDecl->getSourceRange();
+    S.Diag(FreedExpr->getExprLoc(), diag::note_lifetime_safety_freed_here)
+        << FreedExpr->getSourceRange();
+    S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
+        << UseExpr->getSourceRange();
+  }
 
   void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr,
                             const Expr *MovedExpr,
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index ac86e17e9c222..bdcfe728f6776 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -601,11 +601,15 @@ HasCtorField test_dangling_field_ctor() {
 }
 
 struct HasSetterField {
-  LifetimeBoundCtor* field;
-  // FIXME: Does not currently warn (even without `new`)
+  LifetimeBoundCtor* field; // expected-note {{this field dangles}}
+  // FIXME: Does not currently suggest `lifetime_capture_by(this)` (even without `new`)
   void set(const MyObj& obj) {
     field = new LifetimeBoundCtor(obj);
   }
+  void reset() {
+    MyObj obj;
+    field = new LifetimeBoundCtor(obj); // expected-warning {{address of stack memory escapes to a field}}
+  }
 };
 
 HasSetterField test_dangling_field_member_fn() {
@@ -615,4 +619,4 @@ HasSetterField test_dangling_field_member_fn() {
   return x;
 }
 
-}
+} // namespace new_allocation_suggestion
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 86d93ad616952..e4e3a64936825 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2814,8 +2814,8 @@ void delete_pointer_propagation_use_after_free() {
   (void)(*pp)->id;      // expected-note {{later used here}}
 }
 
-void delete_param_pointer(int* x) { // expected-warning {{parameter is later invalidated}}
-  delete x;                         // expected-note {{invalidated here}}
+void delete_param_pointer(int* x) { // expected-warning {{allocated object does not live long enough}}
+  delete x;                         // expected-note {{freed here}}
   (void)x;                          // expected-note {{later used here}}
 }
 
@@ -2828,11 +2828,12 @@ struct S {
   }
 };
 
-void use_innerr_origin_after_delete(MyObj* obj) { // expected-warning {{parameter is later invalidated}}
+void use_inner_origin_after_delete(MyObj* obj) { // expected-warning {{allocated object does not live long enough}}
     int* p = &obj->id;
-    delete obj;                                   // expected-note {{invalidated here}}
+    delete obj;                                   // expected-note {{freed here}}
     (void)*p;                                     // expected-note {{later used here}}
 }
+
 void delete_nullptr_no_warning() {
   int *p = nullptr;
   delete p;
@@ -2878,15 +2879,15 @@ void delete_through_pointer_field() {
 
 void delete_stack_object() {
   MyObj obj;
-  MyObj* p = &obj; // expected-warning {{object whose reference is captured is later invalidated}}
-  delete &obj;     // expected-note {{invalidated here}}
+  MyObj* p = &obj; // expected-warning {{allocated object does not live long enough}}
+  delete &obj;     // expected-note {{freed here}}
   (void)p->id;     // expected-note {{later used here}}
 }
 
 void delete_stack_object_int() {
   int obj;
-  int* p = &obj;  // expected-warning {{object whose reference is captured is later invalidated}}
-  delete &obj;    // expected-note {{invalidated here}}
+  int* p = &obj;  // expected-warning {{allocated object does not live long enough}}
+  delete &obj;    // expected-note {{freed here}}
   (void)*p;       // expected-note {{later used here}}
 }
 

>From a41fecde425abe3711d4b7c918c82a0714b71f5f Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 20 Apr 2026 23:37:50 +0300
Subject: [PATCH 27/32] change dyn_cast to isa

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 904f27d6391d2..6dc535e66761a 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -254,8 +254,7 @@ class LifetimeChecker {
 
       if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) {
         if (Warning.InvalidatedByExpr) {
-          if (const CXXDeleteExpr *DE =
-                  dyn_cast<CXXDeleteExpr>(Warning.InvalidatedByExpr))
+          if (isa<CXXDeleteExpr>(Warning.InvalidatedByExpr))
             if (InvalidatedPVD)
               SemaHelper->reportUseAfterFree(InvalidatedPVD, UF->getUseExpr(),
                                              Warning.InvalidatedByExpr);

>From 6c080336b94a095dcf7d29f3c892694a7110f9d4 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Tue, 21 Apr 2026 17:29:11 +0300
Subject: [PATCH 28/32] address review comments

---
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |  6 --
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +-
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 76 +++++++------------
 .../LifetimeSafety/FactsGenerator.cpp         |  2 +-
 clang/lib/Sema/SemaLifetimeSafety.h           | 44 +++++------
 clang/test/Sema/warn-lifetime-safety.cpp      |  4 +-
 6 files changed, 49 insertions(+), 85 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 370520db0c033..d20ac87a7c8d9 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -70,12 +70,6 @@ class LifetimeSafetySemaHelper {
                                     const Expr *MovedExpr,
                                     SourceLocation ExpiryLoc) {}
 
-  virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
-                                  const Expr *FreedExpr) {}
-
-  virtual void reportUseAfterFree(const ParmVarDecl *IssueExpr,
-                                  const Expr *UseExpr, const Expr *FreedExpr) {}
-
   virtual void reportDanglingField(const Expr *IssueExpr,
                                    const FieldDecl *Field,
                                    const Expr *MovedExpr,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8f76cf635d95b..974458f20fb22 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10963,7 +10963,7 @@ def warn_lifetime_safety_use_after_scope_moved : Warning<
    InGroup<LifetimeSafetyUseAfterScopeMoved>, DefaultIgnore;
 
 def warn_lifetime_safety_use_after_free : Warning<
-    "allocated object does not live long enough">,
+    "%select{allocated object|parameter}0 does not live long enough">,
     InGroup<LifetimeSafetyUseAfterFree>, DefaultIgnore;
 
 def warn_lifetime_safety_return_stack_addr
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 6dc535e66761a..8164eb15cb6cb 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -185,20 +185,17 @@ class LifetimeChecker {
     }
   }
 
-  /// Records warnings for live loans whose access paths match loans directly
-  /// held by the `InvalidatedOrigin`.
-  /// \param InvalidatingExpr The invalidating expression.
-  /// \param InvalidatedOrigin The origin being invalidated.
-  /// \param PP The fact where the invalidation happens.
-  void checkLiveLoansForInvalidation(const Expr *InvalidatingExpr,
-                                     OriginID InvalidatedOrigin,
-                                     ProgramPoint PP) {
-
+  /// Checks for use-after-invalidation errors when a container is modified.
+  ///
+  /// This method identifies origins that are live at the point of invalidation
+  /// and checks if they hold loans that are invalidated by the operation
+  /// (e.g., iterators into a vector that is being pushed to).
+  void checkInvalidation(const InvalidateOriginFact *IOF) {
+    OriginID InvalidatedOrigin = IOF->getInvalidatedOrigin();
+    /// Get loans directly pointing to the invalidated container
     LoanSet DirectlyInvalidatedLoans =
-        LoanPropagation.getLoans(InvalidatedOrigin, PP);
-
-    auto IsInvalidated = [&](const LoanID LID) {
-      const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
+        LoanPropagation.getLoans(InvalidatedOrigin, IOF);
+    auto IsInvalidated = [&](const Loan *L) {
       for (LoanID InvalidID : DirectlyInvalidatedLoans) {
         const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
         if (InvalidL->getAccessPath() == L->getAccessPath())
@@ -206,39 +203,27 @@ class LifetimeChecker {
       }
       return false;
     };
-
     // For each live origin, check if it holds an invalidated loan and report.
-    for (auto &[OID, LiveInfo] : LiveOrigins.getLiveOriginsAt(PP)) {
-      LoanSet HeldLoans = LoanPropagation.getLoans(OID, PP);
-      for (LoanID HeldLoan : HeldLoans) {
-        if (!IsInvalidated(HeldLoan))
-          continue;
-
-        bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
-        bool LastDomination =
-            FinalWarningsMap.lookup(HeldLoan).CausingFactDominatesExpiry;
-        if (!LastDomination) {
-          FinalWarningsMap[HeldLoan] = {
-              /*ExpiryLoc=*/{},
-              /*CausingFact=*/LiveInfo.CausingFact,
-              /*MovedExpr=*/nullptr,
-              /*InvalidatedByExpr=*/InvalidatingExpr,
-              /*CausingFactDominatesExpiry=*/CurDomination};
+    LivenessMap Origins = LiveOrigins.getLiveOriginsAt(IOF);
+    for (auto &[OID, LiveInfo] : Origins) {
+      LoanSet HeldLoans = LoanPropagation.getLoans(OID, IOF);
+      for (LoanID LiveLoanID : HeldLoans)
+        if (IsInvalidated(FactMgr.getLoanMgr().getLoan(LiveLoanID))) {
+          bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
+          bool LastDomination =
+              FinalWarningsMap.lookup(LiveLoanID).CausingFactDominatesExpiry;
+          if (!LastDomination) {
+            FinalWarningsMap[LiveLoanID] = {
+                /*ExpiryLoc=*/{},
+                /*CausingFact=*/LiveInfo.CausingFact,
+                /*MovedExpr=*/nullptr,
+                /*InvalidatedByExpr=*/IOF->getInvalidationExpr(),
+                /*CausingFactDominatesExpiry=*/CurDomination};
+          }
         }
-      }
     }
   }
 
-  /// Checks for use-after-invalidation errors when a container is modified.
-  ///
-  /// This method identifies origins that are live at the point of invalidation
-  /// and checks if they hold loans that are invalidated by the operation
-  /// (e.g., iterators into a vector that is being pushed to).
-  void checkInvalidation(const InvalidateOriginFact *IOF) {
-    checkLiveLoansForInvalidation(IOF->getInvalidationExpr(),
-                                  IOF->getInvalidatedOrigin(), IOF);
-  }
-
   void issuePendingWarnings() {
     if (!SemaHelper)
       return;
@@ -254,14 +239,7 @@ class LifetimeChecker {
 
       if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) {
         if (Warning.InvalidatedByExpr) {
-          if (isa<CXXDeleteExpr>(Warning.InvalidatedByExpr))
-            if (InvalidatedPVD)
-              SemaHelper->reportUseAfterFree(InvalidatedPVD, UF->getUseExpr(),
-                                             Warning.InvalidatedByExpr);
-            else
-              SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(),
-                                             Warning.InvalidatedByExpr);
-          else if (IssueExpr)
+          if (IssueExpr)
             // Use-after-invalidation of an object on stack.
             SemaHelper->reportUseAfterInvalidation(IssueExpr, UF->getUseExpr(),
                                                    Warning.InvalidatedByExpr);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index e78f257d99aa4..019d28db2afdb 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -94,7 +94,7 @@ static const Loan *createLoan(FactManager &FactMgr,
   return FactMgr.getLoanMgr().createLoan(Path, MTE);
 }
 
-/// Creates a loan for the heap allocation
+/// Creates a loan for a `new` allocation
 /// \param NE The CXXNewExpr that represents the allocation
 /// \return The new Loan on success, nullptr otherwise
 static const Loan *createLoan(FactManager &FactMgr, const CXXNewExpr *NE) {
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index 9a1e146850d00..92e7b5cf14ae5 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -58,25 +58,6 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
         << UseExpr->getSourceRange();
   }
 
-  void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
-                          const Expr *FreedExpr) override {
-    S.Diag(IssueExpr->getExprLoc(), diag::warn_lifetime_safety_use_after_free)
-        << IssueExpr->getSourceRange();
-    S.Diag(FreedExpr->getExprLoc(), diag::note_lifetime_safety_freed_here)
-        << FreedExpr->getSourceRange();
-    S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
-        << UseExpr->getSourceRange();
-  }
-  void reportUseAfterFree(const ParmVarDecl *IssueDecl, const Expr *UseExpr,
-                          const Expr *FreedExpr) override {
-    S.Diag(IssueDecl->getBeginLoc(), diag::warn_lifetime_safety_use_after_free)
-        << IssueDecl->getSourceRange();
-    S.Diag(FreedExpr->getExprLoc(), diag::note_lifetime_safety_freed_here)
-        << FreedExpr->getSourceRange();
-    S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
-        << UseExpr->getSourceRange();
-  }
-
   void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr,
                             const Expr *MovedExpr,
                             SourceLocation ExpiryLoc) override {
@@ -130,21 +111,32 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
 
   void reportUseAfterInvalidation(const Expr *IssueExpr, const Expr *UseExpr,
                                   const Expr *InvalidationExpr) override {
-    S.Diag(IssueExpr->getExprLoc(), diag::warn_lifetime_safety_invalidation)
+    auto WarnDiag = isa<CXXDeleteExpr>(InvalidationExpr)
+                        ? diag::warn_lifetime_safety_use_after_free
+                        : diag::warn_lifetime_safety_invalidation;
+    auto UseDiag = isa<CXXDeleteExpr>(InvalidationExpr)
+                       ? diag::note_lifetime_safety_freed_here
+                       : diag::note_lifetime_safety_invalidated_here;
+    S.Diag(IssueExpr->getExprLoc(), WarnDiag)
         << false << IssueExpr->getSourceRange();
-    S.Diag(InvalidationExpr->getExprLoc(),
-           diag::note_lifetime_safety_invalidated_here)
+    S.Diag(InvalidationExpr->getExprLoc(), UseDiag)
         << InvalidationExpr->getSourceRange();
     S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
         << UseExpr->getSourceRange();
   }
   void reportUseAfterInvalidation(const ParmVarDecl *PVD, const Expr *UseExpr,
                                   const Expr *InvalidationExpr) override {
-    S.Diag(PVD->getSourceRange().getBegin(),
-           diag::warn_lifetime_safety_invalidation)
+
+    auto WarnDiag = isa<CXXDeleteExpr>(InvalidationExpr)
+                        ? diag::warn_lifetime_safety_use_after_free
+                        : diag::warn_lifetime_safety_invalidation;
+    auto UseDiag = isa<CXXDeleteExpr>(InvalidationExpr)
+                       ? diag::note_lifetime_safety_freed_here
+                       : diag::note_lifetime_safety_invalidated_here;
+
+    S.Diag(PVD->getSourceRange().getBegin(), WarnDiag)
         << true << PVD->getSourceRange();
-    S.Diag(InvalidationExpr->getExprLoc(),
-           diag::note_lifetime_safety_invalidated_here)
+    S.Diag(InvalidationExpr->getExprLoc(), UseDiag)
         << InvalidationExpr->getSourceRange();
     S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
         << UseExpr->getSourceRange();
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index e4e3a64936825..bb87b13a71c49 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2814,7 +2814,7 @@ void delete_pointer_propagation_use_after_free() {
   (void)(*pp)->id;      // expected-note {{later used here}}
 }
 
-void delete_param_pointer(int* x) { // expected-warning {{allocated object does not live long enough}}
+void delete_param_pointer(int* x) { // expected-warning {{parameter does not live long enough}}
   delete x;                         // expected-note {{freed here}}
   (void)x;                          // expected-note {{later used here}}
 }
@@ -2828,7 +2828,7 @@ struct S {
   }
 };
 
-void use_inner_origin_after_delete(MyObj* obj) { // expected-warning {{allocated object does not live long enough}}
+void use_inner_origin_after_delete(MyObj* obj) { // expected-warning {{parameter does not live long enough}}
     int* p = &obj->id;
     delete obj;                                   // expected-note {{freed here}}
     (void)*p;                                     // expected-note {{later used here}}

>From aca8d3a1b2ca049edeed086e0b2e1be2d04071e6 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Tue, 21 Apr 2026 17:33:30 +0300
Subject: [PATCH 29/32] document fixme

---
 clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 019d28db2afdb..a227532f1d8b0 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -635,8 +635,8 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
   if (!NewList)
     return;
 
-  // FIXME: Once arrays are handled properly, remove the check for Init is
-  // non-null.
+  // FIXME: OriginList is null for `new[]` initializers. Remove this `Init`
+  // check once array origins are supported.
   if (OriginList *Init = getOriginsList(*NE->getInitializer()); Init)
     flow(NewList, Init, true);
 }

>From 74d068e8bff20b5ac0577d83bcb2f9ffa65d6041 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Tue, 21 Apr 2026 17:34:56 +0300
Subject: [PATCH 30/32] change comment

---
 clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index a227532f1d8b0..33da608385dcd 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -94,7 +94,7 @@ static const Loan *createLoan(FactManager &FactMgr,
   return FactMgr.getLoanMgr().createLoan(Path, MTE);
 }
 
-/// Creates a loan for a `new` allocation
+/// Creates a loan for an allocation through 'new'
 /// \param NE The CXXNewExpr that represents the allocation
 /// \return The new Loan on success, nullptr otherwise
 static const Loan *createLoan(FactManager &FactMgr, const CXXNewExpr *NE) {

>From 88b510839d31ece07c2e767244af1c61d9a362f0 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Thu, 23 Apr 2026 18:20:58 +0300
Subject: [PATCH 31/32] add fix for crash on allocating void*

---
 clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 33da608385dcd..efdb1a1691ae3 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -632,7 +632,7 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
 
   NewList = NewList->peelOuterOrigin();
 
-  if (!NewList)
+  if (!NewList || !NE->getInitializer())
     return;
 
   // FIXME: OriginList is null for `new[]` initializers. Remove this `Init`

>From ace9bcb5618a38a387d27bf4e13f158a4f0e09f8 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Thu, 23 Apr 2026 18:24:40 +0300
Subject: [PATCH 32/32] add regeression test

---
 clang/test/Sema/warn-lifetime-safety.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index bb87b13a71c49..fc2aef6024a42 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2891,6 +2891,11 @@ void delete_stack_object_int() {
   (void)*p;       // expected-note {{later used here}}
 }
 
+void allocate_void_ptr() {
+    void** v = new void*;
+    delete v;
+}
+
 } // namespace new_allocation
 
 namespace method_call_uses_field_origins {



More information about the cfe-commits mailing list