[clang] [LifetimeSafety] Detect use-after-return (PR #165370)

Kashika Akhouri via cfe-commits cfe-commits at lists.llvm.org
Tue Nov 18 00:23:09 PST 2025


https://github.com/kashika0112 updated https://github.com/llvm/llvm-project/pull/165370

>From 2ab23aa98033c9f14cc6910786b6041c125b0726 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 01/12] Adding use-after-return in Lifetime Analysis

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  |  19 +
 .../Analyses/LifetimeSafety/FactsGenerator.h  |   1 +
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |   5 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   1 +
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  77 +++-
 clang/lib/Analysis/LifetimeSafety/Dataflow.h  |   3 +
 clang/lib/Analysis/LifetimeSafety/Facts.cpp   |   8 +
 .../LifetimeSafety/FactsGenerator.cpp         |   6 +-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 415 +++++++++---------
 9 files changed, 329 insertions(+), 206 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 6a90aeb01e638..712e1d42e2966 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -44,6 +44,8 @@ class Fact {
     Use,
     /// A marker for a specific point in the code, for testing.
     TestPoint,
+    OriginEscapes,
+    // An origin that stores a loan escapes the function.
   };
 
 private:
@@ -142,6 +144,23 @@ class ReturnOfOriginFact : public Fact {
             const OriginManager &OM) const override;
 };
 
+class OriginEscapesFact : public Fact {
+  OriginID OID;
+  const Stmt *EscapeSource;
+
+public:
+  static bool classof(const Fact *F) {
+    return F->getKind() == Kind::OriginEscapes;
+  }
+
+  OriginEscapesFact(OriginID OID, const Stmt *Source)
+      : Fact(Kind::OriginEscapes), OID(OID), EscapeSource(Source) {}
+  OriginID getEscapedOriginID() const { return OID; }
+  const Stmt *getEscapeSource() const { return EscapeSource; }
+  void dump(llvm::raw_ostream &OS, const LoanManager &,
+            const OriginManager &OM) const override;
+};
+
 class UseFact : public Fact {
   const Expr *UseExpr;
   // True if this use is a write operation (e.g., left-hand side of assignment).
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 5e58abee2bbb3..ec3c130bb6c66 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -93,6 +93,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   FactManager &FactMgr;
   AnalysisDeclContext &AC;
   llvm::SmallVector<Fact *> CurrentBlockFacts;
+  llvm::SmallVector<Fact *> EscapesInCurrentBlock;
   // To distinguish between reads and writes for use-after-free checks, this map
   // stores the `UseFact` for each `DeclRefExpr`. We initially identify all
   // `DeclRefExpr`s as "read" uses. When an assignment is processed, the use
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 91ffbb169f947..f5b1b3d9b6564 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -42,6 +42,11 @@ class LifetimeSafetyReporter {
   virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
                                   SourceLocation FreeLoc,
                                   Confidence Confidence) {}
+
+  virtual void reportUseAfterReturn(const Expr *IssueExpr,
+                                    const Stmt *EscapeStmt,
+                                    SourceLocation ExpiryLoc,
+                                    Confidence Confidence) {}
 };
 
 /// The main entry point for the analysis.
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 5ff4cc4b833d9..6ea5e34cab291 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10730,6 +10730,7 @@ def warn_lifetime_safety_loan_expires_strict : Warning<
    InGroup<LifetimeSafetyStrict>, DefaultIgnore;
 def note_lifetime_safety_used_here : Note<"later used here">;
 def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
+def note_lifetime_safety_returned_here : Note<"returned here">;
 
 // For non-floating point, expressions of the form x == x or x != x
 // should result in a warning, since these always evaluate to a constant.
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index c443c3a5d4f9b..ae5c62ef04d72 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -21,6 +21,7 @@
 #include "clang/Analysis/AnalysisDeclContext.h"
 #include "clang/Basic/SourceLocation.h"
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallSet.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/TimeProfiler.h"
 
@@ -61,10 +62,23 @@ class LifetimeChecker {
                   AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter)
       : LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM),
         Reporter(Reporter) {
-    for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
-      for (const Fact *F : FactMgr.getFacts(B))
-        if (const auto *EF = F->getAs<ExpireFact>())
+    for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) {
+      llvm::SmallVector<const ExpireFact *> BlockExpires;
+      llvm::SmallVector<const OriginEscapesFact *> BlockEscapes;
+      for (const Fact *F : FactMgr.getFacts(B)) {
+        if (const auto *EF = F->getAs<ExpireFact>()) {
           checkExpiry(EF);
+          BlockExpires.push_back(EF);
+        } else if (const auto *OEF = F->getAs<OriginEscapesFact>()) {
+          BlockEscapes.push_back(OEF);
+        }
+      }
+      if (Reporter) {
+        for (const OriginEscapesFact *OEF : BlockEscapes) {
+          checkEscape(OEF, BlockExpires);
+        }
+      }
+    }
     issuePendingWarnings();
   }
 
@@ -106,6 +120,63 @@ class LifetimeChecker {
                                      /*ConfidenceLevel=*/CurConfidence};
   }
 
+  void checkEscape(const OriginEscapesFact *OEF,
+                   llvm::ArrayRef<const ExpireFact *> BlockExpires) {
+
+    if (!Reporter) {
+      return;
+    }
+
+    OriginID returnedOID = OEF->getEscapedOriginID();
+    ProgramPoint PP = OEF;
+
+    LoanSet HeldLoans = LoanPropagation.getLoans(returnedOID, PP);
+    if (HeldLoans.isEmpty()) {
+      return;
+    }
+
+    llvm::SmallSet<LoanID, 4> ExpiredLoansInBlock;
+    llvm::DenseMap<LoanID, SourceLocation> ExpiryLocs;
+
+    for (const ExpireFact *EF : BlockExpires) {
+      ExpiredLoansInBlock.insert(EF->getLoanID());
+      ExpiryLocs[EF->getLoanID()] = EF->getExpiryLoc();
+    }
+
+    bool hasExpiredDependency = false;
+    bool allHeldLoansExpired = true;
+    LoanID exampleExpiredLoan = LoanID();
+
+    for (LoanID heldLoan : HeldLoans) {
+      if (ExpiredLoansInBlock.count(heldLoan)) {
+        hasExpiredDependency = true;
+        if (exampleExpiredLoan.Value == LoanID().Value) {
+          exampleExpiredLoan = heldLoan;
+        }
+      } else {
+        allHeldLoansExpired = false;
+      }
+    }
+
+    if (!hasExpiredDependency) {
+      return;
+    }
+
+    Confidence FinalConfidence;
+    if (allHeldLoansExpired) {
+      FinalConfidence = Confidence::Definite;
+    } else {
+      FinalConfidence = Confidence::Maybe;
+    }
+
+    const Loan &L = FactMgr.getLoanMgr().getLoan(exampleExpiredLoan);
+    SourceLocation ExpiryLoc = ExpiryLocs[exampleExpiredLoan];
+    const Stmt *EscapeSource = OEF->getEscapeSource();
+
+    Reporter->reportUseAfterReturn(L.IssueExpr, EscapeSource, ExpiryLoc,
+                                   FinalConfidence);
+  }
+
   void issuePendingWarnings() {
     if (!Reporter)
       return;
diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
index 2f7bcb6e5dc81..37720ffa03618 100644
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
@@ -168,6 +168,8 @@ class DataflowAnalysis {
       return D->transfer(In, *F->getAs<OriginFlowFact>());
     case Fact::Kind::ReturnOfOrigin:
       return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
+    case Fact::Kind::OriginEscapes:
+      return D->transfer(In, *F->getAs<OriginEscapesFact>());
     case Fact::Kind::Use:
       return D->transfer(In, *F->getAs<UseFact>());
     case Fact::Kind::TestPoint:
@@ -181,6 +183,7 @@ class DataflowAnalysis {
   Lattice transfer(Lattice In, const ExpireFact &) { return In; }
   Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
   Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
+  Lattice transfer(Lattice In, const OriginEscapesFact &) { return In; }
   Lattice transfer(Lattice In, const UseFact &) { return In; }
   Lattice transfer(Lattice In, const TestPointFact &) { return In; }
 };
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 1aea64f864367..29c75959ba0fe 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -50,6 +50,14 @@ void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
   OS << ")\n";
 }
 
+void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+                              const OriginManager &OM) const {
+  OS << "OriginEscapes (";
+  OM.dump(getEscapedOriginID(), OS);
+  OS << ")\n";
+}
+
+
 void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
                    const OriginManager &OM) const {
   OS << "Use (";
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 9b68de107e314..762ed07eb5bb8 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -58,6 +58,7 @@ void FactsGenerator::run() {
   // initializations and destructions are processed in the correct sequence.
   for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
     CurrentBlockFacts.clear();
+    EscapesInCurrentBlock.clear();
     for (unsigned I = 0; I < Block->size(); ++I) {
       const CFGElement &Element = Block->Elements[I];
       if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
@@ -66,6 +67,8 @@ void FactsGenerator::run() {
                    Element.getAs<CFGAutomaticObjDtor>())
         handleDestructor(*DtorOpt);
     }
+    CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
+                             EscapesInCurrentBlock.end());
     FactMgr.addBlockFacts(Block, CurrentBlockFacts);
   }
 }
@@ -166,7 +169,8 @@ void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) {
   if (const Expr *RetExpr = RS->getRetValue()) {
     if (hasOrigin(RetExpr)) {
       OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr);
-      CurrentBlockFacts.push_back(FactMgr.createFact<ReturnOfOriginFact>(OID));
+      EscapesInCurrentBlock.push_back(
+          FactMgr.createFact<OriginEscapesFact>(OID, RS));
     }
   }
 }
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 140b709dbb651..879793575e0b4 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -65,61 +65,61 @@ using namespace clang;
 //===----------------------------------------------------------------------===//
 
 namespace {
-  class UnreachableCodeHandler : public reachable_code::Callback {
-    Sema &S;
-    SourceRange PreviousSilenceableCondVal;
-
-  public:
-    UnreachableCodeHandler(Sema &s) : S(s) {}
-
-    void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L,
-                           SourceRange SilenceableCondVal, SourceRange R1,
-                           SourceRange R2, bool HasFallThroughAttr) override {
-      // If the diagnosed code is `[[fallthrough]];` and
-      // `-Wunreachable-code-fallthrough` is  enabled, suppress `code will never
-      // be executed` warning to avoid generating diagnostic twice
-      if (HasFallThroughAttr &&
-          !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr,
-                                        SourceLocation()))
-        return;
+class UnreachableCodeHandler : public reachable_code::Callback {
+  Sema &S;
+  SourceRange PreviousSilenceableCondVal;
 
-      // Avoid reporting multiple unreachable code diagnostics that are
-      // triggered by the same conditional value.
+public:
+  UnreachableCodeHandler(Sema &s) : S(s) {}
+
+  void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L,
+                         SourceRange SilenceableCondVal, SourceRange R1,
+                         SourceRange R2, bool HasFallThroughAttr) override {
+    // If the diagnosed code is `[[fallthrough]];` and
+    // `-Wunreachable-code-fallthrough` is  enabled, suppress `code will never
+    // be executed` warning to avoid generating diagnostic twice
+    if (HasFallThroughAttr &&
+        !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr,
+                                      SourceLocation()))
+      return;
+
+    // Avoid reporting multiple unreachable code diagnostics that are
+    // triggered by the same conditional value.
       if (PreviousSilenceableCondVal.isValid() &&
           SilenceableCondVal.isValid() &&
-          PreviousSilenceableCondVal == SilenceableCondVal)
-        return;
-      PreviousSilenceableCondVal = SilenceableCondVal;
+        PreviousSilenceableCondVal == SilenceableCondVal)
+      return;
+    PreviousSilenceableCondVal = SilenceableCondVal;
 
-      unsigned diag = diag::warn_unreachable;
-      switch (UK) {
-        case reachable_code::UK_Break:
-          diag = diag::warn_unreachable_break;
-          break;
-        case reachable_code::UK_Return:
-          diag = diag::warn_unreachable_return;
-          break;
-        case reachable_code::UK_Loop_Increment:
-          diag = diag::warn_unreachable_loop_increment;
-          break;
-        case reachable_code::UK_Other:
-          break;
-      }
+    unsigned diag = diag::warn_unreachable;
+    switch (UK) {
+    case reachable_code::UK_Break:
+      diag = diag::warn_unreachable_break;
+      break;
+    case reachable_code::UK_Return:
+      diag = diag::warn_unreachable_return;
+      break;
+    case reachable_code::UK_Loop_Increment:
+      diag = diag::warn_unreachable_loop_increment;
+      break;
+    case reachable_code::UK_Other:
+      break;
+    }
 
-      S.Diag(L, diag) << R1 << R2;
+    S.Diag(L, diag) << R1 << R2;
 
-      SourceLocation Open = SilenceableCondVal.getBegin();
-      if (Open.isValid()) {
-        SourceLocation Close = SilenceableCondVal.getEnd();
-        Close = S.getLocForEndOfToken(Close);
-        if (Close.isValid()) {
-          S.Diag(Open, diag::note_unreachable_silence)
+    SourceLocation Open = SilenceableCondVal.getBegin();
+    if (Open.isValid()) {
+      SourceLocation Close = SilenceableCondVal.getEnd();
+      Close = S.getLocForEndOfToken(Close);
+      if (Close.isValid()) {
+        S.Diag(Open, diag::note_unreachable_silence)
             << FixItHint::CreateInsertion(Open, "/* DISABLES CODE */ (")
             << FixItHint::CreateInsertion(Close, ")");
-        }
       }
     }
-  };
+  }
+};
 } // anonymous namespace
 
 /// CheckUnreachable - Check for unreachable code.
@@ -388,9 +388,9 @@ static void checkThrowInNonThrowingFunc(Sema &S, const FunctionDecl *FD,
   if (BodyCFG->getExit().pred_empty())
     return;
   visitReachableThrows(BodyCFG, [&](const CXXThrowExpr *Throw, CFGBlock &Block) {
-    if (throwEscapes(S, Throw, Block, BodyCFG))
-      EmitDiagForCXXThrowInNonThrowingFunc(S, Throw->getThrowLoc(), FD);
-  });
+        if (throwEscapes(S, Throw, Block, BodyCFG))
+          EmitDiagForCXXThrowInNonThrowingFunc(S, Throw->getThrowLoc(), FD);
+      });
 }
 
 static bool isNoexcept(const FunctionDecl *FD) {
@@ -803,7 +803,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
   }
   else if (isa<BlockDecl>(D)) {
     if (const FunctionType *FT =
-          BlockType->getPointeeType()->getAs<FunctionType>()) {
+            BlockType->getPointeeType()->getAs<FunctionType>()) {
       if (FT->getReturnType()->isVoidType())
         ReturnsVoid = true;
       if (FT->getNoReturnAttr())
@@ -815,7 +815,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
 
   // Short circuit for compilation speed.
   if (CD.checkDiagnostics(Diags, ReturnsVoid, HasNoReturn))
-      return;
+    return;
   SourceLocation LBrace = Body->getBeginLoc(), RBrace = Body->getEndLoc();
 
   // cpu_dispatch functions permit empty function bodies for ICC compatibility.
@@ -892,7 +892,7 @@ class ContainsReference : public ConstEvaluatedExprVisitor<ContainsReference> {
   typedef ConstEvaluatedExprVisitor<ContainsReference> Inherited;
 
   ContainsReference(ASTContext &Context, const DeclRefExpr *Needle)
-    : Inherited(Context), FoundReference(false), Needle(Needle) {}
+      : Inherited(Context), FoundReference(false), Needle(Needle) {}
 
   void VisitExpr(const Expr *E) {
     // Stop evaluating if we already have a reference.
@@ -1016,7 +1016,7 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use,
     int RemoveDiagKind = -1;
     const char *FixitStr =
         S.getLangOpts().CPlusPlus ? (I->Output ? "true" : "false")
-                                  : (I->Output ? "1" : "0");
+                               : (I->Output ? "1" : "0");
     FixItHint Fixit1, Fixit2;
 
     switch (Term ? Term->getStmtClass() : Stmt::DeclStmtClass) {
@@ -1124,7 +1124,7 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use,
         << IsCapturedByBlock << User->getSourceRange();
     if (RemoveDiagKind != -1)
       S.Diag(Fixit1.RemoveRange.getBegin(), diag::note_uninit_fixit_remove_cond)
-        << RemoveDiagKind << Str << I->Output << Fixit1 << Fixit2;
+          << RemoveDiagKind << Str << I->Output << Fixit1 << Fixit2;
 
     Diagnosed = true;
   }
@@ -1294,32 +1294,32 @@ class FallthroughMapper : public DynamicRecursiveASTVisitor {
             // Don't care about other unreachable statements.
           }
         }
-          // If there are no unreachable statements, this may be a special
-          // case in CFG:
-          // case X: {
-          //    A a;  // A has a destructor.
-          //    break;
-          // }
-          // // <<<< This place is represented by a 'hanging' CFG block.
-          // case Y:
-          continue;
+        // If there are no unreachable statements, this may be a special
+        // case in CFG:
+        // case X: {
+        //    A a;  // A has a destructor.
+        //    break;
+        // }
+        // // <<<< This place is represented by a 'hanging' CFG block.
+        // case Y:
+        continue;
       }
 
-        const Stmt *LastStmt = getLastStmt(*P);
-        if (const AttributedStmt *AS = asFallThroughAttr(LastStmt)) {
-          markFallthroughVisited(AS);
-          ++AnnotatedCnt;
-          continue; // Fallthrough annotation, good.
-        }
+      const Stmt *LastStmt = getLastStmt(*P);
+      if (const AttributedStmt *AS = asFallThroughAttr(LastStmt)) {
+        markFallthroughVisited(AS);
+        ++AnnotatedCnt;
+        continue; // Fallthrough annotation, good.
+      }
 
-        if (!LastStmt) { // This block contains no executable statements.
-          // Traverse its predecessors.
-          std::copy(P->pred_begin(), P->pred_end(),
-                    std::back_inserter(BlockQueue));
-          continue;
-        }
+      if (!LastStmt) { // This block contains no executable statements.
+        // Traverse its predecessors.
+        std::copy(P->pred_begin(), P->pred_end(),
+                  std::back_inserter(BlockQueue));
+        continue;
+      }
 
-        ++UnannotatedCnt;
+      ++UnannotatedCnt;
     }
     return !!UnannotatedCnt;
   }
