https://github.com/AbhinavPradeep updated 
https://github.com/llvm/llvm-project/pull/172007

>From db6fafac81a4e62da5b0dc0f255cb135060f9610 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Fri, 12 Dec 2025 22:47:33 +1000
Subject: [PATCH 1/2] Add support for loans to temporary materializations

---
 .../Analyses/LifetimeSafety/FactsGenerator.h  |  2 +
 .../Analysis/Analyses/LifetimeSafety/Loans.h  | 21 +++++++-
 .../LifetimeSafety/FactsGenerator.cpp         | 48 ++++++++++++++++--
 clang/lib/Analysis/LifetimeSafety/Loans.cpp   | 11 ++++-
 clang/test/Sema/warn-lifetime-safety.cpp      | 10 ++++
 .../unittests/Analysis/LifetimeSafetyTest.cpp | 49 +++++++++++++++++--
 6 files changed, 130 insertions(+), 11 deletions(-)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index a1acd8615afdd..cccb25bd41037 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -52,6 +52,8 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
 private:
   void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);
 
+  void handleTemporaryDtor(const CFGTemporaryDtor &TemporaryDtor);
+
   void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
 
   /// Checks if a call-like expression creates a borrow by passing a value to a
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index e9bccd4773622..96b34e52529e3 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -15,6 +15,7 @@
 #define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H
 
 #include "clang/AST/Decl.h"
+#include "clang/AST/ExprCXX.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"
 #include "llvm/Support/raw_ostream.h"
 
@@ -29,9 +30,25 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, 
LoanID ID) {
 /// variable.
 /// TODO: Model access paths of other types, e.g., s.field, heap and globals.
 struct AccessPath {
-  const clang::ValueDecl *D;
+  // Currently, an access path can be:
+  // - ValueDecl * , to represent the storage location corresponding to the
+  //   variable declared in ValueDecl.
+  // - CXXBindTemporaryExpr * , to represent the storage location of the
+  //   temporary object used for the binding in CXXBindTemporaryExpr.
+  const llvm::PointerUnion<const clang::ValueDecl *,
+                           const clang::CXXBindTemporaryExpr *>
+      P;
+
+  AccessPath(const clang::ValueDecl *D) : P(D) {}
+  AccessPath(const clang::CXXBindTemporaryExpr *BTE) : P(BTE) {}
+
+  const clang::ValueDecl *getAsValueDecl() const {
+    return P.dyn_cast<const clang::ValueDecl *>();
+  }
 
-  AccessPath(const clang::ValueDecl *D) : D(D) {}
+  const clang::CXXBindTemporaryExpr *getAsCXXBindTemporaryExpr() const {
+    return P.dyn_cast<const clang::CXXBindTemporaryExpr *>();
+  }
 };
 
 /// An abstract base class for a single "Loan" which represents lending a
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 2f270b03996f2..ba75bdec3e6f9 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -41,6 +41,12 @@ static const PathLoan *createLoan(FactManager &FactMgr,
   return nullptr;
 }
 
+static const PathLoan *createLoan(FactManager &FactMgr,
+                                  const CXXBindTemporaryExpr *BTE) {
+  AccessPath Path(BTE);
+  return FactMgr.getLoanMgr().createLoan<PathLoan>(Path, BTE);
+}
+
 void FactsGenerator::run() {
   llvm::TimeTraceScope TimeProfile("FactGenerator");
   const CFG &Cfg = *AC.getCFG();
@@ -60,6 +66,9 @@ void FactsGenerator::run() {
       else if (std::optional<CFGLifetimeEnds> LifetimeEnds =
                    Element.getAs<CFGLifetimeEnds>())
         handleLifetimeEnds(*LifetimeEnds);
+      else if (std::optional<CFGTemporaryDtor> TemporaryDtor =
+                   Element.getAs<CFGTemporaryDtor>())
+        handleTemporaryDtor(*TemporaryDtor);
     }
     CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
                              EscapesInCurrentBlock.end());
@@ -218,9 +227,18 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
     const MaterializeTemporaryExpr *MTE) {
   if (!hasOrigin(MTE))
     return;
-  // A temporary object's origin is the same as the origin of the
-  // expression that initializes it.
-  killAndFlowOrigin(*MTE, *MTE->getSubExpr());
+  const Expr *Child = MTE->getSubExpr()->IgnoreImpCasts();
+  if (const CXXBindTemporaryExpr *BTE =
+          dyn_cast<const CXXBindTemporaryExpr>(Child)) {
+    // Issue a loan to MTE for the storage location represented by BTE
+    const Loan *L = createLoan(FactMgr, BTE);
+    OriginID OID = FactMgr.getOriginMgr().getOrCreate(*MTE);
+    CurrentBlockFacts.push_back(FactMgr.createFact<IssueFact>(L->getID(), 
OID));
+  } else {
+    // A temporary object's origin is the same as the origin of the
+    // expression that initializes it.
+    killAndFlowOrigin(*MTE, *MTE->getSubExpr());
+  }
 }
 
 void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
@@ -233,13 +251,35 @@ void FactsGenerator::handleLifetimeEnds(const 
CFGLifetimeEnds &LifetimeEnds) {
     if (const auto *BL = dyn_cast<PathLoan>(Loan)) {
       // Check if the loan is for a stack variable and if that variable
       // is the one being destructed.
-      if (BL->getAccessPath().D == LifetimeEndsVD)
+      const AccessPath AP = BL->getAccessPath();
+      const ValueDecl *Path = AP.getAsValueDecl();
+      if (Path == LifetimeEndsVD)
         CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
             BL->getID(), LifetimeEnds.getTriggerStmt()->getEndLoc()));
     }
   }
 }
 
+void FactsGenerator::handleTemporaryDtor(
+    const CFGTemporaryDtor &TemporaryDtor) {
+  const CXXBindTemporaryExpr *BTE = TemporaryDtor.getBindTemporaryExpr();
+  if (!BTE) {
+    return;
+  }
+  // Iterate through all loans to see if any expire.
+  for (const auto *Loan : FactMgr.getLoanMgr().getLoans()) {
+    if (const auto *BL = dyn_cast<PathLoan>(Loan)) {
+      // Check if the loan is for a temporary materialization and if that 
storage
+      // location is the one being destructed.
+      const AccessPath AP = BL->getAccessPath();
+      const CXXBindTemporaryExpr *Path = AP.getAsCXXBindTemporaryExpr();
+      if (Path == BTE)
+        CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
+            BL->getID(), TemporaryDtor.getBindTemporaryExpr()->getEndLoc()));
+    }
+  }
+}
+
 void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) 
{
   assert(isGslPointerType(CCE->getType()));
   if (CCE->getNumArgs() != 1)
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp 
b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index fdfdbb40a2a46..5f6ba6809d2fb 100644
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
@@ -12,7 +12,16 @@ namespace clang::lifetimes::internal {
 
 void PathLoan::dump(llvm::raw_ostream &OS) const {
   OS << getID() << " (Path: ";
-  OS << Path.D->getNameAsString() << ")";
+  if (const clang::ValueDecl *VD = Path.getAsValueDecl()) {
+    OS << VD->getNameAsString();
+  } else if (const clang::CXXBindTemporaryExpr *BTE =
+                 Path.getAsCXXBindTemporaryExpr()) {
+    // No nice "name" for the temporary, so deferring to LLVM default
+    OS << "CXXBindTemporaryExpr at " << BTE;
+  } else {
+    llvm_unreachable("access path is not one of any supported types");
+  }
+  OS << ")";
 }
 
 void PlaceholderLoan::dump(llvm::raw_ostream &OS) const {
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 1191469e23df1..635489581e236 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -16,6 +16,9 @@ class TriviallyDestructedClass {
   View a, b;
 };
 
+MyObj temporary();
+void use(View);
+
 
//===----------------------------------------------------------------------===//
 // Basic Definite Use-After-Free (-W...permissive)
 // These are cases where the pointer is guaranteed to be dangling at the use 
site.
@@ -943,3 +946,10 @@ void parentheses(bool cond) {
   }  // expected-note 4 {{destroyed here}}
   (void)*p;  // expected-note 4 {{later used here}}
 }
+
+void use_temporary_after_destruction() {
+  View a;
+  a = temporary(); // expected-warning {{object whose reference is captured 
does not live long enough}} \
+                  expected-note {{destroyed here}}
+  use(a); // expected-note {{later used here}}
+}
\ No newline at end of file
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp 
b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index fee4e79e27d03..86e5d662880b6 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -9,6 +9,7 @@
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
 #include "clang/Testing/TestAST.h"
 #include "llvm/ADT/StringMap.h"
 #include "gmock/gmock.h"