@@ -1335,48 +1335,48 @@ class FallthroughMapper : public DynamicRecursiveASTVisitor {
     return true;
   }
 
-    // We don't want to traverse local type declarations. We analyze their
-    // methods separately.
-    bool TraverseDecl(Decl *D) override { return true; }
+  // We don't want to traverse local type declarations. We analyze their
+  // methods separately.
+  bool TraverseDecl(Decl *D) override { return true; }
 
-    // We analyze lambda bodies separately. Skip them here.
-    bool TraverseLambdaExpr(LambdaExpr *LE) override {
-      // Traverse the captures, but not the body.
-      for (const auto C : zip(LE->captures(), LE->capture_inits()))
-        TraverseLambdaCapture(LE, &std::get<0>(C), std::get<1>(C));
-      return true;
-    }
+  // We analyze lambda bodies separately. Skip them here.
+  bool TraverseLambdaExpr(LambdaExpr *LE) override {
+    // Traverse the captures, but not the body.
+    for (const auto C : zip(LE->captures(), LE->capture_inits()))
+      TraverseLambdaCapture(LE, &std::get<0>(C), std::get<1>(C));
+    return true;
+  }
 
-  private:
+private:
 
-    static const AttributedStmt *asFallThroughAttr(const Stmt *S) {
-      if (const AttributedStmt *AS = dyn_cast_or_null<AttributedStmt>(S)) {
-        if (hasSpecificAttr<FallThroughAttr>(AS->getAttrs()))
-          return AS;
-      }
-      return nullptr;
+  static const AttributedStmt *asFallThroughAttr(const Stmt *S) {
+    if (const AttributedStmt *AS = dyn_cast_or_null<AttributedStmt>(S)) {
+      if (hasSpecificAttr<FallThroughAttr>(AS->getAttrs()))
+        return AS;
     }
+    return nullptr;
+  }
 
-    static const Stmt *getLastStmt(const CFGBlock &B) {
-      if (const Stmt *Term = B.getTerminatorStmt())
-        return Term;
-      for (const CFGElement &Elem : llvm::reverse(B))
-        if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>())
-          return CS->getStmt();
-      // Workaround to detect a statement thrown out by CFGBuilder:
-      //   case X: {} case Y:
-      //   case X: ; case Y:
-      if (const SwitchCase *SW = dyn_cast_or_null<SwitchCase>(B.getLabel()))
-        if (!isa<SwitchCase>(SW->getSubStmt()))
-          return SW->getSubStmt();
+  static const Stmt *getLastStmt(const CFGBlock &B) {
+    if (const Stmt *Term = B.getTerminatorStmt())
+      return Term;
+    for (const CFGElement &Elem : llvm::reverse(B))
+      if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>())
+        return CS->getStmt();
+    // Workaround to detect a statement thrown out by CFGBuilder:
+    //   case X: {} case Y:
+    //   case X: ; case Y:
+    if (const SwitchCase *SW = dyn_cast_or_null<SwitchCase>(B.getLabel()))
+      if (!isa<SwitchCase>(SW->getSubStmt()))
+        return SW->getSubStmt();
 
-      return nullptr;
-    }
+    return nullptr;
+  }
 
-    bool FoundSwitchStatements;
-    AttrStmts FallthroughStmts;
-    Sema &S;
-    llvm::SmallPtrSet<const CFGBlock *, 16> ReachableBlocks;
+  bool FoundSwitchStatements;
+  AttrStmts FallthroughStmts;
+  Sema &S;
+  llvm::SmallPtrSet<const CFGBlock *, 16> ReachableBlocks;
 };
 } // anonymous namespace
 
@@ -1384,7 +1384,7 @@ static StringRef getFallthroughAttrSpelling(Preprocessor &PP,
                                             SourceLocation Loc) {
   TokenValue FallthroughTokens[] = {
     tok::l_square, tok::l_square,
-    PP.getIdentifierInfo("fallthrough"),
+                                    PP.getIdentifierInfo("fallthrough"),
     tok::r_square, tok::r_square
   };
 
@@ -1513,7 +1513,7 @@ static void diagnoseRepeatedUseOfWeak(Sema &S,
   typedef sema::FunctionScopeInfo::WeakObjectUseMap WeakObjectUseMap;
   typedef sema::FunctionScopeInfo::WeakUseVector WeakUseVector;
   typedef std::pair<const Stmt *, WeakObjectUseMap::const_iterator>
-  StmtUsesPair;
+      StmtUsesPair;
 
   ASTContext &Ctx = S.getASTContext();
 
@@ -1977,7 +1977,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
                : getNotes();
   }
 
- public:
+public:
   ThreadSafetyReporter(Sema &S, SourceLocation FL, SourceLocation FEL)
     : S(S), FunLocation(FL), FunEndLocation(FEL),
       CurrentFunction(nullptr), Verbose(false) {}
@@ -2075,18 +2075,18 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
                                  bool ReentrancyMismatch) override {
     unsigned DiagID = 0;
     switch (LEK) {
-      case LEK_LockedSomePredecessors:
-        DiagID = diag::warn_lock_some_predecessors;
-        break;
-      case LEK_LockedSomeLoopIterations:
-        DiagID = diag::warn_expecting_lock_held_on_loop;
-        break;
-      case LEK_LockedAtEndOfFunction:
-        DiagID = diag::warn_no_unlock;
-        break;
-      case LEK_NotLockedAtEndOfFunction:
-        DiagID = diag::warn_expecting_locked;
-        break;
+    case LEK_LockedSomePredecessors:
+      DiagID = diag::warn_lock_some_predecessors;
+      break;
+    case LEK_LockedSomeLoopIterations:
+      DiagID = diag::warn_expecting_lock_held_on_loop;
+      break;
+    case LEK_LockedAtEndOfFunction:
+      DiagID = diag::warn_no_unlock;
+      break;
+    case LEK_NotLockedAtEndOfFunction:
+      DiagID = diag::warn_expecting_locked;
+      break;
     }
     if (LocEndOfScope.isInvalid())
       LocEndOfScope = FunEndLocation;
@@ -2132,7 +2132,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
       break;
     }
     PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID)
-      << D << getLockKindFromAccessKind(AK));
+                                         << D << getLockKindFromAccessKind(AK));
     Warnings.emplace_back(std::move(Warning), getNotes());
   }
 
@@ -2143,39 +2143,39 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
     unsigned DiagID = 0;
     if (PossibleMatch) {
       switch (POK) {
-        case POK_VarAccess:
-          DiagID = diag::warn_variable_requires_lock_precise;
-          break;
-        case POK_VarDereference:
-          DiagID = diag::warn_var_deref_requires_lock_precise;
-          break;
-        case POK_FunctionCall:
-          DiagID = diag::warn_fun_requires_lock_precise;
-          break;
-        case POK_PassByRef:
-          DiagID = diag::warn_guarded_pass_by_reference;
-          break;
-        case POK_PtPassByRef:
-          DiagID = diag::warn_pt_guarded_pass_by_reference;
-          break;
-        case POK_ReturnByRef:
-          DiagID = diag::warn_guarded_return_by_reference;
-          break;
-        case POK_PtReturnByRef:
-          DiagID = diag::warn_pt_guarded_return_by_reference;
-          break;
-        case POK_PassPointer:
-          DiagID = diag::warn_guarded_pass_pointer;
-          break;
-        case POK_PtPassPointer:
-          DiagID = diag::warn_pt_guarded_pass_pointer;
-          break;
-        case POK_ReturnPointer:
-          DiagID = diag::warn_guarded_return_pointer;
-          break;
-        case POK_PtReturnPointer:
-          DiagID = diag::warn_pt_guarded_return_pointer;
-          break;
+      case POK_VarAccess:
+        DiagID = diag::warn_variable_requires_lock_precise;
+        break;
+      case POK_VarDereference:
+        DiagID = diag::warn_var_deref_requires_lock_precise;
+        break;
+      case POK_FunctionCall:
+        DiagID = diag::warn_fun_requires_lock_precise;
+        break;
+      case POK_PassByRef:
+        DiagID = diag::warn_guarded_pass_by_reference;
+        break;
+      case POK_PtPassByRef:
+        DiagID = diag::warn_pt_guarded_pass_by_reference;
+        break;
+      case POK_ReturnByRef:
+        DiagID = diag::warn_guarded_return_by_reference;
+        break;
+      case POK_PtReturnByRef:
+        DiagID = diag::warn_pt_guarded_return_by_reference;
+        break;
+      case POK_PassPointer:
+        DiagID = diag::warn_guarded_pass_pointer;
+        break;
+      case POK_PtPassPointer:
+        DiagID = diag::warn_pt_guarded_pass_pointer;
+        break;
+      case POK_ReturnPointer:
+        DiagID = diag::warn_guarded_return_pointer;
+        break;
+      case POK_PtReturnPointer:
+        DiagID = diag::warn_pt_guarded_return_pointer;
+        break;
       }
       PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
                                                        << D
@@ -2191,39 +2191,39 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
         Warnings.emplace_back(std::move(Warning), getNotes(Note));
     } else {
       switch (POK) {
-        case POK_VarAccess:
-          DiagID = diag::warn_variable_requires_lock;
-          break;
-        case POK_VarDereference:
-          DiagID = diag::warn_var_deref_requires_lock;
-          break;
-        case POK_FunctionCall:
-          DiagID = diag::warn_fun_requires_lock;
-          break;
-        case POK_PassByRef:
-          DiagID = diag::warn_guarded_pass_by_reference;
-          break;
-        case POK_PtPassByRef:
-          DiagID = diag::warn_pt_guarded_pass_by_reference;
-          break;
-        case POK_ReturnByRef:
-          DiagID = diag::warn_guarded_return_by_reference;
-          break;
-        case POK_PtReturnByRef:
-          DiagID = diag::warn_pt_guarded_return_by_reference;
-          break;
-        case POK_PassPointer:
-          DiagID = diag::warn_guarded_pass_pointer;
-          break;
-        case POK_PtPassPointer:
-          DiagID = diag::warn_pt_guarded_pass_pointer;
-          break;
-        case POK_ReturnPointer:
-          DiagID = diag::warn_guarded_return_pointer;
-          break;
-        case POK_PtReturnPointer:
-          DiagID = diag::warn_pt_guarded_return_pointer;
-          break;
+      case POK_VarAccess:
+        DiagID = diag::warn_variable_requires_lock;
+        break;
+      case POK_VarDereference:
+        DiagID = diag::warn_var_deref_requires_lock;
+        break;
+      case POK_FunctionCall:
+        DiagID = diag::warn_fun_requires_lock;
+        break;
+      case POK_PassByRef:
+        DiagID = diag::warn_guarded_pass_by_reference;
+        break;
+      case POK_PtPassByRef:
+        DiagID = diag::warn_pt_guarded_pass_by_reference;
+        break;
+      case POK_ReturnByRef:
+        DiagID = diag::warn_guarded_return_by_reference;
+        break;
+      case POK_PtReturnByRef:
+        DiagID = diag::warn_pt_guarded_return_by_reference;
+        break;
+      case POK_PassPointer:
+        DiagID = diag::warn_guarded_pass_pointer;
+        break;
+      case POK_PtPassPointer:
+        DiagID = diag::warn_pt_guarded_pass_pointer;
+        break;
+      case POK_ReturnPointer:
+        DiagID = diag::warn_guarded_return_pointer;
+        break;
+      case POK_PtReturnPointer:
+        DiagID = diag::warn_pt_guarded_return_pointer;
+        break;
       }
       PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
                                                        << D
@@ -2241,7 +2241,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
                              SourceLocation Loc) override {
     PartialDiagnosticAt Warning(Loc,
         S.PDiag(diag::warn_acquire_requires_negative_cap)
-        << Kind << LockName << Neg);
+                 << Kind << LockName << Neg);
     Warnings.emplace_back(std::move(Warning), getNotes());
   }
 
@@ -2423,7 +2423,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
 
 public:
   UnsafeBufferUsageReporter(Sema &S, bool SuggestSuggestions)
-    : S(S), SuggestSuggestions(SuggestSuggestions) {}
+      : S(S), SuggestSuggestions(SuggestSuggestions) {}
 
   void handleUnsafeOperation(const Stmt *Operation, bool IsRelatedToDecl,
                              ASTContext &Ctx) override {
@@ -2808,6 +2808,17 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
         << UseExpr->getEndLoc();
   }
 
+  void reportUseAfterReturn(const Expr *IssueExpr, const Stmt *EscapeStmt,
+                            SourceLocation ExpiryLoc, Confidence C) override {
+    S.Diag(IssueExpr->getExprLoc(),
+           C == Confidence::Definite
+               ? diag::warn_lifetime_safety_loan_expires_permissive
+               : diag::warn_lifetime_safety_loan_expires_strict)
+        << IssueExpr->getEndLoc();
+
+    S.Diag(EscapeStmt->getBeginLoc(), diag::note_lifetime_safety_returned_here);
+  }
+
 private:
   Sema &S;
 };
@@ -2815,7 +2826,7 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
 } // namespace clang::lifetimes
 
 void clang::sema::AnalysisBasedWarnings::IssueWarnings(
-     TranslationUnitDecl *TU) {
+    TranslationUnitDecl *TU) {
   if (!TU)
     return; // This is unexpected, give up quietly.
 
@@ -2928,13 +2939,13 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
     AC.getCFGBuildOptions().setAllAlwaysAdd();
   } else {
     AC.getCFGBuildOptions()
-      .setAlwaysAdd(Stmt::BinaryOperatorClass)
-      .setAlwaysAdd(Stmt::CompoundAssignOperatorClass)
-      .setAlwaysAdd(Stmt::BlockExprClass)
-      .setAlwaysAdd(Stmt::CStyleCastExprClass)
-      .setAlwaysAdd(Stmt::DeclRefExprClass)
-      .setAlwaysAdd(Stmt::ImplicitCastExprClass)
-      .setAlwaysAdd(Stmt::UnaryOperatorClass);
+        .setAlwaysAdd(Stmt::BinaryOperatorClass)
+        .setAlwaysAdd(Stmt::CompoundAssignOperatorClass)
+        .setAlwaysAdd(Stmt::BlockExprClass)
+        .setAlwaysAdd(Stmt::CStyleCastExprClass)
+        .setAlwaysAdd(Stmt::DeclRefExprClass)
+        .setAlwaysAdd(Stmt::ImplicitCastExprClass)
+        .setAlwaysAdd(Stmt::UnaryOperatorClass);
   }
 
   // Install the logical handler.

>From 0ba83e2a940795f3427f7141c20822853c51e63a Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 02/12] Adding use-after-return in Lifetime Analysis

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  |  26 +-
 .../Analyses/LifetimeSafety/FactsGenerator.h  |   2 +
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |   2 +-
 .../Analyses/LifetimeSafety/LiveOrigins.h     |  11 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |  10 +
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  91 ++-----
 clang/lib/Analysis/LifetimeSafety/Dataflow.h  |   3 -
 clang/lib/Analysis/LifetimeSafety/Facts.cpp   |   7 -
 .../LifetimeSafety/FactsGenerator.cpp         |   5 +-
 .../Analysis/LifetimeSafety/LiveOrigins.cpp   |  48 +++-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |   8 +-
 .../Sema/warn-lifetime-safety-dataflow.cpp    |   4 +-
 clang/test/Sema/warn-lifetime-safety.cpp      | 118 +++++++++
 .../unittests/Analysis/LifetimeSafetyTest.cpp | 237 ++++++++++++++++++
 14 files changed, 446 insertions(+), 126 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 712e1d42e2966..1b87eee4a8d17 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -39,13 +39,11 @@ class Fact {
     /// loan set.
     OriginFlow,
     /// An origin escapes the function by flowing into the return value.
-    ReturnOfOrigin,
-    /// An origin is used (eg. appears as l-value expression like DeclRefExpr).
     Use,
     /// A marker for a specific point in the code, for testing.
     TestPoint,
+    // Indicates that an origin escapes the function scope (e.g., via return).
     OriginEscapes,
-    // An origin that stores a loan escapes the function.
   };
 
 private:
@@ -130,33 +128,19 @@ class OriginFlowFact : public Fact {
             const OriginManager &OM) const override;
 };
 
-class ReturnOfOriginFact : public Fact {
-  OriginID OID;
-
-public:
-  static bool classof(const Fact *F) {
-    return F->getKind() == Kind::ReturnOfOrigin;
-  }
-
-  ReturnOfOriginFact(OriginID OID) : Fact(Kind::ReturnOfOrigin), OID(OID) {}
-  OriginID getReturnedOriginID() const { return OID; }
-  void dump(llvm::raw_ostream &OS, const LoanManager &,
-            const OriginManager &OM) const override;
-};
-
 class OriginEscapesFact : public Fact {
   OriginID OID;
-  const Stmt *EscapeSource;
+  const Expr *EscapeExpr;
 
 public:
   static bool classof(const Fact *F) {
     return F->getKind() == Kind::OriginEscapes;
   }
 
-  OriginEscapesFact(OriginID OID, const Stmt *Source)
-      : Fact(Kind::OriginEscapes), OID(OID), EscapeSource(Source) {}
+  OriginEscapesFact(OriginID OID, const Expr *EscapeExpr)
+      : Fact(Kind::OriginEscapes), OID(OID), EscapeExpr(EscapeExpr) {}
   OriginID getEscapedOriginID() const { return OID; }
-  const Stmt *getEscapeSource() const { return EscapeSource; }
+  const Expr *getEscapeExpr() const { return EscapeExpr; };
   void dump(llvm::raw_ostream &OS, const LoanManager &,
             const OriginManager &OM) const override;
 };
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index ec3c130bb6c66..21d160493289e 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -93,6 +93,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   FactManager &FactMgr;
   AnalysisDeclContext &AC;
   llvm::SmallVector<Fact *> CurrentBlockFacts;
+  // Collect origins that escape the function in this block. These are handled
+  // at the end of the block to ensure `OEFs` appear after `Expire` facts.
   llvm::SmallVector<Fact *> EscapesInCurrentBlock;
   // To distinguish between reads and writes for use-after-free checks, this map
   // stores the `UseFact` for each `DeclRefExpr`. We initially identify all
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index f5b1b3d9b6564..b34a7f18b5809 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -44,7 +44,7 @@ class LifetimeSafetyReporter {
                                   Confidence Confidence) {}
 
   virtual void reportUseAfterReturn(const Expr *IssueExpr,
-                                    const Stmt *EscapeStmt,
+                                    const Expr *EscapeExpr,
                                     SourceLocation ExpiryLoc,
                                     Confidence Confidence) {}
 };
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
index c4f5f0e9ae46c..dae97501c0b53 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
@@ -43,7 +43,7 @@ struct LivenessInfo {
   /// multiple uses along different paths, this will point to the use appearing
   /// earlier in the translation unit.
   /// This is 'null' when the origin is not live.
-  const UseFact *CausingUseFact;
+  const Fact *CausingFact;
 
   /// The kind of liveness of the origin.
   /// `Must`: The origin is live on all control-flow paths from the current
@@ -56,17 +56,16 @@ struct LivenessInfo {
   /// while `Maybe`-be-alive suggests a potential one on some paths.
   LivenessKind Kind;
 
-  LivenessInfo() : CausingUseFact(nullptr), Kind(LivenessKind::Dead) {}
-  LivenessInfo(const UseFact *UF, LivenessKind K)
-      : CausingUseFact(UF), Kind(K) {}
+  LivenessInfo() : CausingFact(nullptr), Kind(LivenessKind::Dead) {}
+  LivenessInfo(const Fact *CF, LivenessKind K) : CausingFact(CF), Kind(K) {}
 
   bool operator==(const LivenessInfo &Other) const {
-    return CausingUseFact == Other.CausingUseFact && Kind == Other.Kind;
+    return CausingFact == Other.CausingFact && Kind == Other.Kind;
   }
   bool operator!=(const LivenessInfo &Other) const { return !(*this == Other); }
 
   void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
-    IDBuilder.AddPointer(CausingUseFact);
+    IDBuilder.AddPointer(CausingFact);
     IDBuilder.Add(Kind);
   }
 };
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 6ea5e34cab291..4eb7263715f4a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10728,6 +10728,16 @@ def warn_lifetime_safety_loan_expires_permissive : Warning<
 def warn_lifetime_safety_loan_expires_strict : Warning<
    "object whose reference is captured may not live long enough">,
    InGroup<LifetimeSafetyStrict>, DefaultIgnore;
+
+def warn_lifetime_safety_return_stack_addr_permissive
+    : Warning<"returning reference to stack allocated object">,
+      InGroup<LifetimeSafetyPermissive>,
+      DefaultIgnore;
+def warn_lifetime_safety_return_stack_addr_strict
+    : Warning<"may be returning reference to stack allocated object">,
+      InGroup<LifetimeSafetyStrict>,
+      DefaultIgnore;
+
 def note_lifetime_safety_used_here : Note<"later used here">;
 def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
 def note_lifetime_safety_returned_here : Note<"returned here">;
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index ae5c62ef04d72..64ca5439cc264 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -44,7 +44,7 @@ namespace {
 /// Struct to store the complete context for a potential lifetime violation.
 struct PendingWarning {
   SourceLocation ExpiryLoc; // Where the loan expired.
-  const Expr *UseExpr;      // Where the origin holding this loan was used.
+  const Fact *CausingFact;  // If the use is a UseFact or OriginEscapeFact
   Confidence ConfidenceLevel;
 };
 
@@ -64,25 +64,17 @@ class LifetimeChecker {
         Reporter(Reporter) {
     for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) {
       llvm::SmallVector<const ExpireFact *> BlockExpires;
-      llvm::SmallVector<const OriginEscapesFact *> BlockEscapes;
       for (const Fact *F : FactMgr.getFacts(B)) {
         if (const auto *EF = F->getAs<ExpireFact>()) {
           checkExpiry(EF);
           BlockExpires.push_back(EF);
-        } else if (const auto *OEF = F->getAs<OriginEscapesFact>()) {
-          BlockEscapes.push_back(OEF);
-        }
-      }
-      if (Reporter) {
-        for (const OriginEscapesFact *OEF : BlockEscapes) {
-          checkEscape(OEF, BlockExpires);
         }
       }
     }
     issuePendingWarnings();
   }
 
-  /// Checks for use-after-free errors when a loan expires.
+  /// Checks for use-after-free & use-after-return errors when a loan expires.
   ///
   /// This method examines all live origins at the expiry point and determines
   /// if any of them hold the expiring loan. If so, it creates a pending
@@ -97,7 +89,8 @@ class LifetimeChecker {
     LoanID ExpiredLoan = EF->getLoanID();
     LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF);
     Confidence CurConfidence = Confidence::None;
-    const UseFact *BadUse = nullptr;
+    const Fact *BestCausingFact = nullptr;
+
     for (auto &[OID, LiveInfo] : Origins) {
       LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
       if (!HeldLoans.contains(ExpiredLoan))
@@ -106,85 +99,37 @@ class LifetimeChecker {
       Confidence NewConfidence = livenessKindToConfidence(LiveInfo.Kind);
       if (CurConfidence < NewConfidence) {
         CurConfidence = NewConfidence;
-        BadUse = LiveInfo.CausingUseFact;
+        BestCausingFact = LiveInfo.CausingFact;
       }
     }
-    if (!BadUse)
+    if (!BestCausingFact)
       return;
     // We have a use-after-free.
     Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel;
     if (LastConf >= CurConfidence)
       return;
     FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
-                                     /*UseExpr=*/BadUse->getUseExpr(),
+                                     /*BestCausingFact*/ BestCausingFact,
                                      /*ConfidenceLevel=*/CurConfidence};
   }
 
-  void checkEscape(const OriginEscapesFact *OEF,
-                   llvm::ArrayRef<const ExpireFact *> BlockExpires) {
-
-    if (!Reporter) {
-      return;
-    }
-
-    OriginID returnedOID = OEF->getEscapedOriginID();
-    ProgramPoint PP = OEF;
-
-    LoanSet HeldLoans = LoanPropagation.getLoans(returnedOID, PP);
-    if (HeldLoans.isEmpty()) {
-      return;
-    }
-
-    llvm::SmallSet<LoanID, 4> ExpiredLoansInBlock;
-    llvm::DenseMap<LoanID, SourceLocation> ExpiryLocs;
-
-    for (const ExpireFact *EF : BlockExpires) {
-      ExpiredLoansInBlock.insert(EF->getLoanID());
-      ExpiryLocs[EF->getLoanID()] = EF->getExpiryLoc();
-    }
-
-    bool hasExpiredDependency = false;
-    bool allHeldLoansExpired = true;
-    LoanID exampleExpiredLoan = LoanID();
-
-    for (LoanID heldLoan : HeldLoans) {
-      if (ExpiredLoansInBlock.count(heldLoan)) {
-        hasExpiredDependency = true;
-        if (exampleExpiredLoan.Value == LoanID().Value) {
-          exampleExpiredLoan = heldLoan;
-        }
-      } else {
-        allHeldLoansExpired = false;
-      }
-    }
-
-    if (!hasExpiredDependency) {
-      return;
-    }
-
-    Confidence FinalConfidence;
-    if (allHeldLoansExpired) {
-      FinalConfidence = Confidence::Definite;
-    } else {
-      FinalConfidence = Confidence::Maybe;
-    }
-
-    const Loan &L = FactMgr.getLoanMgr().getLoan(exampleExpiredLoan);
-    SourceLocation ExpiryLoc = ExpiryLocs[exampleExpiredLoan];
-    const Stmt *EscapeSource = OEF->getEscapeSource();
-
-    Reporter->reportUseAfterReturn(L.IssueExpr, EscapeSource, ExpiryLoc,
-                                   FinalConfidence);
-  }
-
   void issuePendingWarnings() {
     if (!Reporter)
       return;
     for (const auto &[LID, Warning] : FinalWarningsMap) {
       const Loan &L = FactMgr.getLoanMgr().getLoan(LID);
       const Expr *IssueExpr = L.IssueExpr;
-      Reporter->reportUseAfterFree(IssueExpr, Warning.UseExpr,
-                                   Warning.ExpiryLoc, Warning.ConfidenceLevel);
+      const Fact *CausingFact = Warning.CausingFact;
+      Confidence Confidence = Warning.ConfidenceLevel;
+      SourceLocation ExpiryLoc = Warning.ExpiryLoc;
+
+      if (const auto *UF = CausingFact->getAs<UseFact>()) {
+        Reporter->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc,
+                                     Confidence);
+      } else if (const auto *OEF = CausingFact->getAs<OriginEscapesFact>()) {
+        Reporter->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(),
+                                       ExpiryLoc, Confidence);
+      }
     }
   }
 };
diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
index 37720ffa03618..23ef9dbcdcb86 100644
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
@@ -166,8 +166,6 @@ class DataflowAnalysis {
       return D->transfer(In, *F->getAs<ExpireFact>());
     case Fact::Kind::OriginFlow:
       return D->transfer(In, *F->getAs<OriginFlowFact>());
-    case Fact::Kind::ReturnOfOrigin:
-      return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
     case Fact::Kind::OriginEscapes:
       return D->transfer(In, *F->getAs<OriginEscapesFact>());
     case Fact::Kind::Use:
@@ -182,7 +180,6 @@ class DataflowAnalysis {
   Lattice transfer(Lattice In, const IssueFact &) { return In; }
   Lattice transfer(Lattice In, const ExpireFact &) { return In; }
   Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
-  Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
   Lattice transfer(Lattice In, const OriginEscapesFact &) { return In; }
   Lattice transfer(Lattice In, const UseFact &) { return In; }
   Lattice transfer(Lattice In, const TestPointFact &) { return In; }
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 29c75959ba0fe..45fce4bcf8ba3 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -43,13 +43,6 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &,
   OS << ")\n";
 }
 
-void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
-                              const OriginManager &OM) const {
-  OS << "ReturnOfOrigin (";
-  OM.dump(getReturnedOriginID(), OS);
-  OS << ")\n";
-}
-
 void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &,
                               const OriginManager &OM) const {
   OS << "OriginEscapes (";
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 762ed07eb5bb8..72a1b52786d54 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -59,6 +59,7 @@ void FactsGenerator::run() {
   for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
     CurrentBlockFacts.clear();
     EscapesInCurrentBlock.clear();
+    EscapesInCurrentBlock.clear();
     for (unsigned I = 0; I < Block->size(); ++I) {
       const CFGElement &Element = Block->Elements[I];
       if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
@@ -67,6 +68,8 @@ void FactsGenerator::run() {
                    Element.getAs<CFGAutomaticObjDtor>())
         handleDestructor(*DtorOpt);
     }
+    CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
+                             EscapesInCurrentBlock.end());
     CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
                              EscapesInCurrentBlock.end());
     FactMgr.addBlockFacts(Block, CurrentBlockFacts);
@@ -170,7 +173,7 @@ void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) {
     if (hasOrigin(RetExpr)) {
       OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr);
       EscapesInCurrentBlock.push_back(
-          FactMgr.createFact<OriginEscapesFact>(OID, RS));
+          FactMgr.createFact<OriginEscapesFact>(OID, RetExpr));
     }
   }
 }
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index cddb3f3ce4c1c..02a079184723c 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -53,6 +53,16 @@ struct Lattice {
   }
 };
 
+static SourceLocation GetFactLoc(const Fact &F) {
+  if (const auto *UF = F.getAs<UseFact>()) {
+    return UF->getUseExpr()->getExprLoc();
+  }
+  if (const auto *OEF = F.getAs<OriginEscapesFact>()) {
+    return OEF->getEscapeExpr()->getExprLoc();
+  }
+  return SourceLocation(); // Invalid SourceLocation
+}
+
 /// The analysis that tracks which origins are live, with granular information
 /// about the causing use fact and confidence level. This is a backward
 /// analysis.
@@ -74,11 +84,25 @@ class AnalysisImpl
   /// one.
   Lattice join(Lattice L1, Lattice L2) const {
     LivenessMap Merged = L1.LiveOrigins;
-    // Take the earliest UseFact to make the join hermetic and commutative.
-    auto CombineUseFact = [](const UseFact &A,
-                             const UseFact &B) -> const UseFact * {
-      return A.getUseExpr()->getExprLoc() < B.getUseExpr()->getExprLoc() ? &A
-                                                                         : &B;
+    // Take the earliest Fact to make the join hermetic and commutative.
+    auto CombineCausingFact = [](const Fact &A, const Fact &B) -> const Fact * {
+      SourceLocation LocA = GetFactLoc(A);
+      SourceLocation LocB = GetFactLoc(B);
+
+      bool aValid = LocA.isValid();
+      bool bValid = LocB.isValid();
+
+      if (aValid && bValid) {
+        if (LocA < LocB)
+          return &A;
+        if (LocB < LocA)
+          return &B;
+      } else if (aValid) {
+        return &A;
+      } else if (bValid) {
+        return &B;
+      }
+      return &A < &B ? &A : &B;
     };
     auto CombineLivenessKind = [](LivenessKind K1,
                                   LivenessKind K2) -> LivenessKind {
@@ -93,11 +117,11 @@ class AnalysisImpl
                                    const LivenessInfo *L2) -> LivenessInfo {
       assert((L1 || L2) && "unexpectedly merging 2 empty sets");
       if (!L1)
-        return LivenessInfo(L2->CausingUseFact, LivenessKind::Maybe);
+        return LivenessInfo(L2->CausingFact, LivenessKind::Maybe);
       if (!L2)
-        return LivenessInfo(L1->CausingUseFact, LivenessKind::Maybe);
+        return LivenessInfo(L1->CausingFact, LivenessKind::Maybe);
       return LivenessInfo(
-          CombineUseFact(*L1->CausingUseFact, *L2->CausingUseFact),
+          CombineCausingFact(*L1->CausingFact, *L2->CausingFact),
           CombineLivenessKind(L1->Kind, L2->Kind));
     };
     return Lattice(utils::join(
@@ -120,6 +144,14 @@ class AnalysisImpl
                                LivenessInfo(&UF, LivenessKind::Must)));
   }
 
+  // A return operation makes the origin live with definite confidence, as it
+  /// dominates this program point.
+  Lattice transfer(Lattice In, const OriginEscapesFact &OEF) {
+    OriginID OID = OEF.getEscapedOriginID();
+    return Lattice(Factory.add(In.LiveOrigins, OID,
+                               LivenessInfo(&OEF, LivenessKind::Must)));
+  }
+
   /// Issuing a new loan to an origin kills its liveness.
   Lattice transfer(Lattice In, const IssueFact &IF) {
     return Lattice(Factory.remove(In.LiveOrigins, IF.getOriginID()));
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 879793575e0b4..08b621f39441c 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2808,15 +2808,15 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
         << UseExpr->getEndLoc();
   }
 
-  void reportUseAfterReturn(const Expr *IssueExpr, const Stmt *EscapeStmt,
+  void reportUseAfterReturn(const Expr *IssueExpr, const Expr *EscapeExpr,
                             SourceLocation ExpiryLoc, Confidence C) override {
     S.Diag(IssueExpr->getExprLoc(),
            C == Confidence::Definite
-               ? diag::warn_lifetime_safety_loan_expires_permissive
-               : diag::warn_lifetime_safety_loan_expires_strict)
+               ? diag::warn_lifetime_safety_return_stack_addr_permissive
+               : diag::warn_lifetime_safety_return_stack_addr_strict)
         << IssueExpr->getEndLoc();
 
-    S.Diag(EscapeStmt->getBeginLoc(), diag::note_lifetime_safety_returned_here);
+    S.Diag(EscapeExpr->getBeginLoc(), diag::note_lifetime_safety_returned_here);
   }
 
 private:
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index 31148b990d6bd..32796c2ae43c4 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -18,8 +18,8 @@ MyObj* return_local_addr() {
   return p;
 // CHECK:   Use ([[O_P]] (Decl: p), Read)
 // CHECK:   OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p))
-// CHECK:   ReturnOfOrigin ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
 // CHECK:   Expire ([[L_X]] (Path: x))
+// CHECK:   OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
 }
 
 
@@ -49,8 +49,8 @@ MyObj* assign_and_return_local_addr() {
   return ptr2;
 // CHECK:   Use ([[O_PTR2]] (Decl: ptr2), Read)
 // CHECK:   OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2))
-// CHECK:   ReturnOfOrigin ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
 // CHECK:   Expire ([[L_Y]] (Path: y))
+// CHECK:   OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
 }
 
 // Return of Non-Pointer Type
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 4f234f0ac6e2d..55bf6253a8c82 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -396,6 +396,118 @@ void loan_from_previous_iteration(MyObj safe, bool condition) {
   }             // expected-note {{destroyed here}}
 }
 