@@ -116,7 +117,7 @@ class LifetimeTestHelper {
     std::vector<LoanID> LID;
     for (const Loan *L : Analysis.getFactManager().getLoanMgr().getLoans())
       if (const auto *BL = dyn_cast<PathLoan>(L))
-        if (BL->getAccessPath().D == VD)
+        if (BL->getAccessPath().getAsValueDecl() == VD)
           LID.push_back(L->getID());
     if (LID.empty()) {
       ADD_FAILURE() << "Loan for '" << VarName << "' not found.";
@@ -125,6 +126,14 @@ class LifetimeTestHelper {
     return LID;
   }
 
+  bool isLoanToATemporary(LoanID LID) {
+    const Loan *L = Analysis.getFactManager().getLoanMgr().getLoan(LID);
+    if (const auto *BL = dyn_cast<PathLoan>(L)) {
+      return BL->getAccessPath().getAsCXXBindTemporaryExpr() != nullptr;
+    }
+    return false;
+  }
+
   // 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.
@@ -402,6 +411,35 @@ MATCHER_P(AreLiveAt, Annotation, "") {
                             arg, result_listener);
 }
 
+MATCHER_P(HasLoanToATemporary, Annotation, "") {
+  const OriginInfo &Info = arg;
+  auto &Helper = Info.Helper;
+  std::optional<OriginID> OIDOpt = Helper.getOriginForDecl(Info.OriginVar);
+  if (!OIDOpt) {
+    *result_listener << "could not find origin for '" << Info.OriginVar.str()
+                     << "'";
+    return false;
+  }
+
+  std::optional<LoanSet> LoansSetOpt =
+      Helper.getLoansAtPoint(*OIDOpt, Annotation);
+  if (!LoansSetOpt) {
+    *result_listener << "could not get a valid loan set at point '"
+                     << Annotation << "'";
+    return false;
+  }
+
+  std::vector<LoanID> Loans(LoansSetOpt->begin(), LoansSetOpt->end());
+
+  for (LoanID LID : Loans) {
+    if (Helper.isLoanToATemporary(LID))
+      return true;
+  }
+  *result_listener << "could not find loan to a temporary for '"
+                   << Info.OriginVar.str() << "'";
+  return false;
+}
+
 // Base test fixture to manage the runner and helper.
 class LifetimeAnalysisTest : public ::testing::Test {
 protected:
@@ -807,12 +845,15 @@ TEST_F(LifetimeAnalysisTest, ExtraParenthesis) {
 TEST_F(LifetimeAnalysisTest, ViewFromTemporary) {
   SetupTest(R"(
     MyObj temporary();
+    void use(View);
     void target() {
-      View v = temporary();
-      POINT(p1);
+        View a;
+        a = temporary();
+        POINT(p1);
+        use(a);
     }
   )");
-  EXPECT_THAT(Origin("v"), HasLoansTo({}, "p1"));
+  EXPECT_THAT(Origin("a"), HasLoanToATemporary("p1"));
 }
 
 TEST_F(LifetimeAnalysisTest, GslPointerWithConstAndAuto) {

>From 51a8c9cd6e24fafa92cf76ee24edbcc720f4e395 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Mon, 15 Dec 2025 19:28:41 +1000
Subject: [PATCH 2/2] Use MaterializeTemporaryExpr to represent temporary
 storage

---
 .../Analysis/Analyses/LifetimeSafety/Loans.h  | 14 +++----
 .../LifetimeSafety/FactsGenerator.cpp         | 39 ++++++++++++-------
 clang/lib/Analysis/LifetimeSafety/Loans.cpp   |  6 +--
 clang/test/Sema/warn-lifetime-safety.cpp      |  2 +-
 .../unittests/Analysis/LifetimeSafetyTest.cpp |  3 +-
 5 files changed, 38 insertions(+), 26 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index 96b34e52529e3..6dfe4c8fa6b9c 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -30,24 +30,24 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, 
LoanID ID) {
 /// variable.
 /// TODO: Model access paths of other types, e.g., s.field, heap and globals.
 struct AccessPath {
-  // Currently, an access path can be:
+  // An access path can be:
   // - ValueDecl * , to represent the storage location corresponding to the
   //   variable declared in ValueDecl.
-  // - CXXBindTemporaryExpr * , to represent the storage location of the
-  //   temporary object used for the binding in CXXBindTemporaryExpr.
+  // - MaterializeTemporaryExpr * , to represent the storage location of the
+  //   temporary object materialized via this MaterializeTemporaryExpr.
   const llvm::PointerUnion<const clang::ValueDecl *,
-                           const clang::CXXBindTemporaryExpr *>
+                           const clang::MaterializeTemporaryExpr *>
       P;
 
   AccessPath(const clang::ValueDecl *D) : P(D) {}
-  AccessPath(const clang::CXXBindTemporaryExpr *BTE) : P(BTE) {}
+  AccessPath(const clang::MaterializeTemporaryExpr *MTE) : P(MTE) {}
 
   const clang::ValueDecl *getAsValueDecl() const {
     return P.dyn_cast<const clang::ValueDecl *>();
   }
 
-  const clang::CXXBindTemporaryExpr *getAsCXXBindTemporaryExpr() const {
-    return P.dyn_cast<const clang::CXXBindTemporaryExpr *>();
+  const clang::MaterializeTemporaryExpr *getAsMaterializeTemporaryExpr() const 
{
+    return P.dyn_cast<const clang::MaterializeTemporaryExpr *>();
   }
 };
 
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index ba75bdec3e6f9..fbfed5d5a2cfd 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -41,10 +41,24 @@ static const PathLoan *createLoan(FactManager &FactMgr,
   return nullptr;
 }
 
+/// Creates a loan for the storage location of a temporary object.
+/// \param MTW The MaterializeTemporaryExpr that represents the temporary
+/// binding. \return The new Loan.
 static const PathLoan *createLoan(FactManager &FactMgr,
-                                  const CXXBindTemporaryExpr *BTE) {
-  AccessPath Path(BTE);
-  return FactMgr.getLoanMgr().createLoan<PathLoan>(Path, BTE);
+                                  const MaterializeTemporaryExpr *MTE) {
+  AccessPath Path(MTE);
+  return FactMgr.getLoanMgr().createLoan<PathLoan>(Path, MTE);
+}
+
+/// Try to find a CXXBindTemporaryExpr that descends from MTE, stripping away
+/// any implicit casts.
+/// \param MTE MaterializeTemporaryExpr whose descendants we are interested in.
+/// \return Pointer to descendant CXXBindTemporaryExpr or nullptr when not
+/// found.
+static const CXXBindTemporaryExpr *
+getChildBinding(const MaterializeTemporaryExpr *MTE) {
+  const Expr *Child = MTE->getSubExpr()->IgnoreImpCasts();
+  return dyn_cast<CXXBindTemporaryExpr>(Child);
 }
 
 void FactsGenerator::run() {
@@ -227,16 +241,12 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
     const MaterializeTemporaryExpr *MTE) {
   if (!hasOrigin(MTE))
     return;
-  const Expr *Child = MTE->getSubExpr()->IgnoreImpCasts();
-  if (const CXXBindTemporaryExpr *BTE =
-          dyn_cast<const CXXBindTemporaryExpr>(Child)) {
-    // Issue a loan to MTE for the storage location represented by BTE
-    const Loan *L = createLoan(FactMgr, BTE);
+  if (getChildBinding(MTE)) {
+    // Issue a loan to MTE for the storage location represented by MTE.
+    const Loan *L = createLoan(FactMgr, MTE);
     OriginID OID = FactMgr.getOriginMgr().getOrCreate(*MTE);
     CurrentBlockFacts.push_back(FactMgr.createFact<IssueFact>(L->getID(), 
OID));
   } else {
-    // A temporary object's origin is the same as the origin of the
-    // expression that initializes it.
     killAndFlowOrigin(*MTE, *MTE->getSubExpr());
   }
 }
@@ -271,11 +281,14 @@ void FactsGenerator::handleTemporaryDtor(
     if (const auto *BL = dyn_cast<PathLoan>(Loan)) {
       // Check if the loan is for a temporary materialization and if that 
storage
       // location is the one being destructed.
-      const AccessPath AP = BL->getAccessPath();
-      const CXXBindTemporaryExpr *Path = AP.getAsCXXBindTemporaryExpr();
-      if (Path == BTE)
+      const AccessPath &AP = BL->getAccessPath();
+      const MaterializeTemporaryExpr *Path = 
AP.getAsMaterializeTemporaryExpr();
+      if (!Path)
+        continue;
+      if (BTE == getChildBinding(Path)) {
         CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
             BL->getID(), TemporaryDtor.getBindTemporaryExpr()->getEndLoc()));
+      }
     }
   }
 }
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp 
b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index 5f6ba6809d2fb..7d105e6a96967 100644
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
@@ -14,10 +14,10 @@ void PathLoan::dump(llvm::raw_ostream &OS) const {
   OS << getID() << " (Path: ";
   if (const clang::ValueDecl *VD = Path.getAsValueDecl()) {
     OS << VD->getNameAsString();
-  } else if (const clang::CXXBindTemporaryExpr *BTE =
-                 Path.getAsCXXBindTemporaryExpr()) {
+  } else if (const clang::MaterializeTemporaryExpr *MTE =
+                 Path.getAsMaterializeTemporaryExpr()) {
     // No nice "name" for the temporary, so deferring to LLVM default
-    OS << "CXXBindTemporaryExpr at " << BTE;
+    OS << "MaterializeTemporaryExpr at " << MTE;
   } else {
     llvm_unreachable("access path is not one of any supported types");
   }
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 635489581e236..78e82a3f89b0f 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -952,4 +952,4 @@ void use_temporary_after_destruction() {
   a = temporary(); // expected-warning {{object whose reference is captured 
does not live long enough}} \
                   expected-note {{destroyed here}}
   use(a); // expected-note {{later used here}}
-}
\ No newline at end of file
+}
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp 
b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 86e5d662880b6..9f585111ba40c 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -129,7 +129,7 @@ class LifetimeTestHelper {
   bool isLoanToATemporary(LoanID LID) {
     const Loan *L = Analysis.getFactManager().getLoanMgr().getLoan(LID);
     if (const auto *BL = dyn_cast<PathLoan>(L)) {
-      return BL->getAccessPath().getAsCXXBindTemporaryExpr() != nullptr;
+      return BL->getAccessPath().getAsMaterializeTemporaryExpr() != nullptr;
     }
     return false;
   }
@@ -841,7 +841,6 @@ TEST_F(LifetimeAnalysisTest, ExtraParenthesis) {
   EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "p1"));
 }
 
-// FIXME: Handle temporaries.
 TEST_F(LifetimeAnalysisTest, ViewFromTemporary) {
   SetupTest(R"(
     MyObj temporary();

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

Reply via email to