+//===----------------------------------------------------------------------===//
+// Basic Definite Use-After-Return (Return-Stack-Address) (-W...permissive)
+// These are cases where the pointer is guaranteed to be dangling at the use site.
+//===----------------------------------------------------------------------===//
+
+MyObj* simple_return_stack_address(){
+  MyObj s;      
+  MyObj* p = &s; // expected-warning {{returning reference to stack allocated object}}
+  return p;      // expected-note {{returned here}}
+}
+
+MyObj* conditional_assign_unconditional_return(MyObj safe, bool c){
+  MyObj s; 
+  MyObj* p = &safe;
+  if(c){
+    p = &s;       // expected-warning {{returning reference to stack allocated object}}
+  }     
+  return p;      // expected-note {{returned here}}
+}
+
+MyObj* conditional_assign_both_branches(MyObj safe, bool c){
+
+  MyObj s;
+  MyObj* p = nullptr;
+  if (c) {
+    p = &s;     // expected-warning {{returning reference to stack allocated object}}
+  } else {
+    p = &safe;
+  }
+  return p;     // expected-note {{returned here}}
+
+}
+
+MyObj* reassign_safe_to_local(MyObj safe){
+  MyObj local;
+  MyObj* p = &safe;
+
+  p = &local;   // expected-warning {{returning reference to stack allocated object}}
+  return p;     // expected-note {{returned here}}
+}
+
+MyObj* pointer_chain_to_local(){
+  MyObj local;
+  MyObj* p1 = &local; // expected-warning {{returning reference to stack allocated object}}
+
+  MyObj* p2 = p1; 
+
+  return p2;          // expected-note {{returned here}}
+}
+
+MyObj* multiple_assign_multiple_return(MyObj safe, bool c1, bool c2){
+  MyObj local1;
+  MyObj local2;
+  MyObj* p = nullptr;
+  if(c1){
+    p = &local1;      // expected-warning {{returning reference to stack allocated object}}
+    return p;         // expected-note {{returned here}}
+  }
+  else if(c2){
+    p = &local2;      // expected-warning {{returning reference to stack allocated object}}
+    return p;         // expected-note {{returned here}}
+  }
+  p = &safe;
+  return p;
+}
+
+MyObj* multiple_assign_single_return(MyObj safe, bool c1, bool c2){
+  MyObj local1;
+  MyObj local2;
+  MyObj* p = nullptr;
+  if(c1){
+    p = &local1;     // expected-warning {{returning reference to stack allocated object}}
+  }
+  else if(c2){
+    p = &local2;     // expected-warning {{returning reference to stack allocated object}}
+  }
+  else{
+  p = &safe;
+  }
+  
+  return p;         // expected-note {{returned here}} // expected-note {{returned here}}
+}
+
+//===----------------------------------------------------------------------===//
+// Use-After-Scope & Use-After-Return (Return-Stack-Address) Combined
+// These are cases where the diagnostic kind is determined by location
+//===----------------------------------------------------------------------===//
+
+MyObj* uaf_before_uar(){
+  MyObj* p;
+  {
+    MyObj local_obj; 
+    p = &local_obj;  // expected-warning {{object whose reference is captured does not live long enough}}
+  }                  // expected-note {{destroyed here}}
+  return p;          // expected-note {{later used here}}
+}
+
+MyObj* uar_before_uaf(MyObj safe, bool c){
+  MyObj* p;
+  {
+    MyObj local_obj; 
+    p = &local_obj;  // expected-warning {{returning reference to stack allocated object}}
+    if(c){
+      return p;      // expected-note {{returned here}}
+    }
+
+  }
+  (void)*p;
+  p = &safe;
+  return p;
+}
+
 //===----------------------------------------------------------------------===//
 // No-Error Cases
 //===----------------------------------------------------------------------===//
@@ -434,6 +546,12 @@ void no_error_loan_from_current_iteration(bool cond) {
   }
 }
 
+MyObj* safe_return(MyObj safe){
+  MyObj local;
+  MyObj *p = &local;
+  p = &safe;    // p has been reassigned
+  return p;     // This is safe
+}
 
 //===----------------------------------------------------------------------===//
 // Lifetimebound Attribute Tests
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 0c051847f4d47..5370cde779a8a 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -1202,5 +1202,242 @@ TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) {
   EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p1"));
 }
 
+TEST_F(LifetimeAnalysisTest, SimpleReturnStackAddress) {
+  SetupTest(R"(
+    MyObj* target() {
+      MyObj s;
+      MyObj* p = &s;
+      POINT(p1);
+      return p;
+    }
+  )");
+  EXPECT_THAT(Origin("p"), HasLoansTo({"s"}, "p1"));
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, ConditionalAssignUnconditionalReturn) {
+  SetupTest(R"(
+    MyObj* target(bool c) {
+      MyObj s1;
+      MyObj* p = nullptr;
+      POINT(before_if);
+      if (c) {
+        p = &s1;
+        POINT(after_assign);
+      }
+      POINT(after_if);
+      return p;
+    }
+  )");
+  EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_if"));
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_if"));
+
+  EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_assign"));
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign"));
+
+  EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_if"));
+  EXPECT_THAT(Origins({"p"}), MaybeLiveAt("before_if"));
+}
+
+TEST_F(LifetimeAnalysisTest, ConditionalAssignBothBranches) {
+  SetupTest(R"(
+    MyObj* target(bool c) {
+      MyObj s1;
+      static MyObj s2;
+      MyObj* p = nullptr;
+      POINT(before_if);
+      if (c) {
+        p = &s1;
+        POINT(after_assign_if);
+      } else {
+       p = &s2;
+       POINT(after_assign_else);
+      }
+      POINT(after_if);
+      return p;
+    }
+  )");
+  EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2"}, "after_if"));
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_if"));
+
+  EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_assign_if"));
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign_if"));
+
+  EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "after_assign_else"));
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign_else"));
+
+  EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_if"));
+  EXPECT_THAT(NoOrigins(), AreLiveAt("before_if"));
+}
+
+TEST_F(LifetimeAnalysisTest, ReassignFromSafeToLocalThenReturn) {
+  SetupTest(R"(
+    MyObj* target() {
+      static MyObj safe_obj;
+      MyObj local_obj;
+      MyObj* p = &safe_obj;
+      POINT(p1);
+
+      p = &local_obj;
+      POINT(p2); 
+      return p;
+    }
+  )");
+
+  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p2"));
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
+
+  EXPECT_THAT(Origin("p"), HasLoansTo({"safe_obj"}, "p1"));
+  EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, PointerChainToLocal) {
+  SetupTest(R"(
+    MyObj* target() {
+      MyObj local_obj;
+      MyObj* p1 = &local_obj;
+      POINT(p_p1);
+      MyObj* p2 = p1;
+      POINT(p_p2);
+      return p2;
+    }
+  )");
+  EXPECT_THAT(Origin("p2"), HasLoansTo({"local_obj"}, "p_p2"));
+  EXPECT_THAT(Origins({"p2"}), MustBeLiveAt("p_p2"));
+  EXPECT_THAT(Origins({"p1"}), testing::Not(AreLiveAt("p_p2")));
+
+  EXPECT_THAT(Origin("p1"), HasLoansTo({"local_obj"}, "p_p1"));
+  EXPECT_THAT(Origins({"p1"}), MustBeLiveAt("p_p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, MultipleAssignmentMultipleReturn) {
+  SetupTest(R"(
+    MyObj* target(bool c1, bool c2) {
+    static MyObj global_obj;
+      MyObj local_obj1;
+      MyObj local_obj2;
+      MyObj* p = nullptr;
+      POINT(before_cond);
+      if(c1){
+        p = &local_obj1;
+        POINT(after_c1);
+        return p;
+      }
+      else if(c2){
+        p = &local_obj2;
+        POINT(after_c2);
+        return p;
+      }
+      p = &global_obj;
+      POINT(after_cond);
+      return p;
+    }
+  )");
+  // Expect two permissive warnings here in each block
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_cond"));
+  EXPECT_THAT(Origin("p"), HasLoansTo({"global_obj"}, "after_cond"));
+
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c2"));
+  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj2"}, "after_c2"));
+
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c1"));
+  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj1"}, "after_c1"));
+
+  EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_cond"));
+  EXPECT_THAT(NoOrigins(), AreLiveAt("before_cond"));
+}
+
+TEST_F(LifetimeAnalysisTest, MultipleAssignmentsSingleReturn) {
+  SetupTest(R"(
+    MyObj* target(bool c1, bool c2) {
+    static MyObj global_obj;
+      MyObj local_obj1;
+      MyObj local_obj2;
+      MyObj* p = nullptr;
+      POINT(before_cond);
+      if(c1){
+        p = &local_obj1;
+        POINT(after_c1);
+      }
+      else if(c2){
+        p = &local_obj2;
+        POINT(after_c2);
+      }
+      else{
+      p = &global_obj;
+      POINT(after_else);
+      }
+      
+      POINT(after_cond);
+      return p;
+    }
+  )");
+  // Expect permissive warning at return statement with both local_obj1 and
+  // local_obj2 as culprit loans
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_cond"));
+  EXPECT_THAT(
+      Origin("p"),
+      HasLoansTo({"global_obj", "local_obj2", "local_obj1"}, "after_cond"));
+
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_else"));
+  EXPECT_THAT(Origin("p"), HasLoansTo({"global_obj"}, "after_else"));
+
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c2"));
+  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj2"}, "after_c2"));
+
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c1"));
+  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj1"}, "after_c1"));
+
+  EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_cond"));
+  EXPECT_THAT(NoOrigins(), AreLiveAt("before_cond"));
+}
+
+TEST_F(LifetimeAnalysisTest, UseAfterScopeThenReturn) {
+  SetupTest(R"(
+    MyObj* target() {
+      MyObj* p;
+      {
+        MyObj local_obj;
+        p = &local_obj;
+        POINT(p1);
+      }
+      POINT(p2);
+      return p;
+    }
+  )");
+  // Expect a Use-after-scope warning because `p` in `return p` is a use even
+  // before return
+  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p2"));
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
+
+  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1"));
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) {
+  SetupTest(R"(
+    MyObj* target(bool c) {
+      MyObj* p;
+      static MyObj global_obj;
+      {
+        MyObj local_obj;
+        p = &local_obj;
+        if(c){
+          POINT(p1);
+          return p;
+        }
+
+      }
+      POINT(p2);
+      return &global_obj;
+    }
+  )");
+  // Expect a return stack address warning as return is before use after scope
+  EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));
+
+  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1"));
+  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
+}
+
 } // anonymous namespace
 } // namespace clang::lifetimes::internal

>From 1e51cd0de2c302e9d752ab0aaa0778957ff3d82a Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 03/12] Adding use-after-return in Lifetime Analysis

---
 clang/lib/Sema/AnalysisBasedWarnings.cpp | 404 +++++++++++------------
 1 file changed, 202 insertions(+), 202 deletions(-)

diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 08b621f39441c..b12bceca44df9 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -65,61 +65,61 @@ using namespace clang;
 //===----------------------------------------------------------------------===//
 
 namespace {
-class UnreachableCodeHandler : public reachable_code::Callback {
-  Sema &S;
-  SourceRange PreviousSilenceableCondVal;
-
-public:
-  UnreachableCodeHandler(Sema &s) : S(s) {}
-
-  void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L,
-                         SourceRange SilenceableCondVal, SourceRange R1,
-                         SourceRange R2, bool HasFallThroughAttr) override {
-    // If the diagnosed code is `[[fallthrough]];` and
-    // `-Wunreachable-code-fallthrough` is  enabled, suppress `code will never
-    // be executed` warning to avoid generating diagnostic twice
-    if (HasFallThroughAttr &&
-        !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr,
-                                      SourceLocation()))
-      return;
+  class UnreachableCodeHandler : public reachable_code::Callback {
+    Sema &S;
+    SourceRange PreviousSilenceableCondVal;
+
+  public:
+    UnreachableCodeHandler(Sema &s) : S(s) {}
+
+    void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L,
+                           SourceRange SilenceableCondVal, SourceRange R1,
+                           SourceRange R2, bool HasFallThroughAttr) override {
+      // If the diagnosed code is `[[fallthrough]];` and
+      // `-Wunreachable-code-fallthrough` is  enabled, suppress `code will never
+      // be executed` warning to avoid generating diagnostic twice
+      if (HasFallThroughAttr &&
+          !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr,
+                                        SourceLocation()))
+        return;
 
-    // Avoid reporting multiple unreachable code diagnostics that are
-    // triggered by the same conditional value.
+      // Avoid reporting multiple unreachable code diagnostics that are
+      // triggered by the same conditional value.
       if (PreviousSilenceableCondVal.isValid() &&
           SilenceableCondVal.isValid() &&
-        PreviousSilenceableCondVal == SilenceableCondVal)
-      return;
-    PreviousSilenceableCondVal = SilenceableCondVal;
+          PreviousSilenceableCondVal == SilenceableCondVal)
+        return;
+      PreviousSilenceableCondVal = SilenceableCondVal;
 
-    unsigned diag = diag::warn_unreachable;
-    switch (UK) {
-    case reachable_code::UK_Break:
-      diag = diag::warn_unreachable_break;
-      break;
-    case reachable_code::UK_Return:
-      diag = diag::warn_unreachable_return;
-      break;
-    case reachable_code::UK_Loop_Increment:
-      diag = diag::warn_unreachable_loop_increment;
-      break;
-    case reachable_code::UK_Other:
-      break;
-    }
+      unsigned diag = diag::warn_unreachable;
+      switch (UK) {
+        case reachable_code::UK_Break:
+          diag = diag::warn_unreachable_break;
+          break;
+        case reachable_code::UK_Return:
+          diag = diag::warn_unreachable_return;
+          break;
+        case reachable_code::UK_Loop_Increment:
+          diag = diag::warn_unreachable_loop_increment;
+          break;
+        case reachable_code::UK_Other:
+          break;
+      }
 
-    S.Diag(L, diag) << R1 << R2;
+      S.Diag(L, diag) << R1 << R2;
 
-    SourceLocation Open = SilenceableCondVal.getBegin();
-    if (Open.isValid()) {
-      SourceLocation Close = SilenceableCondVal.getEnd();
-      Close = S.getLocForEndOfToken(Close);
-      if (Close.isValid()) {
-        S.Diag(Open, diag::note_unreachable_silence)
+      SourceLocation Open = SilenceableCondVal.getBegin();
+      if (Open.isValid()) {
+        SourceLocation Close = SilenceableCondVal.getEnd();
+        Close = S.getLocForEndOfToken(Close);
+        if (Close.isValid()) {
+          S.Diag(Open, diag::note_unreachable_silence)
             << FixItHint::CreateInsertion(Open, "/* DISABLES CODE */ (")
             << FixItHint::CreateInsertion(Close, ")");
+        }
       }
     }
-  }
-};
+  };
 } // anonymous namespace
 
 /// CheckUnreachable - Check for unreachable code.
@@ -388,9 +388,9 @@ static void checkThrowInNonThrowingFunc(Sema &S, const FunctionDecl *FD,
   if (BodyCFG->getExit().pred_empty())
     return;
   visitReachableThrows(BodyCFG, [&](const CXXThrowExpr *Throw, CFGBlock &Block) {
-        if (throwEscapes(S, Throw, Block, BodyCFG))
-          EmitDiagForCXXThrowInNonThrowingFunc(S, Throw->getThrowLoc(), FD);
-      });
+    if (throwEscapes(S, Throw, Block, BodyCFG))
+      EmitDiagForCXXThrowInNonThrowingFunc(S, Throw->getThrowLoc(), FD);
+  });
 }
 
 static bool isNoexcept(const FunctionDecl *FD) {
@@ -803,7 +803,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
   }
   else if (isa<BlockDecl>(D)) {
     if (const FunctionType *FT =
-            BlockType->getPointeeType()->getAs<FunctionType>()) {
+          BlockType->getPointeeType()->getAs<FunctionType>()) {
       if (FT->getReturnType()->isVoidType())
         ReturnsVoid = true;
       if (FT->getNoReturnAttr())
@@ -815,7 +815,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
 
   // Short circuit for compilation speed.
   if (CD.checkDiagnostics(Diags, ReturnsVoid, HasNoReturn))
-    return;
+      return;
   SourceLocation LBrace = Body->getBeginLoc(), RBrace = Body->getEndLoc();
 
   // cpu_dispatch functions permit empty function bodies for ICC compatibility.
@@ -892,7 +892,7 @@ class ContainsReference : public ConstEvaluatedExprVisitor<ContainsReference> {
   typedef ConstEvaluatedExprVisitor<ContainsReference> Inherited;
 
   ContainsReference(ASTContext &Context, const DeclRefExpr *Needle)
-      : Inherited(Context), FoundReference(false), Needle(Needle) {}
+    : Inherited(Context), FoundReference(false), Needle(Needle) {}
 
   void VisitExpr(const Expr *E) {
     // Stop evaluating if we already have a reference.
@@ -1016,7 +1016,7 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use,
     int RemoveDiagKind = -1;
     const char *FixitStr =
         S.getLangOpts().CPlusPlus ? (I->Output ? "true" : "false")
-                               : (I->Output ? "1" : "0");
+                                  : (I->Output ? "1" : "0");
     FixItHint Fixit1, Fixit2;
 
     switch (Term ? Term->getStmtClass() : Stmt::DeclStmtClass) {
@@ -1124,7 +1124,7 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use,
         << IsCapturedByBlock << User->getSourceRange();
     if (RemoveDiagKind != -1)
       S.Diag(Fixit1.RemoveRange.getBegin(), diag::note_uninit_fixit_remove_cond)
-          << RemoveDiagKind << Str << I->Output << Fixit1 << Fixit2;
+        << RemoveDiagKind << Str << I->Output << Fixit1 << Fixit2;
 
     Diagnosed = true;
   }
@@ -1294,32 +1294,32 @@ class FallthroughMapper : public DynamicRecursiveASTVisitor {
             // Don't care about other unreachable statements.
           }
         }
-        // If there are no unreachable statements, this may be a special
-        // case in CFG:
-        // case X: {
-        //    A a;  // A has a destructor.
-        //    break;
-        // }
-        // // <<<< This place is represented by a 'hanging' CFG block.
-        // case Y:
-        continue;
+          // If there are no unreachable statements, this may be a special
+          // case in CFG:
+          // case X: {
+          //    A a;  // A has a destructor.
+          //    break;
+          // }
+          // // <<<< This place is represented by a 'hanging' CFG block.
+          // case Y:
+          continue;
       }
 
-      const Stmt *LastStmt = getLastStmt(*P);
-      if (const AttributedStmt *AS = asFallThroughAttr(LastStmt)) {
-        markFallthroughVisited(AS);
-        ++AnnotatedCnt;
-        continue; // Fallthrough annotation, good.
-      }
+        const Stmt *LastStmt = getLastStmt(*P);
+        if (const AttributedStmt *AS = asFallThroughAttr(LastStmt)) {
+          markFallthroughVisited(AS);
+          ++AnnotatedCnt;
+          continue; // Fallthrough annotation, good.
+        }
 
-      if (!LastStmt) { // This block contains no executable statements.
-        // Traverse its predecessors.
-        std::copy(P->pred_begin(), P->pred_end(),
-                  std::back_inserter(BlockQueue));
-        continue;
-      }
+        if (!LastStmt) { // This block contains no executable statements.
+          // Traverse its predecessors.
+          std::copy(P->pred_begin(), P->pred_end(),
+                    std::back_inserter(BlockQueue));
+          continue;
+        }
 
-      ++UnannotatedCnt;
+        ++UnannotatedCnt;
     }
     return !!UnannotatedCnt;
   }
@@ -1335,48 +1335,48 @@ class FallthroughMapper : public DynamicRecursiveASTVisitor {
     return true;
   }
 
-  // We don't want to traverse local type declarations. We analyze their
-  // methods separately.
-  bool TraverseDecl(Decl *D) override { return true; }
+    // We don't want to traverse local type declarations. We analyze their
+    // methods separately.
+    bool TraverseDecl(Decl *D) override { return true; }
 
-  // We analyze lambda bodies separately. Skip them here.
-  bool TraverseLambdaExpr(LambdaExpr *LE) override {
-    // Traverse the captures, but not the body.
-    for (const auto C : zip(LE->captures(), LE->capture_inits()))
-      TraverseLambdaCapture(LE, &std::get<0>(C), std::get<1>(C));
-    return true;
-  }
+    // We analyze lambda bodies separately. Skip them here.
+    bool TraverseLambdaExpr(LambdaExpr *LE) override {
+      // Traverse the captures, but not the body.
+      for (const auto C : zip(LE->captures(), LE->capture_inits()))
+        TraverseLambdaCapture(LE, &std::get<0>(C), std::get<1>(C));
+      return true;
+    }
 
-private:
+  private:
 
-  static const AttributedStmt *asFallThroughAttr(const Stmt *S) {
-    if (const AttributedStmt *AS = dyn_cast_or_null<AttributedStmt>(S)) {
-      if (hasSpecificAttr<FallThroughAttr>(AS->getAttrs()))
-        return AS;
+    static const AttributedStmt *asFallThroughAttr(const Stmt *S) {
+      if (const AttributedStmt *AS = dyn_cast_or_null<AttributedStmt>(S)) {
+        if (hasSpecificAttr<FallThroughAttr>(AS->getAttrs()))
+          return AS;
+      }
+      return nullptr;
     }
-    return nullptr;
-  }
 
-  static const Stmt *getLastStmt(const CFGBlock &B) {
-    if (const Stmt *Term = B.getTerminatorStmt())
-      return Term;
-    for (const CFGElement &Elem : llvm::reverse(B))
-      if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>())
-        return CS->getStmt();
-    // Workaround to detect a statement thrown out by CFGBuilder:
-    //   case X: {} case Y:
-    //   case X: ; case Y:
-    if (const SwitchCase *SW = dyn_cast_or_null<SwitchCase>(B.getLabel()))
-      if (!isa<SwitchCase>(SW->getSubStmt()))
-        return SW->getSubStmt();
+    static const Stmt *getLastStmt(const CFGBlock &B) {
+      if (const Stmt *Term = B.getTerminatorStmt())
+        return Term;
+      for (const CFGElement &Elem : llvm::reverse(B))
+        if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>())
+          return CS->getStmt();
+      // Workaround to detect a statement thrown out by CFGBuilder:
+      //   case X: {} case Y:
+      //   case X: ; case Y:
+      if (const SwitchCase *SW = dyn_cast_or_null<SwitchCase>(B.getLabel()))
+        if (!isa<SwitchCase>(SW->getSubStmt()))
+          return SW->getSubStmt();
 
-    return nullptr;
-  }
+      return nullptr;
+    }
 
-  bool FoundSwitchStatements;
-  AttrStmts FallthroughStmts;
-  Sema &S;
-  llvm::SmallPtrSet<const CFGBlock *, 16> ReachableBlocks;
+    bool FoundSwitchStatements;
+    AttrStmts FallthroughStmts;
+    Sema &S;
+    llvm::SmallPtrSet<const CFGBlock *, 16> ReachableBlocks;
 };
 } // anonymous namespace
 
@@ -1384,7 +1384,7 @@ static StringRef getFallthroughAttrSpelling(Preprocessor &PP,
                                             SourceLocation Loc) {
   TokenValue FallthroughTokens[] = {
     tok::l_square, tok::l_square,
-                                    PP.getIdentifierInfo("fallthrough"),
+    PP.getIdentifierInfo("fallthrough"),
     tok::r_square, tok::r_square
   };
 
@@ -1513,7 +1513,7 @@ static void diagnoseRepeatedUseOfWeak(Sema &S,
   typedef sema::FunctionScopeInfo::WeakObjectUseMap WeakObjectUseMap;
   typedef sema::FunctionScopeInfo::WeakUseVector WeakUseVector;
   typedef std::pair<const Stmt *, WeakObjectUseMap::const_iterator>
-      StmtUsesPair;
+  StmtUsesPair;
 
   ASTContext &Ctx = S.getASTContext();
 
@@ -1977,7 +1977,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
                : getNotes();
   }
 
-public:
+ public:
   ThreadSafetyReporter(Sema &S, SourceLocation FL, SourceLocation FEL)
     : S(S), FunLocation(FL), FunEndLocation(FEL),
       CurrentFunction(nullptr), Verbose(false) {}
@@ -2075,18 +2075,18 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
                                  bool ReentrancyMismatch) override {
     unsigned DiagID = 0;
     switch (LEK) {
-    case LEK_LockedSomePredecessors:
-      DiagID = diag::warn_lock_some_predecessors;
-      break;
-    case LEK_LockedSomeLoopIterations:
-      DiagID = diag::warn_expecting_lock_held_on_loop;
-      break;
-    case LEK_LockedAtEndOfFunction:
-      DiagID = diag::warn_no_unlock;
-      break;
-    case LEK_NotLockedAtEndOfFunction:
-      DiagID = diag::warn_expecting_locked;
-      break;
+      case LEK_LockedSomePredecessors:
+        DiagID = diag::warn_lock_some_predecessors;
+        break;
+      case LEK_LockedSomeLoopIterations:
+        DiagID = diag::warn_expecting_lock_held_on_loop;
+        break;
+      case LEK_LockedAtEndOfFunction:
+        DiagID = diag::warn_no_unlock;
+        break;
+      case LEK_NotLockedAtEndOfFunction:
+        DiagID = diag::warn_expecting_locked;
+        break;
     }
     if (LocEndOfScope.isInvalid())
       LocEndOfScope = FunEndLocation;
@@ -2132,7 +2132,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
       break;
     }
     PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID)
-                                         << D << getLockKindFromAccessKind(AK));
+      << D << getLockKindFromAccessKind(AK));
     Warnings.emplace_back(std::move(Warning), getNotes());
   }
 
@@ -2143,39 +2143,39 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
     unsigned DiagID = 0;
     if (PossibleMatch) {
       switch (POK) {
-      case POK_VarAccess:
-        DiagID = diag::warn_variable_requires_lock_precise;
-        break;
-      case POK_VarDereference:
-        DiagID = diag::warn_var_deref_requires_lock_precise;
-        break;
-      case POK_FunctionCall:
-        DiagID = diag::warn_fun_requires_lock_precise;
-        break;
-      case POK_PassByRef:
-        DiagID = diag::warn_guarded_pass_by_reference;
-        break;
-      case POK_PtPassByRef:
-        DiagID = diag::warn_pt_guarded_pass_by_reference;
-        break;
-      case POK_ReturnByRef:
-        DiagID = diag::warn_guarded_return_by_reference;
-        break;
-      case POK_PtReturnByRef:
-        DiagID = diag::warn_pt_guarded_return_by_reference;
-        break;
-      case POK_PassPointer:
-        DiagID = diag::warn_guarded_pass_pointer;
-        break;
-      case POK_PtPassPointer:
-        DiagID = diag::warn_pt_guarded_pass_pointer;
-        break;
-      case POK_ReturnPointer:
-        DiagID = diag::warn_guarded_return_pointer;
-        break;
-      case POK_PtReturnPointer:
-        DiagID = diag::warn_pt_guarded_return_pointer;
-        break;
+        case POK_VarAccess:
+          DiagID = diag::warn_variable_requires_lock_precise;
+          break;
+        case POK_VarDereference:
+          DiagID = diag::warn_var_deref_requires_lock_precise;
+          break;
+        case POK_FunctionCall:
+          DiagID = diag::warn_fun_requires_lock_precise;
+          break;
+        case POK_PassByRef:
+          DiagID = diag::warn_guarded_pass_by_reference;
+          break;
+        case POK_PtPassByRef:
+          DiagID = diag::warn_pt_guarded_pass_by_reference;
+          break;
+        case POK_ReturnByRef:
+          DiagID = diag::warn_guarded_return_by_reference;
+          break;
+        case POK_PtReturnByRef:
+          DiagID = diag::warn_pt_guarded_return_by_reference;
+          break;
+        case POK_PassPointer:
+          DiagID = diag::warn_guarded_pass_pointer;
+          break;
+        case POK_PtPassPointer:
+          DiagID = diag::warn_pt_guarded_pass_pointer;
+          break;
+        case POK_ReturnPointer:
+          DiagID = diag::warn_guarded_return_pointer;
+          break;
+        case POK_PtReturnPointer:
+          DiagID = diag::warn_pt_guarded_return_pointer;
+          break;
       }
       PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
                                                        << D
@@ -2191,39 +2191,39 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
         Warnings.emplace_back(std::move(Warning), getNotes(Note));
     } else {
       switch (POK) {
-      case POK_VarAccess:
-        DiagID = diag::warn_variable_requires_lock;
-        break;
-      case POK_VarDereference:
-        DiagID = diag::warn_var_deref_requires_lock;
-        break;
-      case POK_FunctionCall:
-        DiagID = diag::warn_fun_requires_lock;
-        break;
-      case POK_PassByRef:
-        DiagID = diag::warn_guarded_pass_by_reference;
-        break;
-      case POK_PtPassByRef:
-        DiagID = diag::warn_pt_guarded_pass_by_reference;
-        break;
-      case POK_ReturnByRef:
-        DiagID = diag::warn_guarded_return_by_reference;
-        break;
-      case POK_PtReturnByRef:
-        DiagID = diag::warn_pt_guarded_return_by_reference;
-        break;
-      case POK_PassPointer:
-        DiagID = diag::warn_guarded_pass_pointer;
-        break;
-      case POK_PtPassPointer:
-        DiagID = diag::warn_pt_guarded_pass_pointer;
-        break;
-      case POK_ReturnPointer:
-        DiagID = diag::warn_guarded_return_pointer;
-        break;
-      case POK_PtReturnPointer:
-        DiagID = diag::warn_pt_guarded_return_pointer;
-        break;
+        case POK_VarAccess:
+          DiagID = diag::warn_variable_requires_lock;
+          break;
+        case POK_VarDereference:
+          DiagID = diag::warn_var_deref_requires_lock;
+          break;
+        case POK_FunctionCall:
+          DiagID = diag::warn_fun_requires_lock;
+          break;
+        case POK_PassByRef:
+          DiagID = diag::warn_guarded_pass_by_reference;
+          break;
+        case POK_PtPassByRef:
+          DiagID = diag::warn_pt_guarded_pass_by_reference;
+          break;
+        case POK_ReturnByRef:
+          DiagID = diag::warn_guarded_return_by_reference;
+          break;
+        case POK_PtReturnByRef:
+          DiagID = diag::warn_pt_guarded_return_by_reference;
+          break;
+        case POK_PassPointer:
+          DiagID = diag::warn_guarded_pass_pointer;
+          break;
+        case POK_PtPassPointer:
+          DiagID = diag::warn_pt_guarded_pass_pointer;
+          break;
+        case POK_ReturnPointer:
+          DiagID = diag::warn_guarded_return_pointer;
+          break;
+        case POK_PtReturnPointer:
+          DiagID = diag::warn_pt_guarded_return_pointer;
+          break;
       }
       PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
                                                        << D
@@ -2241,7 +2241,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
                              SourceLocation Loc) override {
     PartialDiagnosticAt Warning(Loc,
         S.PDiag(diag::warn_acquire_requires_negative_cap)
-                 << Kind << LockName << Neg);
+        << Kind << LockName << Neg);
     Warnings.emplace_back(std::move(Warning), getNotes());
   }
 
@@ -2423,7 +2423,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
 
 public:
   UnsafeBufferUsageReporter(Sema &S, bool SuggestSuggestions)
-      : S(S), SuggestSuggestions(SuggestSuggestions) {}
+    : S(S), SuggestSuggestions(SuggestSuggestions) {}
 
   void handleUnsafeOperation(const Stmt *Operation, bool IsRelatedToDecl,
                              ASTContext &Ctx) override {
@@ -2826,7 +2826,7 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
 } // namespace clang::lifetimes
 
 void clang::sema::AnalysisBasedWarnings::IssueWarnings(
-    TranslationUnitDecl *TU) {
+     TranslationUnitDecl *TU) {
   if (!TU)
     return; // This is unexpected, give up quietly.
 
@@ -2939,13 +2939,13 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
     AC.getCFGBuildOptions().setAllAlwaysAdd();
   } else {
     AC.getCFGBuildOptions()
-        .setAlwaysAdd(Stmt::BinaryOperatorClass)
-        .setAlwaysAdd(Stmt::CompoundAssignOperatorClass)
-        .setAlwaysAdd(Stmt::BlockExprClass)
-        .setAlwaysAdd(Stmt::CStyleCastExprClass)
-        .setAlwaysAdd(Stmt::DeclRefExprClass)
-        .setAlwaysAdd(Stmt::ImplicitCastExprClass)
-        .setAlwaysAdd(Stmt::UnaryOperatorClass);
+      .setAlwaysAdd(Stmt::BinaryOperatorClass)
+      .setAlwaysAdd(Stmt::CompoundAssignOperatorClass)
+      .setAlwaysAdd(Stmt::BlockExprClass)
+      .setAlwaysAdd(Stmt::CStyleCastExprClass)
+      .setAlwaysAdd(Stmt::DeclRefExprClass)
+      .setAlwaysAdd(Stmt::ImplicitCastExprClass)
+      .setAlwaysAdd(Stmt::UnaryOperatorClass);
   }
 
   // Install the logical handler.

>From 9411d9c6acb0e36d432d2334183b665c8d528483 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 04/12] Adding use-after-return in Lifetime Analysis

---
 clang/test/Sema/warn-lifetime-safety.cpp | 66 ++++++++++++++++--------
 1 file changed, 45 insertions(+), 21 deletions(-)

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 55bf6253a8c82..adeace7c37b9b 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -416,67 +416,73 @@ MyObj* conditional_assign_unconditional_return(MyObj safe, bool c){
   return p;      // expected-note {{returned here}}
 }
 
-MyObj* conditional_assign_both_branches(MyObj safe, bool c){
+View conditional_assign_both_branches(MyObj safe, bool c){
 
   MyObj s;
-  MyObj* p = nullptr;
+  View p;
   if (c) {
-    p = &s;     // expected-warning {{returning reference to stack allocated object}}
+    p = s;      // expected-warning {{returning reference to stack allocated object}}
   } else {
-    p = &safe;
+    p = safe;
   }
   return p;     // expected-note {{returned here}}
 
 }
 
-MyObj* reassign_safe_to_local(MyObj safe){
+View reassign_safe_to_local(MyObj safe){
   MyObj local;
-  MyObj* p = &safe;
+  View p = safe;
 
-  p = &local;   // expected-warning {{returning reference to stack allocated object}}
+  p = local;    // expected-warning {{returning reference to stack allocated object}}
   return p;     // expected-note {{returned here}}
 }
 
-MyObj* pointer_chain_to_local(){
+View pointer_chain_to_local(){
   MyObj local;
-  MyObj* p1 = &local; // expected-warning {{returning reference to stack allocated object}}
+  View p1 = local;     // expected-warning {{returning reference to stack allocated object}}
 
-  MyObj* p2 = p1; 
+  View p2 = p1; 
 
   return p2;          // expected-note {{returned here}}
 }
 
-MyObj* multiple_assign_multiple_return(MyObj safe, bool c1, bool c2){
+View multiple_assign_multiple_return(MyObj safe, bool c1, bool c2){
   MyObj local1;
   MyObj local2;
-  MyObj* p = nullptr;
+  View p;
   if(c1){
-    p = &local1;      // expected-warning {{returning reference to stack allocated object}}
+    p = local1;       // expected-warning {{returning reference to stack allocated object}}
     return p;         // expected-note {{returned here}}
   }
   else if(c2){
-    p = &local2;      // expected-warning {{returning reference to stack allocated object}}
+    p = local2;       // expected-warning {{returning reference to stack allocated object}}
     return p;         // expected-note {{returned here}}
   }
-  p = &safe;
+  p = safe;
   return p;
 }
 
-MyObj* multiple_assign_single_return(MyObj safe, bool c1, bool c2){
+View multiple_assign_single_return(MyObj safe, bool c1, bool c2){
   MyObj local1;
   MyObj local2;
-  MyObj* p = nullptr;
+  View p;
   if(c1){
-    p = &local1;     // expected-warning {{returning reference to stack allocated object}}
+    p = local1;      // expected-warning {{returning reference to stack allocated object}}
   }
   else if(c2){
-    p = &local2;     // expected-warning {{returning reference to stack allocated object}}
+    p = local2;      // expected-warning {{returning reference to stack allocated object}}
   }
   else{
-  p = &safe;
+    p = safe;
   }
   
-  return p;         // expected-note {{returned here}} // expected-note {{returned here}}
+  return p;         // expected-note 2 {{returned here}}
+}
+
+View direct_return_of_local(){
+  MyObj stack;      
+  return stack;     // expected-warning {{returning reference to stack allocated object}}
+                    // expected-note at -1 {{returned here}}
 }
 
 //===----------------------------------------------------------------------===//
@@ -700,3 +706,21 @@ void lifetimebound_ctor() {
   }
   (void)v;
 }
+
+View lifetimebound_return_of_local(){
+  MyObj stack;
+  return Identity(stack); // expected-warning {{returning reference to stack allocated object}}
+                          // expected-note at -1 {{returned here}}
+}
+
+// FIXME: The analysis does not currently model the lifetime of by-value
+// parameters, so it fails to diagnose this UAR violation.
+View lifetimebound_return_of_by_value_param(MyObj stack_param) {
+  return Identity(stack_param); 
+}
+
+// FIXME: The analysis does not currently model the lifetime of by-value
+// parameters, so it fails to diagnose this UAF violation.
+void uaf_from_by_value_param_failing(MyObj param, View* out_p) {
+  *out_p = Identity(param);
+}
\ No newline at end of file

>From 90a831154dd61729aba2015c53b5e9fd4e7f83b1 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 05/12] Adding use-after-return in Lifetime Analysis

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

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 64ca5439cc264..5dc605fbf407b 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -63,11 +63,9 @@ class LifetimeChecker {
       : LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM),
         Reporter(Reporter) {
     for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) {
-      llvm::SmallVector<const ExpireFact *> BlockExpires;
       for (const Fact *F : FactMgr.getFacts(B)) {
         if (const auto *EF = F->getAs<ExpireFact>()) {
           checkExpiry(EF);
-          BlockExpires.push_back(EF);
         }
       }
     }
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index adeace7c37b9b..a07bfe61f3d26 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -485,6 +485,13 @@ View direct_return_of_local(){
                     // expected-note at -1 {{returned here}}
 }
 
+MyObj& reference_return_of_local(){
+  MyObj stack;      
+  return stack;     // expected-warning {{returning reference to stack allocated object}}
+                    // expected-note at -1 {{returned here}}
+                    // expected-warning at -2 {{reference to stack memory associated with local variable 'stack' returned}}
+}
+
 //===----------------------------------------------------------------------===//
 // Use-After-Scope & Use-After-Return (Return-Stack-Address) Combined
 // These are cases where the diagnostic kind is determined by location
@@ -564,6 +571,7 @@ MyObj* safe_return(MyObj safe){
 //===----------------------------------------------------------------------===//
 
 View Identity(View v [[clang::lifetimebound]]);
+const MyObj& IdentityRef(const MyObj& obj [[clang::lifetimebound]]);
 View Choose(bool cond, View a [[clang::lifetimebound]], View b [[clang::lifetimebound]]);
 MyObj* GetPointer(const MyObj& obj [[clang::lifetimebound]]);
 
@@ -713,6 +721,13 @@ View lifetimebound_return_of_local(){
                           // expected-note at -1 {{returned here}}
 }
 
+const MyObj& lifetimebound_return_ref_to_local() {
+  MyObj stack;
+  return IdentityRef(stack); // expected-warning {{returning reference to stack allocated object}}
+                             // expected-note at -1 {{returned here}}
+                             // expected-warning at -2 {{reference to stack memory associated with local variable 'stack' returned}}
+}
+
 // FIXME: The analysis does not currently model the lifetime of by-value
 // parameters, so it fails to diagnose this UAR violation.
 View lifetimebound_return_of_by_value_param(MyObj stack_param) {

>From 58fd6c81428c0e0fd269c2aa6684cdfdd9089728 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 06/12] Adding use-after-return in Lifetime Analysis

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp        | 11 ++++-------
 clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp |  3 ---
 2 files changed, 4 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 5dc605fbf407b..92ab1bb8f9e33 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -62,13 +62,10 @@ class LifetimeChecker {
                   AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter)
       : LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM),
         Reporter(Reporter) {
-    for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) {
-      for (const Fact *F : FactMgr.getFacts(B)) {
-        if (const auto *EF = F->getAs<ExpireFact>()) {
+    for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
+      for (const Fact *F : FactMgr.getFacts(B))
+        if (const auto *EF = F->getAs<ExpireFact>())
           checkExpiry(EF);
-        }
-      }
-    }
     issuePendingWarnings();
   }
 
@@ -107,7 +104,7 @@ class LifetimeChecker {
     if (LastConf >= CurConfidence)
       return;
     FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
-                                     /*BestCausingFact*/ BestCausingFact,
+                                     /*BestCausingFact=*/BestCausingFact,
                                      /*ConfidenceLevel=*/CurConfidence};
   }
 
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 72a1b52786d54..39e7882655711 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -59,7 +59,6 @@ void FactsGenerator::run() {
   for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
     CurrentBlockFacts.clear();
     EscapesInCurrentBlock.clear();
-    EscapesInCurrentBlock.clear();
     for (unsigned I = 0; I < Block->size(); ++I) {
       const CFGElement &Element = Block->Elements[I];
       if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
@@ -68,8 +67,6 @@ void FactsGenerator::run() {
                    Element.getAs<CFGAutomaticObjDtor>())
         handleDestructor(*DtorOpt);
     }
-    CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
-                             EscapesInCurrentBlock.end());
     CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
                              EscapesInCurrentBlock.end());
     FactMgr.addBlockFacts(Block, CurrentBlockFacts);

>From 87714fc0180c1aff79c6456d76c244b4781bbeed Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 07/12] Adding use-after-return in Lifetime Analysis

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  |  2 +-
 .../Analyses/LifetimeSafety/FactsGenerator.h  |  4 +-
 .../Analyses/LifetimeSafety/LiveOrigins.h     |  9 ++-
 .../clang/Basic/DiagnosticSemaKinds.td        |  4 +-
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 17 +++--
 .../Analysis/LifetimeSafety/LiveOrigins.cpp   | 49 ++++++-------
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |  3 +-
 clang/test/Sema/warn-lifetime-safety.cpp      | 70 ++++++++-----------
 .../unittests/Analysis/LifetimeSafetyTest.cpp |  3 +-
 9 files changed, 75 insertions(+), 86 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 1b87eee4a8d17..bc5ce65b4a4e7 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -42,7 +42,7 @@ class Fact {
     Use,
     /// A marker for a specific point in the code, for testing.
     TestPoint,
-    // Indicates that an origin escapes the function scope (e.g., via return).
+    /// Indicates that an origin escapes the function scope (e.g., via return).
     OriginEscapes,
   };
 
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 21d160493289e..72ea7267b90e5 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -93,8 +93,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   FactManager &FactMgr;
   AnalysisDeclContext &AC;
   llvm::SmallVector<Fact *> CurrentBlockFacts;
-  // Collect origins that escape the function in this block. These are handled
-  // at the end of the block to ensure `OEFs` appear after `Expire` facts.
+  // Collect origins that escape the function in this block (OriginEscapesFact),
+  // appended at the end to ensure they appear after ExpireFact entries.
   llvm::SmallVector<Fact *> EscapesInCurrentBlock;
   // To distinguish between reads and writes for use-after-free checks, this map
   // stores the `UseFact` for each `DeclRefExpr`. We initially identify all
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
index dae97501c0b53..f23236ba8a3d8 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
@@ -43,7 +43,7 @@ struct LivenessInfo {
   /// multiple uses along different paths, this will point to the use appearing
   /// earlier in the translation unit.
   /// This is 'null' when the origin is not live.
-  const Fact *CausingFact;
+  llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact;
 
   /// The kind of liveness of the origin.
   /// `Must`: The origin is live on all control-flow paths from the current
@@ -57,7 +57,10 @@ struct LivenessInfo {
   LivenessKind Kind;
 
   LivenessInfo() : CausingFact(nullptr), Kind(LivenessKind::Dead) {}
-  LivenessInfo(const Fact *CF, LivenessKind K) : CausingFact(CF), Kind(K) {}
+  LivenessInfo(
+      llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CF,
+      LivenessKind K)
+      : CausingFact(CF), Kind(K) {}
 
   bool operator==(const LivenessInfo &Other) const {
     return CausingFact == Other.CausingFact && Kind == Other.Kind;
@@ -65,7 +68,7 @@ struct LivenessInfo {
   bool operator!=(const LivenessInfo &Other) const { return !(*this == Other); }
 
   void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
-    IDBuilder.AddPointer(CausingFact);
+    IDBuilder.AddPointer(CausingFact.getOpaqueValue());
     IDBuilder.Add(Kind);
   }
 };
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 4eb7263715f4a..f59ae17cb99cb 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10730,11 +10730,11 @@ def warn_lifetime_safety_loan_expires_strict : Warning<
    InGroup<LifetimeSafetyStrict>, DefaultIgnore;
 
 def warn_lifetime_safety_return_stack_addr_permissive
-    : Warning<"returning reference to stack allocated object">,
+    : Warning<"address of stack memory is returned later">,
       InGroup<LifetimeSafetyPermissive>,
       DefaultIgnore;
 def warn_lifetime_safety_return_stack_addr_strict
-    : Warning<"may be returning reference to stack allocated object">,
+    : Warning<"address of stack memory maybe returned later">,
       InGroup<LifetimeSafetyStrict>,
       DefaultIgnore;
 
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 92ab1bb8f9e33..7354d311bfb75 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -44,7 +44,8 @@ namespace {
 /// Struct to store the complete context for a potential lifetime violation.
 struct PendingWarning {
   SourceLocation ExpiryLoc; // Where the loan expired.
-  const Fact *CausingFact;  // If the use is a UseFact or OriginEscapeFact
+  llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
+      CausingFact; // If the use is a UseFact or OriginEscapeFact
   Confidence ConfidenceLevel;
 };
 
@@ -84,7 +85,8 @@ class LifetimeChecker {
     LoanID ExpiredLoan = EF->getLoanID();
     LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF);
     Confidence CurConfidence = Confidence::None;
-    const Fact *BestCausingFact = nullptr;
+    llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
+        BestCausingFact = nullptr;
 
     for (auto &[OID, LiveInfo] : Origins) {
       LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
@@ -114,17 +116,20 @@ class LifetimeChecker {
     for (const auto &[LID, Warning] : FinalWarningsMap) {
       const Loan &L = FactMgr.getLoanMgr().getLoan(LID);
       const Expr *IssueExpr = L.IssueExpr;
-      const Fact *CausingFact = Warning.CausingFact;
+      llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
+          CausingFact = Warning.CausingFact;
       Confidence Confidence = Warning.ConfidenceLevel;
       SourceLocation ExpiryLoc = Warning.ExpiryLoc;
 
-      if (const auto *UF = CausingFact->getAs<UseFact>()) {
+      if (const auto *UF = CausingFact.dyn_cast<const UseFact *>())
         Reporter->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc,
                                      Confidence);
-      } else if (const auto *OEF = CausingFact->getAs<OriginEscapesFact>()) {
+      else if (const auto *OEF =
+                   CausingFact.dyn_cast<const OriginEscapesFact *>())
         Reporter->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(),
                                        ExpiryLoc, Confidence);
-      }
+      else
+        llvm_unreachable("Unhandled CausingFact type");
     }
   }
 };
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index 02a079184723c..65727c819830e 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -53,14 +53,15 @@ struct Lattice {
   }
 };
 
-static SourceLocation GetFactLoc(const Fact &F) {
-  if (const auto *UF = F.getAs<UseFact>()) {
+static SourceLocation
+GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) {
+  if (const auto *UF = F.dyn_cast<const UseFact *>())
     return UF->getUseExpr()->getExprLoc();
-  }
-  if (const auto *OEF = F.getAs<OriginEscapesFact>()) {
+
+  if (const auto *OEF = F.dyn_cast<const OriginEscapesFact *>())
     return OEF->getEscapeExpr()->getExprLoc();
-  }
-  return SourceLocation(); // Invalid SourceLocation
+
+  llvm_unreachable("unhandled causing fact in PointerUnion");
 }
 
 /// The analysis that tracks which origins are live, with granular information
@@ -85,24 +86,15 @@ class AnalysisImpl
   Lattice join(Lattice L1, Lattice L2) const {
     LivenessMap Merged = L1.LiveOrigins;
     // Take the earliest Fact to make the join hermetic and commutative.
-    auto CombineCausingFact = [](const Fact &A, const Fact &B) -> const Fact * {
-      SourceLocation LocA = GetFactLoc(A);
-      SourceLocation LocB = GetFactLoc(B);
-
-      bool aValid = LocA.isValid();
-      bool bValid = LocB.isValid();
-
-      if (aValid && bValid) {
-        if (LocA < LocB)
-          return &A;
-        if (LocB < LocA)
-          return &B;
-      } else if (aValid) {
-        return &A;
-      } else if (bValid) {
-        return &B;
-      }
-      return &A < &B ? &A : &B;
+    auto CombineCausingFact =
+        [](llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> A,
+           llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> B)
+        -> llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> {
+      if (!A)
+        return B;
+      if (!B)
+        return A;
+      return GetFactLoc(A) < GetFactLoc(B) ? A : B;
     };
     auto CombineLivenessKind = [](LivenessKind K1,
                                   LivenessKind K2) -> LivenessKind {
@@ -120,9 +112,8 @@ class AnalysisImpl
         return LivenessInfo(L2->CausingFact, LivenessKind::Maybe);
       if (!L2)
         return LivenessInfo(L1->CausingFact, LivenessKind::Maybe);
-      return LivenessInfo(
-          CombineCausingFact(*L1->CausingFact, *L2->CausingFact),
-          CombineLivenessKind(L1->Kind, L2->Kind));
+      return LivenessInfo(CombineCausingFact(L1->CausingFact, L2->CausingFact),
+                          CombineLivenessKind(L1->Kind, L2->Kind));
     };
     return Lattice(utils::join(
         L1.LiveOrigins, L2.LiveOrigins, Factory, CombineLivenessInfo,
@@ -144,8 +135,8 @@ class AnalysisImpl
                                LivenessInfo(&UF, LivenessKind::Must)));
   }
 
-  // A return operation makes the origin live with definite confidence, as it
-  /// dominates this program point.
+  /// An escaping origin (e.g., via return) makes the origin live with definite
+  /// confidence, as it dominates this program point.
   Lattice transfer(Lattice In, const OriginEscapesFact &OEF) {
     OriginID OID = OEF.getEscapedOriginID();
     return Lattice(Factory.add(In.LiveOrigins, OID,
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index b12bceca44df9..7af85ffaa63d1 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2816,7 +2816,8 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
                : diag::warn_lifetime_safety_return_stack_addr_strict)
         << IssueExpr->getEndLoc();
 
-    S.Diag(EscapeExpr->getBeginLoc(), diag::note_lifetime_safety_returned_here);
+    S.Diag(EscapeExpr->getExprLoc(), diag::note_lifetime_safety_returned_here)
+        << EscapeExpr->getEndLoc();
   }
 
 private:
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index a07bfe61f3d26..d56f4c3ccc6fa 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety -verify %s
+// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety -Wno-dangling -verify %s
 
 struct MyObj {
   int id;
@@ -403,25 +403,24 @@ void loan_from_previous_iteration(MyObj safe, bool condition) {
 
 MyObj* simple_return_stack_address(){
   MyObj s;      
-  MyObj* p = &s; // expected-warning {{returning reference to stack allocated object}}
+  MyObj* p = &s; // expected-warning {{address of stack memory is returned later}}
   return p;      // expected-note {{returned here}}
 }
 
-MyObj* conditional_assign_unconditional_return(MyObj safe, bool c){
+const MyObj* conditional_assign_unconditional_return(const MyObj& safe, bool c){
   MyObj s; 
-  MyObj* p = &safe;
+  const MyObj* p = &safe;
   if(c){
-    p = &s;       // expected-warning {{returning reference to stack allocated object}}
+    p = &s;       // expected-warning {{address of stack memory is returned later}}
   }     
   return p;      // expected-note {{returned here}}
 }
 
-View conditional_assign_both_branches(MyObj safe, bool c){
-
+View conditional_assign_both_branches(const MyObj& safe, bool c){
   MyObj s;
   View p;
   if (c) {
-    p = s;      // expected-warning {{returning reference to stack allocated object}}
+    p = s;      // expected-warning {{address of stack memory is returned later}}
   } else {
     p = safe;
   }
@@ -429,67 +428,62 @@ View conditional_assign_both_branches(MyObj safe, bool c){
 
 }
 
-View reassign_safe_to_local(MyObj safe){
+View reassign_safe_to_local(const MyObj& safe){
   MyObj local;
   View p = safe;
-
-  p = local;    // expected-warning {{returning reference to stack allocated object}}
+  p = local;    // expected-warning {{address of stack memory is returned later}}
   return p;     // expected-note {{returned here}}
 }
 
 View pointer_chain_to_local(){
   MyObj local;
-  View p1 = local;     // expected-warning {{returning reference to stack allocated object}}
-
+  View p1 = local;     // expected-warning {{address of stack memory is returned later}}
   View p2 = p1; 
-
   return p2;          // expected-note {{returned here}}
 }
 
-View multiple_assign_multiple_return(MyObj safe, bool c1, bool c2){
+View multiple_assign_multiple_return(const MyObj& safe, bool c1, bool c2){
   MyObj local1;
   MyObj local2;
   View p;
   if(c1){
-    p = local1;       // expected-warning {{returning reference to stack allocated object}}
+    p = local1;       // expected-warning {{address of stack memory is returned later}}
     return p;         // expected-note {{returned here}}
   }
   else if(c2){
-    p = local2;       // expected-warning {{returning reference to stack allocated object}}
+    p = local2;       // expected-warning {{address of stack memory is returned later}}
     return p;         // expected-note {{returned here}}
   }
   p = safe;
   return p;
 }
 
-View multiple_assign_single_return(MyObj safe, bool c1, bool c2){
+View multiple_assign_single_return(const MyObj& safe, bool c1, bool c2){
   MyObj local1;
   MyObj local2;
   View p;
   if(c1){
-    p = local1;      // expected-warning {{returning reference to stack allocated object}}
+    p = local1;      // expected-warning {{address of stack memory is returned later}}
   }
   else if(c2){
-    p = local2;      // expected-warning {{returning reference to stack allocated object}}
+    p = local2;      // expected-warning {{address of stack memory is returned later}}
   }
   else{
     p = safe;
   }
-  
   return p;         // expected-note 2 {{returned here}}
 }
 
 View direct_return_of_local(){
   MyObj stack;      
-  return stack;     // expected-warning {{returning reference to stack allocated object}}
+  return stack;     // expected-warning {{address of stack memory is returned later}}
                     // expected-note at -1 {{returned here}}
 }
 
 MyObj& reference_return_of_local(){
   MyObj stack;      
-  return stack;     // expected-warning {{returning reference to stack allocated object}}
+  return stack;     // expected-warning {{address of stack memory is returned later}}
                     // expected-note at -1 {{returned here}}
-                    // expected-warning at -2 {{reference to stack memory associated with local variable 'stack' returned}}
 }
 
 //===----------------------------------------------------------------------===//
@@ -506,18 +500,17 @@ MyObj* uaf_before_uar(){
   return p;          // expected-note {{later used here}}
 }
 
-MyObj* uar_before_uaf(MyObj safe, bool c){
-  MyObj* p;
+View uar_before_uaf(const MyObj& safe, bool c){
+  View p;
   {
     MyObj local_obj; 
-    p = &local_obj;  // expected-warning {{returning reference to stack allocated object}}
+    p = local_obj;  // expected-warning {{address of stack memory is returned later}}
     if(c){
       return p;      // expected-note {{returned here}}
     }
-
   }
-  (void)*p;
-  p = &safe;
+  p.use();
+  p = safe;
   return p;
 }
 
@@ -559,10 +552,10 @@ void no_error_loan_from_current_iteration(bool cond) {
   }
 }
 
-MyObj* safe_return(MyObj safe){
+View safe_return(const MyObj& safe){
   MyObj local;
-  MyObj *p = &local;
-  p = &safe;    // p has been reassigned
+  View p = local;
+  p = safe;     // p has been reassigned
   return p;     // This is safe
 }
 
@@ -717,25 +710,22 @@ void lifetimebound_ctor() {
 
 View lifetimebound_return_of_local(){
   MyObj stack;
-  return Identity(stack); // expected-warning {{returning reference to stack allocated object}}
+  return Identity(stack); // expected-warning {{address of stack memory is returned later}}
                           // expected-note at -1 {{returned here}}
 }
 
 const MyObj& lifetimebound_return_ref_to_local() {
   MyObj stack;
-  return IdentityRef(stack); // expected-warning {{returning reference to stack allocated object}}
+  return IdentityRef(stack); // expected-warning {{address of stack memory is returned later}}
                              // expected-note at -1 {{returned here}}
-                             // expected-warning at -2 {{reference to stack memory associated with local variable 'stack' returned}}
 }
 
-// FIXME: The analysis does not currently model the lifetime of by-value
-// parameters, so it fails to diagnose this UAR violation.
+// FIXME: Fails to diagnose UAF when a reference to a by-value param escapes via an out-param.
 View lifetimebound_return_of_by_value_param(MyObj stack_param) {
   return Identity(stack_param); 
 }
 
-// FIXME: The analysis does not currently model the lifetime of by-value
-// parameters, so it fails to diagnose this UAF violation.
+// FIXME: Fails to diagnose UAF when a reference to a by-value param escapes via an out-param.
 void uaf_from_by_value_param_failing(MyObj param, View* out_p) {
   *out_p = Identity(param);
 }
\ No newline at end of file
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 5370cde779a8a..a1897748c0c83 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -1313,7 +1313,7 @@ TEST_F(LifetimeAnalysisTest, PointerChainToLocal) {
 TEST_F(LifetimeAnalysisTest, MultipleAssignmentMultipleReturn) {
   SetupTest(R"(
     MyObj* target(bool c1, bool c2) {
-    static MyObj global_obj;
+      static MyObj global_obj;
       MyObj local_obj1;
       MyObj local_obj2;
       MyObj* p = nullptr;
@@ -1426,7 +1426,6 @@ TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) {
           POINT(p1);
           return p;
         }
-
       }
       POINT(p2);
       return &global_obj;

>From efa7c67ce7ada5d186561cd2fe46349e9e3b1a7f Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 08/12] Adding use-after-return in Lifetime Analysis

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 7354d311bfb75..63c9bf104d940 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -21,7 +21,6 @@
 #include "clang/Analysis/AnalysisDeclContext.h"
 #include "clang/Basic/SourceLocation.h"
 #include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/SmallSet.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/TimeProfiler.h"
 

>From 15baebe8633266f8b8618445ae4bc70af92fb266 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 09/12] Adding use-after-return in Lifetime Analysis

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  |   5 +-
 .../Analyses/LifetimeSafety/FactsGenerator.h  |   2 -
 .../clang/Basic/DiagnosticSemaKinds.td        |   2 +-
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |   5 +-
 clang/lib/Analysis/LifetimeSafety/Facts.cpp   |  12 ++
 .../LifetimeSafety/FactsGenerator.cpp         |   3 +
 clang/test/Sema/warn-lifetime-safety.cpp      |  47 +++--
 .../unittests/Analysis/LifetimeSafetyTest.cpp | 197 +++++++++++-------
 8 files changed, 164 insertions(+), 109 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index bc5ce65b4a4e7..d080bd10e8be6 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -38,11 +38,11 @@ class Fact {
     /// it. Otherwise, the source's loan set is merged into the destination's
     /// loan set.
     OriginFlow,
-    /// An origin escapes the function by flowing into the return value.
+    /// An origin is used (eg. appears as l-value expression like DeclRefExpr).
     Use,
     /// A marker for a specific point in the code, for testing.
     TestPoint,
-    /// Indicates that an origin escapes the function scope (e.g., via return).
+    /// An origin that escapes the function scope (e.g., via return).
     OriginEscapes,
   };
 
@@ -222,6 +222,7 @@ class FactManager {
   const LoanManager &getLoanMgr() const { return LoanMgr; }
   OriginManager &getOriginMgr() { return OriginMgr; }
   const OriginManager &getOriginMgr() const { return OriginMgr; }
+  llvm::ArrayRef<const Fact *> getBlockContaining(ProgramPoint P) const;
 
 private:
   LoanManager LoanMgr;
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 72ea7267b90e5..ec3c130bb6c66 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -93,8 +93,6 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   FactManager &FactMgr;
   AnalysisDeclContext &AC;
   llvm::SmallVector<Fact *> CurrentBlockFacts;
-  // Collect origins that escape the function in this block (OriginEscapesFact),
-  // appended at the end to ensure they appear after ExpireFact entries.
   llvm::SmallVector<Fact *> EscapesInCurrentBlock;
   // To distinguish between reads and writes for use-after-free checks, this map
   // stores the `UseFact` for each `DeclRefExpr`. We initially identify all
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f59ae17cb99cb..bdad73fd92cd4 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10734,7 +10734,7 @@ def warn_lifetime_safety_return_stack_addr_permissive
       InGroup<LifetimeSafetyPermissive>,
       DefaultIgnore;
 def warn_lifetime_safety_return_stack_addr_strict
-    : Warning<"address of stack memory maybe returned later">,
+    : Warning<"address of stack memory may be returned later">,
       InGroup<LifetimeSafetyStrict>,
       DefaultIgnore;
 
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 63c9bf104d940..1f7c282dadac2 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -43,8 +43,7 @@ namespace {
 /// Struct to store the complete context for a potential lifetime violation.
 struct PendingWarning {
   SourceLocation ExpiryLoc; // Where the loan expired.
-  llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
-      CausingFact; // If the use is a UseFact or OriginEscapeFact
+  llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact;
   Confidence ConfidenceLevel;
 };
 
@@ -84,6 +83,8 @@ class LifetimeChecker {
     LoanID ExpiredLoan = EF->getLoanID();
     LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF);
     Confidence CurConfidence = Confidence::None;
+    // The UseFact or OriginEscapesFact most indicative of a lifetime error,
+    // prioritized by earlier source location.
     llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
         BestCausingFact = nullptr;
 
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 45fce4bcf8ba3..9258fa1e538af 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -100,4 +100,16 @@ void FactManager::dump(const CFG &Cfg, AnalysisDeclContext &AC) const {
   }
 }
 
+llvm::ArrayRef<const Fact *>
+FactManager::getBlockContaining(ProgramPoint P) const {
+  for (const auto &Entry : BlockToFactsMap) {
+    const auto &Facts = Entry.second;
+    for (const Fact *F : Facts) {
+      if (F == P)
+        return Facts;
+    }
+  }
+  return {};
+}
+
 } // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 39e7882655711..9d9d665f162a7 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -58,6 +58,9 @@ void FactsGenerator::run() {
   // initializations and destructions are processed in the correct sequence.
   for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
     CurrentBlockFacts.clear();
+    // Collect origins that escape the function in this block
+    // (OriginEscapesFact),
+    // appended at the end to ensure they appear after ExpireFact entries.
     EscapesInCurrentBlock.clear();
     for (unsigned I = 0; I < Block->size(); ++I) {
       const CFGElement &Element = Block->Elements[I];
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index d56f4c3ccc6fa..02dc121a36dec 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -401,56 +401,57 @@ void loan_from_previous_iteration(MyObj safe, bool condition) {
 // These are cases where the pointer is guaranteed to be dangling at the use site.
 //===----------------------------------------------------------------------===//
 
-MyObj* simple_return_stack_address(){
+MyObj* simple_return_stack_address() {
   MyObj s;      
   MyObj* p = &s; // expected-warning {{address of stack memory is returned later}}
   return p;      // expected-note {{returned here}}
 }
 
-const MyObj* conditional_assign_unconditional_return(const MyObj& safe, bool c){
+const MyObj* conditional_assign_unconditional_return(const MyObj& safe, bool c) {
   MyObj s; 
   const MyObj* p = &safe;
-  if(c){
+  if (c) {
     p = &s;       // expected-warning {{address of stack memory is returned later}}
   }     
   return p;      // expected-note {{returned here}}
 }
 
-View conditional_assign_both_branches(const MyObj& safe, bool c){
+View conditional_assign_both_branches(const MyObj& safe, bool c) {
   MyObj s;
   View p;
   if (c) {
     p = s;      // expected-warning {{address of stack memory is returned later}}
-  } else {
+  } 
+  else {
     p = safe;
   }
   return p;     // expected-note {{returned here}}
 
 }
 
-View reassign_safe_to_local(const MyObj& safe){
+View reassign_safe_to_local(const MyObj& safe) {
   MyObj local;
   View p = safe;
   p = local;    // expected-warning {{address of stack memory is returned later}}
   return p;     // expected-note {{returned here}}
 }
 
-View pointer_chain_to_local(){
+View pointer_chain_to_local() {
   MyObj local;
   View p1 = local;     // expected-warning {{address of stack memory is returned later}}
   View p2 = p1; 
   return p2;          // expected-note {{returned here}}
 }
 
-View multiple_assign_multiple_return(const MyObj& safe, bool c1, bool c2){
+View multiple_assign_multiple_return(const MyObj& safe, bool c1, bool c2) {
   MyObj local1;
   MyObj local2;
   View p;
-  if(c1){
+  if (c1) {
     p = local1;       // expected-warning {{address of stack memory is returned later}}
     return p;         // expected-note {{returned here}}
   }
-  else if(c2){
+  else if (c2) {
     p = local2;       // expected-warning {{address of stack memory is returned later}}
     return p;         // expected-note {{returned here}}
   }
@@ -458,29 +459,29 @@ View multiple_assign_multiple_return(const MyObj& safe, bool c1, bool c2){
   return p;
 }
 
-View multiple_assign_single_return(const MyObj& safe, bool c1, bool c2){
+View multiple_assign_single_return(const MyObj& safe, bool c1, bool c2) {
   MyObj local1;
   MyObj local2;
   View p;
-  if(c1){
+  if (c1) {
     p = local1;      // expected-warning {{address of stack memory is returned later}}
   }
-  else if(c2){
+  else if (c2) {
     p = local2;      // expected-warning {{address of stack memory is returned later}}
   }
-  else{
+  else {
     p = safe;
   }
   return p;         // expected-note 2 {{returned here}}
 }
 
-View direct_return_of_local(){
+View direct_return_of_local() {
   MyObj stack;      
   return stack;     // expected-warning {{address of stack memory is returned later}}
                     // expected-note at -1 {{returned here}}
 }
 
-MyObj& reference_return_of_local(){
+MyObj& reference_return_of_local() {
   MyObj stack;      
   return stack;     // expected-warning {{address of stack memory is returned later}}
                     // expected-note at -1 {{returned here}}
@@ -491,7 +492,7 @@ MyObj& reference_return_of_local(){
 // These are cases where the diagnostic kind is determined by location
 //===----------------------------------------------------------------------===//
 
-MyObj* uaf_before_uar(){
+MyObj* uaf_before_uar() {
   MyObj* p;
   {
     MyObj local_obj; 
@@ -500,12 +501,12 @@ MyObj* uaf_before_uar(){
   return p;          // expected-note {{later used here}}
 }
 
-View uar_before_uaf(const MyObj& safe, bool c){
+View uar_before_uaf(const MyObj& safe, bool c) {
   View p;
   {
     MyObj local_obj; 
     p = local_obj;  // expected-warning {{address of stack memory is returned later}}
-    if(c){
+    if (c) {
       return p;      // expected-note {{returned here}}
     }
   }
@@ -552,7 +553,7 @@ void no_error_loan_from_current_iteration(bool cond) {
   }
 }
 
-View safe_return(const MyObj& safe){
+View safe_return(const MyObj& safe) {
   MyObj local;
   View p = local;
   p = safe;     // p has been reassigned
@@ -708,7 +709,7 @@ void lifetimebound_ctor() {
   (void)v;
 }
 
-View lifetimebound_return_of_local(){
+View lifetimebound_return_of_local() {
   MyObj stack;
   return Identity(stack); // expected-warning {{address of stack memory is returned later}}
                           // expected-note at -1 {{returned here}}
@@ -720,7 +721,7 @@ const MyObj& lifetimebound_return_ref_to_local() {
                              // expected-note at -1 {{returned here}}
 }
 
-// FIXME: Fails to diagnose UAF when a reference to a by-value param escapes via an out-param.
+// FIXME: Fails to diagnose UAR when a reference to a by-value param escapes via the return value.
 View lifetimebound_return_of_by_value_param(MyObj stack_param) {
   return Identity(stack_param); 
 }
@@ -728,4 +729,4 @@ View lifetimebound_return_of_by_value_param(MyObj stack_param) {
 // FIXME: Fails to diagnose UAF when a reference to a by-value param escapes via an out-param.
 void uaf_from_by_value_param_failing(MyObj param, View* out_p) {
   *out_p = Identity(param);
-}
\ No newline at end of file
+}
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index a1897748c0c83..41f8e2ac44693 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -122,6 +122,39 @@ class LifetimeTestHelper {
     return LID;
   }
 
+  std::optional<LoanSet> getLiveLoansAtPoint(ProgramPoint P) const {
+    const auto &LiveOriginsAnalysis = Runner.getAnalysis().getLiveOrigins();
+    const auto &LoanPropagation = Runner.getAnalysis().getLoanPropagation();
+
+    LivenessMap LiveOriginsMap = LiveOriginsAnalysis.getLiveOriginsAt(P);
+
+    LoanSet::Factory F;
+    LoanSet Result = F.getEmptySet();
+
+    for (const auto &[OID, LI] : LiveOriginsMap) {
+      if (LI.Kind != LivenessKind::Dead) {
+        LoanSet Loans = LoanPropagation.getLoans(OID, P);
+        Result = clang::lifetimes::internal::utils::join(Result, Loans, F);
+      }
+    }
+
+    if (Result.isEmpty())
+      return std::nullopt;
+
+    return Result;
+  }
+
+  const ExpireFact *
+  getExpireFactFromAllFacts(const llvm::ArrayRef<const Fact *> &FactsInBlock,
+                            const LoanID &loanID) {
+    for (const Fact *F : FactsInBlock) {
+      if (auto const *CurrentEF = F->getAs<ExpireFact>())
+        if (CurrentEF->getLoanID() == loanID)
+          return CurrentEF;
+    }
+    return nullptr;
+  }
+
   std::optional<LoanSet> getLoansAtPoint(OriginID OID,
                                          llvm::StringRef Annotation) {
     ProgramPoint PP = Runner.getProgramPoint(Annotation);
@@ -141,6 +174,14 @@ class LifetimeTestHelper {
     return Result;
   }
 
+  ProgramPoint getProgramPoint(llvm::StringRef Annotation) {
+    return Runner.getProgramPoint(Annotation);
+  }
+
+  llvm::ArrayRef<const Fact *> getBlockContaining(ProgramPoint P) {
+    return Runner.getAnalysis().getFactManager().getBlockContaining(P);
+  }
+
 private:
   template <typename DeclT> DeclT *findDecl(llvm::StringRef Name) {
     auto &Ctx = Runner.getASTContext();
@@ -304,6 +345,43 @@ MATCHER_P2(AreLiveAtImpl, Annotation, ConfFilter, "") {
   return true;
 }
 
+MATCHER_P2(HasLiveLoanAtExpiryImpl, HelperPtr, Annotation, "") {
+  llvm::StringRef VarName = arg;
+  LifetimeTestHelper &Helper = *HelperPtr;
+
+  std::vector<LoanID> Loans = Helper.getLoansForVar(VarName);
+  if (Loans.empty()) {
+    *result_listener << "No loans found for variable" << VarName.str();
+    return false;
+  }
+
+  ProgramPoint PP = Helper.getProgramPoint(Annotation);
+  llvm::ArrayRef<const Fact *> AllFactsInBlock = Helper.getBlockContaining(PP);
+
+  bool NoExpireFactLive = false;
+  for (const LoanID CurrentLoanID : Loans) {
+    const ExpireFact *EF =
+        Helper.getExpireFactFromAllFacts(AllFactsInBlock, CurrentLoanID);
+    if (!EF) {
+      NoExpireFactLive = true;
+      continue;
+    }
+    std::optional<LoanSet> LiveLoans = Helper.getLiveLoansAtPoint(EF);
+    if (!LiveLoans.has_value()) {
+      *result_listener << "No Live Loans At Expiry Location.";
+      continue;
+    }
+    if (LiveLoans->contains({CurrentLoanID}))
+      return true;
+  }
+  if (NoExpireFactLive) {
+    *result_listener << "No Expire Fact for loan of " << VarName.str();
+    return false;
+  }
+  *result_listener << "No loans of " << VarName.str() << " are live";
+  return false;
+}
+
 MATCHER_P(MustBeLiveAt, Annotation, "") {
   return ExplainMatchResult(AreLiveAtImpl(Annotation, LivenessKindFilter::Must),
                             arg, result_listener);
@@ -353,6 +431,10 @@ class LifetimeAnalysisTest : public ::testing::Test {
     return HasLoansToImpl(std::vector<std::string>(LoanVars), Annotation);
   }
 
+  auto HasLiveLoanAtExpiry(const char *Annotation) {
+    return HasLiveLoanAtExpiryImpl(Helper.get(), Annotation);
+  }
+
   std::unique_ptr<LifetimeTestRunner> Runner;
   std::unique_ptr<LifetimeTestHelper> Helper;
 };
@@ -1211,8 +1293,7 @@ TEST_F(LifetimeAnalysisTest, SimpleReturnStackAddress) {
       return p;
     }
   )");
-  EXPECT_THAT(Origin("p"), HasLoansTo({"s"}, "p1"));
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
+  EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
 }
 
 TEST_F(LifetimeAnalysisTest, ConditionalAssignUnconditionalReturn) {
@@ -1220,23 +1301,28 @@ TEST_F(LifetimeAnalysisTest, ConditionalAssignUnconditionalReturn) {
     MyObj* target(bool c) {
       MyObj s1;
       MyObj* p = nullptr;
-      POINT(before_if);
       if (c) {
         p = &s1;
-        POINT(after_assign);
       }
-      POINT(after_if);
+      POINT(P);
       return p;
     }
   )");
-  EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_if"));
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_if"));
-
-  EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_assign"));
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign"));
+  EXPECT_THAT("s1", HasLiveLoanAtExpiry("P"));
+}
 
-  EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_if"));
-  EXPECT_THAT(Origins({"p"}), MaybeLiveAt("before_if"));
+TEST_F(LifetimeAnalysisTest, MultipleAssignments) {
+  SetupTest(R"(
+    MyObj* target() {
+      MyObj s;
+      MyObj* p1 = &s;
+      MyObj* p2 = &s;
+      POINT(P);
+      return p2;
+    }
+  )");
+  // Test if atleast one loan to "s" is live;
+  EXPECT_THAT("s", HasLiveLoanAtExpiry("P"));
 }
 
 TEST_F(LifetimeAnalysisTest, ConditionalAssignBothBranches) {
@@ -1245,29 +1331,16 @@ TEST_F(LifetimeAnalysisTest, ConditionalAssignBothBranches) {
       MyObj s1;
       static MyObj s2;
       MyObj* p = nullptr;
-      POINT(before_if);
       if (c) {
         p = &s1;
-        POINT(after_assign_if);
       } else {
        p = &s2;
-       POINT(after_assign_else);
       }
-      POINT(after_if);
+      POINT(P);
       return p;
     }
   )");
-  EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2"}, "after_if"));
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_if"));
-
-  EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_assign_if"));
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign_if"));
-
-  EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "after_assign_else"));
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign_else"));
-
-  EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_if"));
-  EXPECT_THAT(NoOrigins(), AreLiveAt("before_if"));
+  EXPECT_THAT("s1", HasLiveLoanAtExpiry("P"));
 }
 
 TEST_F(LifetimeAnalysisTest, ReassignFromSafeToLocalThenReturn) {
@@ -1276,19 +1349,13 @@ TEST_F(LifetimeAnalysisTest, ReassignFromSafeToLocalThenReturn) {
       static MyObj safe_obj;
       MyObj local_obj;
       MyObj* p = &safe_obj;
-      POINT(p1);
 
       p = &local_obj;
-      POINT(p2); 
+      POINT(P); 
       return p;
     }
   )");
-
-  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p2"));
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
-
-  EXPECT_THAT(Origin("p"), HasLoansTo({"safe_obj"}, "p1"));
-  EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
+  EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("P"));
 }
 
 TEST_F(LifetimeAnalysisTest, PointerChainToLocal) {
@@ -1296,18 +1363,12 @@ TEST_F(LifetimeAnalysisTest, PointerChainToLocal) {
     MyObj* target() {
       MyObj local_obj;
       MyObj* p1 = &local_obj;
-      POINT(p_p1);
       MyObj* p2 = p1;
-      POINT(p_p2);
+      POINT(P);
       return p2;
     }
   )");
-  EXPECT_THAT(Origin("p2"), HasLoansTo({"local_obj"}, "p_p2"));
-  EXPECT_THAT(Origins({"p2"}), MustBeLiveAt("p_p2"));
-  EXPECT_THAT(Origins({"p1"}), testing::Not(AreLiveAt("p_p2")));
-
-  EXPECT_THAT(Origin("p1"), HasLoansTo({"local_obj"}, "p_p1"));
-  EXPECT_THAT(Origins({"p1"}), MustBeLiveAt("p_p1"));
+  EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("P"));
 }
 
 TEST_F(LifetimeAnalysisTest, MultipleAssignmentMultipleReturn) {
@@ -1317,34 +1378,27 @@ TEST_F(LifetimeAnalysisTest, MultipleAssignmentMultipleReturn) {
       MyObj local_obj1;
       MyObj local_obj2;
       MyObj* p = nullptr;
-      POINT(before_cond);
       if(c1){
         p = &local_obj1;
-        POINT(after_c1);
+        POINT(C1);
         return p;
       }
       else if(c2){
         p = &local_obj2;
-        POINT(after_c2);
+        POINT(C2);
         return p;
       }
       p = &global_obj;
-      POINT(after_cond);
+      POINT(C3);
       return p;
     }
   )");
-  // Expect two permissive warnings here in each block
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_cond"));
-  EXPECT_THAT(Origin("p"), HasLoansTo({"global_obj"}, "after_cond"));
 
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c2"));
-  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj2"}, "after_c2"));
+  EXPECT_THAT("local_obj1", HasLiveLoanAtExpiry("C1"));
+  EXPECT_THAT("local_obj2", HasLiveLoanAtExpiry("C2"));
 
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c1"));
-  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj1"}, "after_c1"));
-
-  EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_cond"));
-  EXPECT_THAT(NoOrigins(), AreLiveAt("before_cond"));
+  EXPECT_THAT("local_obj1", testing::Not(HasLiveLoanAtExpiry("C3")));
+  EXPECT_THAT("local_obj2", testing::Not(HasLiveLoanAtExpiry("C3")));
 }
 
 TEST_F(LifetimeAnalysisTest, MultipleAssignmentsSingleReturn) {
@@ -1354,42 +1408,23 @@ TEST_F(LifetimeAnalysisTest, MultipleAssignmentsSingleReturn) {
       MyObj local_obj1;
       MyObj local_obj2;
       MyObj* p = nullptr;
-      POINT(before_cond);
       if(c1){
         p = &local_obj1;
-        POINT(after_c1);
       }
       else if(c2){
         p = &local_obj2;
-        POINT(after_c2);
       }
       else{
       p = &global_obj;
-      POINT(after_else);
       }
-      
-      POINT(after_cond);
+      POINT(P);
       return p;
     }
   )");
   // Expect permissive warning at return statement with both local_obj1 and
   // local_obj2 as culprit loans
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_cond"));
-  EXPECT_THAT(
-      Origin("p"),
-      HasLoansTo({"global_obj", "local_obj2", "local_obj1"}, "after_cond"));
-
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_else"));
-  EXPECT_THAT(Origin("p"), HasLoansTo({"global_obj"}, "after_else"));
-
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c2"));
-  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj2"}, "after_c2"));
-
-  EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c1"));
-  EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj1"}, "after_c1"));
-
-  EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_cond"));
-  EXPECT_THAT(NoOrigins(), AreLiveAt("before_cond"));
+  EXPECT_THAT("local_obj1", HasLiveLoanAtExpiry("P"));
+  EXPECT_THAT("local_obj2", HasLiveLoanAtExpiry("P"));
 }
 
 TEST_F(LifetimeAnalysisTest, UseAfterScopeThenReturn) {
@@ -1412,6 +1447,8 @@ TEST_F(LifetimeAnalysisTest, UseAfterScopeThenReturn) {
 
   EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1"));
   EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
+
+  EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("p2"));
 }
 
 TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) {
@@ -1432,6 +1469,8 @@ TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) {
     }
   )");
   // Expect a return stack address warning as return is before use after scope
+  EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("p1"));
+
   EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));
 
   EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1"));

>From 9d0e5db7ab745b5066834db66931f8740b787115 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 10/12] Adding use-after-return in Lifetime Analysis

---
 clang/test/Sema/warn-lifetime-safety.cpp      |  6 +++++
 .../unittests/Analysis/LifetimeSafetyTest.cpp | 25 ++++++++++++++-----
 2 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 02dc121a36dec..bcb99d329116b 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -407,6 +407,12 @@ MyObj* simple_return_stack_address() {
   return p;      // expected-note {{returned here}}
 }
 
+MyObj* direct_return() {
+  MyObj s;      
+  return &s;     // expected-warning {{address of stack memory is returned later}}
+                 // expected-note at -1 {{returned here}}
+}
+
 const MyObj* conditional_assign_unconditional_return(const MyObj& safe, bool c) {
   MyObj s; 
   const MyObj* p = &safe;
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 41f8e2ac44693..01e538c8dcbab 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -20,6 +20,7 @@ namespace clang::lifetimes::internal {
 namespace {
 
 using namespace ast_matchers;
+using ::testing::Not;
 using ::testing::SizeIs;
 using ::testing::UnorderedElementsAreArray;
 
@@ -122,6 +123,9 @@ class LifetimeTestHelper {
     return LID;
   }
 
+  // Gets the set of loans that are live at the given program point. A loan is
+  // considered live at point P if there is a live origin which contains this
+  // loan.
   std::optional<LoanSet> getLiveLoansAtPoint(ProgramPoint P) const {
     const auto &LiveOriginsAnalysis = Runner.getAnalysis().getLiveOrigins();
     const auto &LoanPropagation = Runner.getAnalysis().getLoanPropagation();
@@ -132,10 +136,8 @@ class LifetimeTestHelper {
     LoanSet Result = F.getEmptySet();
 
     for (const auto &[OID, LI] : LiveOriginsMap) {
-      if (LI.Kind != LivenessKind::Dead) {
-        LoanSet Loans = LoanPropagation.getLoans(OID, P);
-        Result = clang::lifetimes::internal::utils::join(Result, Loans, F);
-      }
+      LoanSet Loans = LoanPropagation.getLoans(OID, P);
+      Result = clang::lifetimes::internal::utils::join(Result, Loans, F);
     }
 
     if (Result.isEmpty())
@@ -1296,6 +1298,17 @@ TEST_F(LifetimeAnalysisTest, SimpleReturnStackAddress) {
   EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
 }
 
+TEST_F(LifetimeAnalysisTest, DirectReturn) {
+  SetupTest(R"(
+    MyObj* target() {
+      MyObj s;
+      POINT(P);
+      return &s;
+    }
+  )");
+  EXPECT_THAT("s", HasLiveLoanAtExpiry("P"));
+}
+
 TEST_F(LifetimeAnalysisTest, ConditionalAssignUnconditionalReturn) {
   SetupTest(R"(
     MyObj* target(bool c) {
@@ -1397,8 +1410,8 @@ TEST_F(LifetimeAnalysisTest, MultipleAssignmentMultipleReturn) {
   EXPECT_THAT("local_obj1", HasLiveLoanAtExpiry("C1"));
   EXPECT_THAT("local_obj2", HasLiveLoanAtExpiry("C2"));
 
-  EXPECT_THAT("local_obj1", testing::Not(HasLiveLoanAtExpiry("C3")));
-  EXPECT_THAT("local_obj2", testing::Not(HasLiveLoanAtExpiry("C3")));
+  EXPECT_THAT("local_obj1", Not(HasLiveLoanAtExpiry("C3")));
+  EXPECT_THAT("local_obj2", Not(HasLiveLoanAtExpiry("C3")));
 }
 
 TEST_F(LifetimeAnalysisTest, MultipleAssignmentsSingleReturn) {

>From 97efff2769a991a0ea77a586bb12fe434fd137b7 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 11/12] Adding use-after-return in Lifetime Analysis

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

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index ec3c130bb6c66..72ea7267b90e5 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -93,6 +93,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   FactManager &FactMgr;
   AnalysisDeclContext &AC;
   llvm::SmallVector<Fact *> CurrentBlockFacts;
+  // Collect origins that escape the function in this block (OriginEscapesFact),
+  // appended at the end to ensure they appear after ExpireFact entries.
   llvm::SmallVector<Fact *> EscapesInCurrentBlock;
   // To distinguish between reads and writes for use-after-free checks, this map
   // stores the `UseFact` for each `DeclRefExpr`. We initially identify all
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 9d9d665f162a7..39e7882655711 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -58,9 +58,6 @@ void FactsGenerator::run() {
   // initializations and destructions are processed in the correct sequence.
   for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
     CurrentBlockFacts.clear();
-    // Collect origins that escape the function in this block
-    // (OriginEscapesFact),
-    // appended at the end to ensure they appear after ExpireFact entries.
     EscapesInCurrentBlock.clear();
     for (unsigned I = 0; I < Block->size(); ++I) {
       const CFGElement &Element = Block->Elements[I];

>From f85b397496467cbfd884212d8a1ee228e460bfb5 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 12/12] Adding use-after-return in Lifetime Analysis

---
 .../clang/Analysis/Analyses/LifetimeSafety/Facts.h    |  4 +++-
 .../Analysis/Analyses/LifetimeSafety/FactsGenerator.h |  3 ++-
 .../Analysis/Analyses/LifetimeSafety/LiveOrigins.h    | 10 +++++-----
 clang/lib/Analysis/LifetimeSafety/Facts.cpp           |  3 +--
 clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp     | 11 +++--------
 clang/unittests/Analysis/LifetimeSafetyTest.cpp       |  5 -----
 6 files changed, 14 insertions(+), 22 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index d080bd10e8be6..277d36116bfc3 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -217,12 +217,14 @@ class FactManager {
   /// user-defined locations in the code.
   /// \note This is intended for testing only.
   llvm::StringMap<ProgramPoint> getTestPoints() const;
+  /// Retrieves all the facts in the block containing Program Point P.
+  /// \note This is intended for testing only.
+  llvm::ArrayRef<const Fact *> getBlockContaining(ProgramPoint P) const;
 
   LoanManager &getLoanMgr() { return LoanMgr; }
   const LoanManager &getLoanMgr() const { return LoanMgr; }
   OriginManager &getOriginMgr() { return OriginMgr; }
   const OriginManager &getOriginMgr() const { return OriginMgr; }
-  llvm::ArrayRef<const Fact *> getBlockContaining(ProgramPoint P) const;
 
 private:
   LoanManager LoanMgr;
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 72ea7267b90e5..aa6f98e97e020 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -94,7 +94,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   AnalysisDeclContext &AC;
   llvm::SmallVector<Fact *> CurrentBlockFacts;
   // Collect origins that escape the function in this block (OriginEscapesFact),
-  // appended at the end to ensure they appear after ExpireFact entries.
+  // appended at the end of CurrentBlockFacts to ensure they appear after
+  // ExpireFact entries.
   llvm::SmallVector<Fact *> EscapesInCurrentBlock;
   // To distinguish between reads and writes for use-after-free checks, this map
   // stores the `UseFact` for each `DeclRefExpr`. We initially identify all
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
index f23236ba8a3d8..8ad17db83499d 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
@@ -31,6 +31,9 @@
 
 namespace clang::lifetimes::internal {
 
+using CausingFactType =
+    ::llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>;
+
 enum class LivenessKind : uint8_t {
   Dead,  // Not alive
   Maybe, // Live on some path but not all paths (may-be-live)
@@ -43,7 +46,7 @@ struct LivenessInfo {
   /// multiple uses along different paths, this will point to the use appearing
   /// earlier in the translation unit.
   /// This is 'null' when the origin is not live.
-  llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact;
+  CausingFactType CausingFact;
 
   /// The kind of liveness of the origin.
   /// `Must`: The origin is live on all control-flow paths from the current
@@ -57,10 +60,7 @@ struct LivenessInfo {
   LivenessKind Kind;
 
   LivenessInfo() : CausingFact(nullptr), Kind(LivenessKind::Dead) {}
-  LivenessInfo(
-      llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CF,
-      LivenessKind K)
-      : CausingFact(CF), Kind(K) {}
+  LivenessInfo(CausingFactType CF, LivenessKind K) : CausingFact(CF), Kind(K) {}
 
   bool operator==(const LivenessInfo &Other) const {
     return CausingFact == Other.CausingFact && Kind == Other.Kind;
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 9258fa1e538af..903b702a5bd3c 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -104,10 +104,9 @@ llvm::ArrayRef<const Fact *>
 FactManager::getBlockContaining(ProgramPoint P) const {
   for (const auto &Entry : BlockToFactsMap) {
     const auto &Facts = Entry.second;
-    for (const Fact *F : Facts) {
+    for (const Fact *F : Facts)
       if (F == P)
         return Facts;
-    }
   }
   return {};
 }
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index 65727c819830e..7e82604135e5a 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -53,14 +53,11 @@ struct Lattice {
   }
 };
 
-static SourceLocation
-GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) {
+static SourceLocation GetFactLoc(CausingFactType F) {
   if (const auto *UF = F.dyn_cast<const UseFact *>())
     return UF->getUseExpr()->getExprLoc();
-
   if (const auto *OEF = F.dyn_cast<const OriginEscapesFact *>())
     return OEF->getEscapeExpr()->getExprLoc();
-
   llvm_unreachable("unhandled causing fact in PointerUnion");
 }
 
@@ -86,10 +83,8 @@ class AnalysisImpl
   Lattice join(Lattice L1, Lattice L2) const {
     LivenessMap Merged = L1.LiveOrigins;
     // Take the earliest Fact to make the join hermetic and commutative.
-    auto CombineCausingFact =
-        [](llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> A,
-           llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> B)
-        -> llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> {
+    auto CombineCausingFact = [](CausingFactType A,
+                                 CausingFactType B) -> CausingFactType {
       if (!A)
         return B;
       if (!B)
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 01e538c8dcbab..1a45866780a08 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -1434,8 +1434,6 @@ TEST_F(LifetimeAnalysisTest, MultipleAssignmentsSingleReturn) {
       return p;
     }
   )");
-  // Expect permissive warning at return statement with both local_obj1 and
-  // local_obj2 as culprit loans
   EXPECT_THAT("local_obj1", HasLiveLoanAtExpiry("P"));
   EXPECT_THAT("local_obj2", HasLiveLoanAtExpiry("P"));
 }
@@ -1453,8 +1451,6 @@ TEST_F(LifetimeAnalysisTest, UseAfterScopeThenReturn) {
       return p;
     }
   )");
-  // Expect a Use-after-scope warning because `p` in `return p` is a use even
-  // before return
   EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p2"));
   EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
 
@@ -1481,7 +1477,6 @@ TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) {
       return &global_obj;
     }
   )");
-  // Expect a return stack address warning as return is before use after scope
   EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("p1"));
 
   EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));



More information about the cfe-commits mailing list