https://github.com/aeft updated https://github.com/llvm/llvm-project/pull/187917

>From 2c86039bf1e5f1e15294318d041053b5b608b395 Mon Sep 17 00:00:00 2001
From: Alex Wang <[email protected]>
Date: Sat, 21 Mar 2026 23:53:45 -0700
Subject: [PATCH 1/8] [LifetimeSafety] Track origins for lifetimebound calls
 returning record types

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  |   2 +-
 .../Analyses/LifetimeSafety/FactsGenerator.h  |   3 +
 .../Analyses/LifetimeSafety/Origins.h         |  22 +-
 .../LifetimeSafety/FactsGenerator.cpp         |  41 ++-
 .../LifetimeSafety/LifetimeSafety.cpp         |   3 +
 clang/lib/Analysis/LifetimeSafety/Origins.cpp |  88 ++++++-
 .../Sema/warn-lifetime-analysis-nocfg.cpp     |  20 +-
 clang/test/Sema/warn-lifetime-safety.cpp      | 249 +++++++++++++++++-
 8 files changed, 390 insertions(+), 38 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 0f848abd913d3..e821e96527deb 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -318,7 +318,7 @@ class TestPointFact : public Fact {
 class FactManager {
 public:
   FactManager(const AnalysisDeclContext &AC, const CFG &Cfg)
-      : OriginMgr(AC.getASTContext(), AC.getDecl()) {
+      : OriginMgr(AC.getASTContext()) {
     BlockToFacts.resize(Cfg.getNumBlockIDs());
   }
 
diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index dfcbdc7d73007..775b51afa0237 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -57,6 +57,9 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
   OriginList *getOriginsList(const ValueDecl &D);
   OriginList *getOriginsList(const Expr &E);
 
+  bool hasOrigins(QualType QT) const;
+  bool hasOrigins(const Expr *E) const;
+
   void flow(OriginList *Dst, OriginList *Src, bool Kill);
 
   void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr);
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
index 8c638bdcace3f..12148d9b29c9c 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
@@ -20,6 +20,7 @@
 #include "clang/AST/TypeBase.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeStats.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"
+#include "clang/Analysis/AnalysisDeclContext.h"
 #include "llvm/Support/raw_ostream.h"
 
 namespace clang::lifetimes::internal {
@@ -117,15 +118,17 @@ class OriginList {
   OriginList *InnerList = nullptr;
 };
 
-bool hasOrigins(QualType QT);
-bool hasOrigins(const Expr *E);
 bool doesDeclHaveStorage(const ValueDecl *D);
 
 /// Manages the creation, storage, and retrieval of origins for pointer-like
 /// variables and expressions.
 class OriginManager {
 public:
-  explicit OriginManager(ASTContext &AST, const Decl *D);
+  explicit OriginManager(ASTContext &AST);
+
+  /// Must be called after collectLifetimeboundOriginTypes() to ensure
+  /// ThisOrigins reflects the complete set of tracked types.
+  void initializeThisOrigins(const Decl *D);
 
   /// Gets or creates the OriginList for a given ValueDecl.
   ///
@@ -155,11 +158,20 @@ class OriginManager {
 
   unsigned getNumOrigins() const { return NextOriginID.Value; }
 
+  bool hasOrigins(QualType QT) const;
+  bool hasOrigins(const Expr *E) const;
+
   void dump(OriginID OID, llvm::raw_ostream &OS) const;
 
   /// Collects statistics about expressions that lack associated origins.
   void collectMissingOrigins(Stmt &FunctionBody, LifetimeSafetyStats &LSStats);
 
+  /// Pre-scans the function body (and constructor init lists) to discover
+  /// return types of [[clang::lifetimebound]] calls, registering them for
+  /// origin tracking.
+  void collectLifetimeboundOriginTypes(AnalysisDeclContext &AC);
+  void registerLifetimeboundOriginType(QualType QT);
+
 private:
   OriginID getNextOriginID() { return NextOriginID++; }
 
@@ -178,6 +190,10 @@ class OriginManager {
   llvm::DenseMap<const clang::ValueDecl *, OriginList *> DeclToList;
   llvm::DenseMap<const clang::Expr *, OriginList *> ExprToList;
   std::optional<OriginList *> ThisOrigins;
+  /// Types that are not inherently pointer-like but require origin tracking
+  /// because they are returned from functions with [[clang::lifetimebound]]
+  /// parameters.
+  llvm::DenseSet<const Type *> LifetimeboundOriginTypes;
 };
 } // namespace clang::lifetimes::internal
 
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 80a73a2bf687e..03b297c30f008 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -37,6 +37,14 @@ OriginList *FactsGenerator::getOriginsList(const Expr &E) {
   return FactMgr.getOriginMgr().getOrCreateList(&E);
 }
 
+bool FactsGenerator::hasOrigins(QualType QT) const {
+  return FactMgr.getOriginMgr().hasOrigins(QT);
+}
+
+bool FactsGenerator::hasOrigins(const Expr *E) const {
+  return FactMgr.getOriginMgr().hasOrigins(E);
+}
+
 /// Propagates origin information from Src to Dst through all levels of
 /// indirection, creating OriginFlowFacts at each level.
 ///
@@ -182,14 +190,13 @@ void FactsGenerator::VisitCXXConstructExpr(const 
CXXConstructExpr *CCE) {
     handleGSLPointerConstruction(CCE);
     return;
   }
-  // Implicit copy/move constructors of lambda closures lack
-  // [[clang::lifetimebound]], so `handleFunctionCall` cannot propagate 
origins.
-  // Handle them directly to keep the origin chain intact (e.g., `return
-  // lambda;` copies the closure).
-  if (const auto *RD = CCE->getType()->getAsCXXRecordDecl();
-      RD && RD->isLambda() &&
-      CCE->getConstructor()->isCopyOrMoveConstructor() &&
-      CCE->getNumArgs() == 1) {
+  // For defaulted (implicit or `= default`) copy/move constructors, propagate
+  // origins directly. User-defined copy/move constructors have opaque 
semantics
+  // and fall through to `handleFunctionCall`, where [[clang::lifetimebound]] 
is
+  // needed to propagate origins.
+  if (CCE->getConstructor()->isCopyOrMoveConstructor() &&
+      CCE->getConstructor()->isDefaulted() && CCE->getNumArgs() == 1 &&
+      hasOrigins(CCE->getType())) {
     const Expr *Arg = CCE->getArg(0);
     if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) {
       flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
@@ -398,8 +405,20 @@ void FactsGenerator::VisitCXXOperatorCallExpr(const 
CXXOperatorCallExpr *OCE) {
   // and are handled separately.
   if (OCE->getOperator() == OO_Equal && OCE->getNumArgs() == 2 &&
       hasOrigins(OCE->getArg(0)->getType())) {
-    handleAssignment(OCE->getArg(0), OCE->getArg(1));
-    return;
+    // Pointer-like types: assignment inherently propagates origins.
+    QualType LHSTy = OCE->getArg(0)->getType();
+    if (LHSTy->isPointerOrReferenceType() || isGslPointerType(LHSTy)) {
+      handleAssignment(OCE->getArg(0), OCE->getArg(1));
+      return;
+    }
+    // Other tracked types: only defaulted operator= propagates origins.
+    // User-defined operator= has opaque semantics, so don't handle them now.
+    if (const auto *MD =
+            dyn_cast_or_null<CXXMethodDecl>(OCE->getDirectCallee());
+        MD && MD->isDefaulted()) {
+      handleAssignment(OCE->getArg(0), OCE->getArg(1));
+      return;
+    }
   }
 
   ArrayRef Args = {OCE->getArgs(), OCE->getNumArgs()};
@@ -646,7 +665,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
   auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
     const ParmVarDecl *PVD = nullptr;
     if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
-        Method && Method->isInstance()) {
+        Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) {
       if (I == 0)
         // For the 'this' argument, the attribute is on the method itself.
         return implicitObjectParamIsLifetimeBound(Method) ||
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp 
b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
index 714f979fa5ee7..56a187202d8fa 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
@@ -71,6 +71,9 @@ void LifetimeSafetyAnalysis::run() {
 
   FactMgr = std::make_unique<FactManager>(AC, Cfg);
 
+  FactMgr->getOriginMgr().collectLifetimeboundOriginTypes(AC);
+  FactMgr->getOriginMgr().initializeThisOrigins(AC.getDecl());
+
   FactsGenerator FactGen(*FactMgr, AC);
   FactGen.run();
 
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp 
b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 0122f7a734541..2655835317d9b 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -18,6 +18,7 @@
 #include "clang/AST/TypeBase.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeStats.h"
+#include "clang/Analysis/AnalysisDeclContext.h"
 #include "llvm/ADT/StringMap.h"
 
 namespace clang::lifetimes::internal {
@@ -29,10 +30,10 @@ class MissingOriginCollector
 public:
   MissingOriginCollector(
       const llvm::DenseMap<const clang::Expr *, OriginList *> 
&ExprToOriginList,
-      LifetimeSafetyStats &LSStats)
-      : ExprToOriginList(ExprToOriginList), LSStats(LSStats) {}
+      const OriginManager &OM, LifetimeSafetyStats &LSStats)
+      : ExprToOriginList(ExprToOriginList), OM(OM), LSStats(LSStats) {}
   bool VisitExpr(Expr *E) {
-    if (!hasOrigins(E))
+    if (!OM.hasOrigins(E))
       return true;
     // Check if we have an origin for this expression.
     if (!ExprToOriginList.contains(E)) {
@@ -46,13 +47,60 @@ class MissingOriginCollector
 
 private:
   const llvm::DenseMap<const clang::Expr *, OriginList *> &ExprToOriginList;
+  const OriginManager &OM;
   LifetimeSafetyStats &LSStats;
 };
+
+class LifetimeboundOriginTypeCollector
+    : public RecursiveASTVisitor<LifetimeboundOriginTypeCollector> {
+public:
+  LifetimeboundOriginTypeCollector(OriginManager &OM) : OM(OM) {}
+
+  bool VisitCallExpr(const CallExpr *CE) {
+    if (const auto *FD = CE->getDirectCallee())
+      collect(FD, FD->getReturnType());
+    return true;
+  }
+
+  bool VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
+    collect(CCE->getConstructor(), CCE->getType());
+    return true;
+  }
+
+  bool shouldVisitLambdaBody() const { return false; }
+
+private:
+  OriginManager &OM;
+
+  void collect(const FunctionDecl *FD, QualType RetType) {
+    if (!FD)
+      return;
+    FD = getDeclWithMergedLifetimeBoundAttrs(FD);
+
+    if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+        MD && MD->isInstance() && !isa<CXXConstructorDecl>(MD) &&
+        implicitObjectParamIsLifetimeBound(MD)) {
+      OM.registerLifetimeboundOriginType(RetType);
+      return;
+    }
+
+    for (const auto *Param : FD->parameters()) {
+      if (Param->hasAttr<LifetimeBoundAttr>()) {
+        OM.registerLifetimeboundOriginType(RetType);
+        return;
+      }
+    }
+  }
+};
+
 } // namespace
 
-bool hasOrigins(QualType QT) {
+bool OriginManager::hasOrigins(QualType QT) const {
   if (QT->isPointerOrReferenceType() || isGslPointerType(QT))
     return true;
+  if (LifetimeboundOriginTypes.contains(
+          QT->getCanonicalTypeUnqualified().getTypePtr()))
+    return true;
   const auto *RD = QT->getAsCXXRecordDecl();
   if (!RD)
     return false;
@@ -70,7 +118,9 @@ bool hasOrigins(QualType QT) {
 ///
 /// An expression has origins if:
 /// - It's a glvalue (has addressable storage), OR
-/// - Its type is pointer-like (pointer, reference, or gsl::Pointer)
+/// - Its type is pointer-like (pointer, reference, or gsl::Pointer), OR
+/// - Its type is registered for origin tracking (e.g., return type of a
+/// [[clang::lifetimebound]] function)
 ///
 /// Examples:
 /// - `int x; x` : has origin (glvalue)
@@ -78,7 +128,7 @@ bool hasOrigins(QualType QT) {
 /// - `std::string_view{}` : has 1 origin (prvalue of pointer type)
 /// - `42` : no origin (prvalue of non-pointer type)
 /// - `x + y` : (where x, y are int) → no origin (prvalue of non-pointer type)
-bool hasOrigins(const Expr *E) {
+bool OriginManager::hasOrigins(const Expr *E) const {
   return E->isGLValue() || hasOrigins(E->getType());
 }
 
@@ -99,8 +149,9 @@ bool doesDeclHaveStorage(const ValueDecl *D) {
   return !D->getType()->isReferenceType();
 }
 
-OriginManager::OriginManager(ASTContext &AST, const Decl *D) : AST(AST) {
-  // Create OriginList for 'this' expr.
+OriginManager::OriginManager(ASTContext &AST) : AST(AST) {}
+
+void OriginManager::initializeThisOrigins(const Decl *D) {
   const auto *MD = llvm::dyn_cast_or_null<CXXMethodDecl>(D);
   if (!MD || !MD->isInstance())
     return;
@@ -232,8 +283,27 @@ const Origin &OriginManager::getOrigin(OriginID ID) const {
 
 void OriginManager::collectMissingOrigins(Stmt &FunctionBody,
                                           LifetimeSafetyStats &LSStats) {
-  MissingOriginCollector Collector(this->ExprToList, LSStats);
+  MissingOriginCollector Collector(this->ExprToList, *this, LSStats);
   Collector.TraverseStmt(const_cast<Stmt *>(&FunctionBody));
 }
 
+void OriginManager::collectLifetimeboundOriginTypes(AnalysisDeclContext &AC) {
+  LifetimeboundOriginTypeCollector Collector(*this);
+  if (Stmt *Body = AC.getBody())
+    Collector.TraverseStmt(Body);
+  if (const auto *CD = dyn_cast<CXXConstructorDecl>(AC.getDecl()))
+    for (const auto *Init : CD->inits())
+      Collector.TraverseStmt(Init->getInit());
+}
+
+void OriginManager::registerLifetimeboundOriginType(QualType QT) {
+  // TODO: Support [[gsl::Owner]] return types. For now, skip them because they
+  // change owner origin-list shape and can break GSL construction flow.
+  if (!QT->getAsCXXRecordDecl() || isGslOwnerType(QT) || hasOrigins(QT))
+    return;
+
+  LifetimeboundOriginTypes.insert(
+      QT->getCanonicalTypeUnqualified().getTypePtr());
+}
+
 } // namespace clang::lifetimes::internal
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp 
b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index a725119444e2f..d58f23e4b554c 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -576,12 +576,13 @@ struct FooView {
   FooView(const Foo& foo [[clang::lifetimebound]]);
 };
 FooView test3(int i, std::optional<Foo> a) {
-  // FIXME: Detect this using the CFG-based lifetime analysis.
-  //        Origin tracking for non-pointers type retured from lifetimebound 
fn is missing.
-  //        https://github.com/llvm/llvm-project/issues/163600
   if (i)
-    return *a; // expected-warning {{address of stack memory}}
-  return a.value(); // expected-warning {{address of stack memory}}
+    return *a; // expected-warning {{address of stack memory}} \
+               // cfg-warning {{address of stack memory is returned later}} \
+               // cfg-note {{returned here}}
+  return a.value(); // expected-warning {{address of stack memory}} \
+                    // cfg-warning {{address of stack memory is returned 
later}} \
+                    // cfg-note {{returned here}}
 }
 } // namespace GH93386
 
@@ -591,11 +592,10 @@ struct UrlAnalyzed {
 };
 std::string StrCat(std::string_view, std::string_view);
 void test1() {
-  // FIXME: Detect this using the CFG-based lifetime analysis.
-  //        Origin tracking for non-pointers type retured from lifetimebound 
fn is missing.
-  //        https://github.com/llvm/llvm-project/issues/163600
-  UrlAnalyzed url(StrCat("abc", "bcd")); // expected-warning {{object backing 
the pointer will be destroyed}}
-  use(url);
+  UrlAnalyzed url(StrCat("abc", "bcd")); // expected-warning {{object backing 
the pointer will be destroyed}} \
+                                         // cfg-warning {{object whose 
reference is captured does not live long enough}} \
+                                         // cfg-note {{destroyed here}}
+  use(url);                              // cfg-note {{later used here}}
 }
 
 std::string_view ReturnStringView(std::string_view abc 
[[clang::lifetimebound]]);
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 76d43445f8636..7119b0d9da832 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -909,7 +909,6 @@ void lifetimebound_return_reference() {
   (void)*ptr;             // expected-note {{later used here}}
 }
 
-// FIXME: No warning for non gsl::Pointer types. Origin tracking is only 
supported for pointer types.
 struct LifetimeBoundCtor {
   LifetimeBoundCtor();
   LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
@@ -919,9 +918,9 @@ void lifetimebound_ctor() {
   LifetimeBoundCtor v;
   {
     MyObj obj;
-    v = obj;
-  }
-  (void)v;
+    v = obj; // expected-warning {{object whose reference is captured does not 
live long enough}}
+  }          // expected-note {{destroyed here}}
+  (void)v;   // expected-note {{later used here}}
 }
 
 View lifetimebound_return_of_local() {
@@ -2126,3 +2125,245 @@ void indexing_with_static_operator() {
 
 }
 } // namespace static_call_operator
+
+namespace track_origins_for_lifetimebound_record_type {
+
+template <class T> void use(T);
+
+struct S {
+  S();
+  S(const std::string &s [[clang::lifetimebound]]);
+
+  S return_self_after_registration() const;
+};
+
+S getS(const std::string &s [[clang::lifetimebound]]);
+
+void from_free_function() {
+  S s = getS(std::string("temp")); // expected-warning {{object whose 
reference is captured does not live long enough}} \
+                                   // expected-note {{destroyed here}}
+  use(s);                          // expected-note {{later used here}}
+}
+
+void from_constructor() {
+  S s(std::string("temp")); // expected-warning {{object whose reference is 
captured does not live long enough}} \
+                              // expected-note {{destroyed here}}
+  use(s); // expected-note {{later used here}}
+}
+
+struct Factory {
+  S make(const std::string &s [[clang::lifetimebound]]);
+  static S create(const std::string &s [[clang::lifetimebound]]);
+  S makeThis() const [[clang::lifetimebound]];
+};
+
+void from_method() {
+  Factory f;
+  S s = f.make(std::string("temp")); // expected-warning {{object whose 
reference is captured does not live long enough}} \
+                                     // expected-note {{destroyed here}}
+  use(s);                            // expected-note {{later used here}}
+}
+
+void from_static_method() {
+  S s = Factory::create(std::string("temp")); // expected-warning {{object 
whose reference is captured does not live long enough}} \
+                                              // expected-note {{destroyed 
here}}
+  use(s);                                     // expected-note {{later used 
here}}
+}
+
+void from_lifetimebound_this_method() {
+  S value;
+  {
+    Factory f;
+    value = f.makeThis(); // expected-warning {{object whose reference is 
captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  use(value); // expected-note {{later used here}}
+}
+
+void across_scope() {
+  S s{};
+  {
+    std::string str{"abc"};
+    s = getS(str); // expected-warning {{object whose reference is captured 
does not live long enough}}
+  }                // expected-note {{destroyed here}}
+  use(s); // expected-note {{later used here}}
+}
+
+void same_scope() {
+  std::string str{"abc"};
+  S s = getS(str);
+  use(s);
+}
+
+S copy_propagation() {
+  std::string str{"abc"};
+  S a = getS(str); // expected-warning {{address of stack memory is returned 
later}}
+  S b = a;
+  return b; // expected-note {{returned here}}
+}
+
+void assignment_propagation() {
+  S a, b;
+  {
+    std::string str{"abc"};
+    a = getS(str); // expected-warning {{object whose reference is captured 
does not live long enough}}
+    b = a;
+  }                // expected-note {{destroyed here}}
+  use(b);          // expected-note {{later used here}}
+}
+
+S getSNoAnnotation(const std::string &s);
+
+void no_annotation() {
+  S s = getSNoAnnotation(std::string("temp"));
+  use(s);
+}
+
+void mix_annotated_and_not() {
+  S s1 = getS(std::string("temp")); // expected-warning {{object whose 
reference is captured does not live long enough}} \
+                                    // expected-note {{destroyed here}}
+  S s2 = getSNoAnnotation(std::string("temp"));
+  use(s1); // expected-note {{later used here}}
+  use(s2);
+}
+
+S getS2(const std::string &a [[clang::lifetimebound]], const std::string &b 
[[clang::lifetimebound]]);
+
+S multiple_lifetimebound_params() {
+  std::string str{"abc"};
+  S s = getS2(str, std::string("temp")); // expected-warning {{address of 
stack memory is returned later}} \
+                                         // expected-warning {{object whose 
reference is captured does not live long enough}} \
+                                         // expected-note {{destroyed here}}
+  return s;                              // expected-note {{returned here}} \
+                                         // expected-note {{later used here}}
+}
+
+int getInt(const std::string &s [[clang::lifetimebound]]);
+
+void primitive_return() {
+  int i = getInt(std::string("temp"));
+  use(i);
+}
+
+template <class T>
+T make(const std::string &s [[clang::lifetimebound]]);
+
+void from_template_instantiation() {
+  S s = make<S>(std::string("temp")); // expected-warning {{object whose 
reference is captured does not live long enough}} \
+                                      // expected-note {{destroyed here}}
+  use(s);                             // expected-note {{later used here}}
+}
+
+struct FieldInitFromLifetimebound {
+  S value; // function-note {{this field dangles}}
+  FieldInitFromLifetimebound() : value(getS(std::string("temp"))) {} // 
function-warning {{address of stack memory escapes to a field}}
+};
+
+S S::return_self_after_registration() const {
+  std::string s{"abc"};
+  getS(s);
+  return *this;
+}
+
+struct SWithUserDefinedCopyLikeOps {
+  SWithUserDefinedCopyLikeOps();
+  SWithUserDefinedCopyLikeOps(const std::string &s [[clang::lifetimebound]]) : 
owned(s), data(s) {}
+
+  SWithUserDefinedCopyLikeOps(const SWithUserDefinedCopyLikeOps &other) : 
owned("copy"), data(owned) {}
+
+  SWithUserDefinedCopyLikeOps &operator=(const SWithUserDefinedCopyLikeOps &) {
+    owned = "copy";
+    data = owned;
+    return *this;
+  }
+
+  std::string owned;
+  std::string_view data;
+};
+
+SWithUserDefinedCopyLikeOps getSWithUserDefinedCopyLikeOps(const std::string 
&s [[clang::lifetimebound]]);
+
+SWithUserDefinedCopyLikeOps 
user_defined_copy_ctor_should_not_assume_origin_propagation() {
+  std::string str{"abc"};
+  SWithUserDefinedCopyLikeOps s = getSWithUserDefinedCopyLikeOps(str);
+  SWithUserDefinedCopyLikeOps copy = s; // Copy is rescued by user-defined 
copy constructor, so should not warn.
+  return copy;
+}
+
+void user_defined_assignment_should_not_assume_origin_propagation() {
+  SWithUserDefinedCopyLikeOps dst;
+  {
+    std::string str{"abc"};
+    SWithUserDefinedCopyLikeOps src = getSWithUserDefinedCopyLikeOps(str);
+    dst = src;
+  }
+  use(dst);
+}
+
+struct SWithOriginPropagatingCopy {
+  SWithOriginPropagatingCopy();
+  SWithOriginPropagatingCopy(const std::string &s [[clang::lifetimebound]]) : 
data(s) {}
+  SWithOriginPropagatingCopy(const SWithOriginPropagatingCopy &other) : 
data(other.data) {}
+  std::string_view data;
+};
+
+SWithOriginPropagatingCopy getSWithOriginPropagatingCopy(const std::string &s 
[[clang::lifetimebound]]);
+
+// FIXME: False negative. User-defined copy ctor may propagate origins.
+SWithOriginPropagatingCopy user_defined_copy_with_origin_propagation() {
+  std::string str{"abc"};
+  SWithOriginPropagatingCopy s = getSWithOriginPropagatingCopy(str);
+  SWithOriginPropagatingCopy copy = s;
+  return copy; // Should warn.
+}
+
+struct DefaultedOuter {
+  DefaultedOuter();
+  DefaultedOuter(const std::string &s [[clang::lifetimebound]]) : inner(s) {}
+  SWithUserDefinedCopyLikeOps inner;
+};
+
+DefaultedOuter getDefaultedOuter(const std::string &s 
[[clang::lifetimebound]]);
+
+// FIXME: False positive. The defaulted outer copy ctor invokes
+// SWithUserDefinedCopyLikeOps's user-defined copy ctor, so `copy` should be
+// semantically safe.
+DefaultedOuter nested_defaulted_outer_with_user_defined_inner() {
+  std::string str{"abc"};
+  DefaultedOuter o = getDefaultedOuter(str); // expected-warning {{address of 
stack memory is returned later}}
+  DefaultedOuter copy = o;
+  return copy; // expected-note {{returned here}}
+}
+
+std::string_view getSV(S s [[clang::lifetimebound]]);
+
+// FIXME: False negative. Non-pointer/ref/gsl::Pointer parameter types marked
+// [[clang::lifetimebound]] are not registered for origin tracking.
+void dangling_view_from_non_pointer_param() {
+  std::string_view sv;
+  {
+    S s;
+    sv = getSV(s);
+  }
+  use(sv); // Should warn.
+}
+
+const S &getRef(const std::string &s [[clang::lifetimebound]]);
+
+// FIXME: False negative. The analysis tracks the returned reference,
+// but loses that information when it is copied into a new `S` object.
+S from_ref() {
+  std::string str{"abc"};
+  S s = getRef(str);
+  return s; // Should warn.
+}
+
+MyObj getMyObj(const MyObj &obj [[clang::lifetimebound]]);
+
+void gsl_owner_return_does_not_crash() {
+  MyObj obj;
+  View v = obj;
+  getMyObj(obj);
+  use(v);
+}
+
+} // namespace track_origins_for_lifetimebound_record_type

>From e6469c2d5953d6100fde75de5a66acdc36f42a57 Mon Sep 17 00:00:00 2001
From: Alex Wang <[email protected]>
Date: Tue, 24 Mar 2026 22:35:39 -0400
Subject: [PATCH 2/8] remove  FIXME

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

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 7119b0d9da832..577f8e56df864 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2299,6 +2299,14 @@ void 
user_defined_assignment_should_not_assume_origin_propagation() {
   use(dst);
 }
 
+const S &getRef(const std::string &s [[clang::lifetimebound]]);
+
+S from_ref() {
+  std::string str{"abc"};
+  S s = getRef(str);
+  return s;
+}
+
 struct SWithOriginPropagatingCopy {
   SWithOriginPropagatingCopy();
   SWithOriginPropagatingCopy(const std::string &s [[clang::lifetimebound]]) : 
data(s) {}
@@ -2347,16 +2355,6 @@ void dangling_view_from_non_pointer_param() {
   use(sv); // Should warn.
 }
 
-const S &getRef(const std::string &s [[clang::lifetimebound]]);
-
-// FIXME: False negative. The analysis tracks the returned reference,
-// but loses that information when it is copied into a new `S` object.
-S from_ref() {
-  std::string str{"abc"};
-  S s = getRef(str);
-  return s; // Should warn.
-}
-
 MyObj getMyObj(const MyObj &obj [[clang::lifetimebound]]);
 
 void gsl_owner_return_does_not_crash() {

>From ca7edf1bac2090e7b5cc019f059391c29ee32244 Mon Sep 17 00:00:00 2001
From: Alex Wang <[email protected]>
Date: Wed, 25 Mar 2026 12:19:40 -0400
Subject: [PATCH 3/8] make collectLifetimeboundOriginTypes and
 initializeThisOrigins private

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  |  3 +--
 .../Analyses/LifetimeSafety/Origins.h         | 20 +++++++--------
 .../LifetimeSafety/LifetimeSafety.cpp         |  3 ---
 clang/lib/Analysis/LifetimeSafety/Origins.cpp | 25 +++++++++++++------
 4 files changed, 27 insertions(+), 24 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index e821e96527deb..e852f66a3fde2 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -317,8 +317,7 @@ class TestPointFact : public Fact {
 
 class FactManager {
 public:
-  FactManager(const AnalysisDeclContext &AC, const CFG &Cfg)
-      : OriginMgr(AC.getASTContext()) {
+  FactManager(const AnalysisDeclContext &AC, const CFG &Cfg) : OriginMgr(AC) {
     BlockToFacts.resize(Cfg.getNumBlockIDs());
   }
 
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
index 12148d9b29c9c..a918e874a6fae 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
@@ -124,11 +124,7 @@ bool doesDeclHaveStorage(const ValueDecl *D);
 /// variables and expressions.
 class OriginManager {
 public:
-  explicit OriginManager(ASTContext &AST);
-
-  /// Must be called after collectLifetimeboundOriginTypes() to ensure
-  /// ThisOrigins reflects the complete set of tracked types.
-  void initializeThisOrigins(const Decl *D);
+  explicit OriginManager(const AnalysisDeclContext &AC);
 
   /// Gets or creates the OriginList for a given ValueDecl.
   ///
@@ -166,12 +162,6 @@ class OriginManager {
   /// Collects statistics about expressions that lack associated origins.
   void collectMissingOrigins(Stmt &FunctionBody, LifetimeSafetyStats &LSStats);
 
-  /// Pre-scans the function body (and constructor init lists) to discover
-  /// return types of [[clang::lifetimebound]] calls, registering them for
-  /// origin tracking.
-  void collectLifetimeboundOriginTypes(AnalysisDeclContext &AC);
-  void registerLifetimeboundOriginType(QualType QT);
-
 private:
   OriginID getNextOriginID() { return NextOriginID++; }
 
@@ -181,6 +171,14 @@ class OriginManager {
   template <typename T>
   OriginList *buildListForType(QualType QT, const T *Node);
 
+  void initializeThisOrigins(const Decl *D);
+
+  /// Pre-scans the function body (and constructor init lists) to discover
+  /// return types of [[clang::lifetimebound]] calls, registering them for
+  /// origin tracking.
+  void collectLifetimeboundOriginTypes(const AnalysisDeclContext &AC);
+  void registerLifetimeboundOriginType(QualType QT);
+
   ASTContext &AST;
   OriginID NextOriginID{0};
   /// TODO(opt): Profile and evaluate the usefulness of small buffer
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp 
b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
index 56a187202d8fa..714f979fa5ee7 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
@@ -71,9 +71,6 @@ void LifetimeSafetyAnalysis::run() {
 
   FactMgr = std::make_unique<FactManager>(AC, Cfg);
 
-  FactMgr->getOriginMgr().collectLifetimeboundOriginTypes(AC);
-  FactMgr->getOriginMgr().initializeThisOrigins(AC.getDecl());
-
   FactsGenerator FactGen(*FactMgr, AC);
   FactGen.run();
 
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp 
b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 2655835317d9b..9e67476174e17 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -54,8 +54,6 @@ class MissingOriginCollector
 class LifetimeboundOriginTypeCollector
     : public RecursiveASTVisitor<LifetimeboundOriginTypeCollector> {
 public:
-  LifetimeboundOriginTypeCollector(OriginManager &OM) : OM(OM) {}
-
   bool VisitCallExpr(const CallExpr *CE) {
     if (const auto *FD = CE->getDirectCallee())
       collect(FD, FD->getReturnType());
@@ -69,8 +67,12 @@ class LifetimeboundOriginTypeCollector
 
   bool shouldVisitLambdaBody() const { return false; }
 
+  const llvm::SmallVector<QualType> &getCollectedTypes() const {
+    return CollectedTypes;
+  }
+
 private:
-  OriginManager &OM;
+  llvm::SmallVector<QualType> CollectedTypes;
 
   void collect(const FunctionDecl *FD, QualType RetType) {
     if (!FD)
@@ -80,13 +82,13 @@ class LifetimeboundOriginTypeCollector
     if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
         MD && MD->isInstance() && !isa<CXXConstructorDecl>(MD) &&
         implicitObjectParamIsLifetimeBound(MD)) {
-      OM.registerLifetimeboundOriginType(RetType);
+      CollectedTypes.push_back(RetType);
       return;
     }
 
     for (const auto *Param : FD->parameters()) {
       if (Param->hasAttr<LifetimeBoundAttr>()) {
-        OM.registerLifetimeboundOriginType(RetType);
+        CollectedTypes.push_back(RetType);
         return;
       }
     }
@@ -149,7 +151,11 @@ bool doesDeclHaveStorage(const ValueDecl *D) {
   return !D->getType()->isReferenceType();
 }
 
-OriginManager::OriginManager(ASTContext &AST) : AST(AST) {}
+OriginManager::OriginManager(const AnalysisDeclContext &AC)
+    : AST(AC.getASTContext()) {
+  collectLifetimeboundOriginTypes(AC);
+  initializeThisOrigins(AC.getDecl());
+}
 
 void OriginManager::initializeThisOrigins(const Decl *D) {
   const auto *MD = llvm::dyn_cast_or_null<CXXMethodDecl>(D);
@@ -287,13 +293,16 @@ void OriginManager::collectMissingOrigins(Stmt 
&FunctionBody,
   Collector.TraverseStmt(const_cast<Stmt *>(&FunctionBody));
 }
 
-void OriginManager::collectLifetimeboundOriginTypes(AnalysisDeclContext &AC) {
-  LifetimeboundOriginTypeCollector Collector(*this);
+void OriginManager::collectLifetimeboundOriginTypes(
+    const AnalysisDeclContext &AC) {
+  LifetimeboundOriginTypeCollector Collector;
   if (Stmt *Body = AC.getBody())
     Collector.TraverseStmt(Body);
   if (const auto *CD = dyn_cast<CXXConstructorDecl>(AC.getDecl()))
     for (const auto *Init : CD->inits())
       Collector.TraverseStmt(Init->getInit());
+  for (QualType QT : Collector.getCollectedTypes())
+    registerLifetimeboundOriginType(QT);
 }
 
 void OriginManager::registerLifetimeboundOriginType(QualType QT) {

>From 685157b554e99d7e9d05bd894a7aca526dafa683 Mon Sep 17 00:00:00 2001
From: Alex Wang <[email protected]>
Date: Wed, 25 Mar 2026 12:37:09 -0400
Subject: [PATCH 4/8] update comment

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

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
index a918e874a6fae..3940c893fca98 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
@@ -189,8 +189,8 @@ class OriginManager {
   llvm::DenseMap<const clang::Expr *, OriginList *> ExprToList;
   std::optional<OriginList *> ThisOrigins;
   /// Types that are not inherently pointer-like but require origin tracking
-  /// because they are returned from functions with [[clang::lifetimebound]]
-  /// parameters.
+  /// because of lifetime annotations (e.g., [[clang::lifetimebound]]) on
+  /// functions that return them.
   llvm::DenseSet<const Type *> LifetimeboundOriginTypes;
 };
 } // namespace clang::lifetimes::internal

>From 12c9bd3d2a679d76ec7fc4725c45a02dabb57b4b Mon Sep 17 00:00:00 2001
From: Alex Wang <[email protected]>
Date: Wed, 25 Mar 2026 14:42:02 -0400
Subject: [PATCH 5/8] update test

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

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 577f8e56df864..1c461dd960936 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2135,6 +2135,7 @@ struct S {
   S(const std::string &s [[clang::lifetimebound]]);
 
   S return_self_after_registration() const;
+  std::string_view getData() const [[clang::lifetimebound]];
 };
 
 S getS(const std::string &s [[clang::lifetimebound]]);
@@ -2147,8 +2148,8 @@ void from_free_function() {
 
 void from_constructor() {
   S s(std::string("temp")); // expected-warning {{object whose reference is 
captured does not live long enough}} \
-                              // expected-note {{destroyed here}}
-  use(s); // expected-note {{later used here}}
+                            // expected-note {{destroyed here}}
+  use(s);                   // expected-note {{later used here}}
 }
 
 struct Factory {
@@ -2175,8 +2176,8 @@ void from_lifetimebound_this_method() {
   {
     Factory f;
     value = f.makeThis(); // expected-warning {{object whose reference is 
captured does not live long enough}}
-  } // expected-note {{destroyed here}}
-  use(value); // expected-note {{later used here}}
+  }                       // expected-note {{destroyed here}}
+  use(value);             // expected-note {{later used here}}
 }
 
 void across_scope() {
@@ -2185,7 +2186,7 @@ void across_scope() {
     std::string str{"abc"};
     s = getS(str); // expected-warning {{object whose reference is captured 
does not live long enough}}
   }                // expected-note {{destroyed here}}
-  use(s); // expected-note {{later used here}}
+  use(s);          // expected-note {{later used here}}
 }
 
 void same_scope() {
@@ -2239,6 +2240,8 @@ S multiple_lifetimebound_params() {
 
 int getInt(const std::string &s [[clang::lifetimebound]]);
 
+// TODO: Diagnose [[clang::lifetimebound]] on functions whose return value
+// cannot refer to any object (e.g., returning int or enum).
 void primitive_return() {
   int i = getInt(std::string("temp"));
   use(i);
@@ -2364,4 +2367,23 @@ void gsl_owner_return_does_not_crash() {
   use(v);
 }
 
+std::unique_ptr<S> getUniqueS(const std::string &s [[clang::lifetimebound]]);
+
+// FIXME: GSL Owner return types of lifetimebound calls are not yet tracked.
+void owner_return_unique_ptr_s() {
+  auto ptr = getUniqueS(std::string("temp"));
+  (void)ptr; // Should warn.
+}
+
+// FIXME: The warning here is from the local unique_ptr being destroyed on
+// return, not from lifetimebound origin tracking. GSL Owner return types are
+// not yet tracked.
+std::string_view return_dangling_view_through_owner() {
+  std::string local;
+  auto ups = getUniqueS(local);
+  S* s = ups.get(); // expected-warning {{address of stack memory is returned 
later}}
+  std::string_view sv = s->getData();
+  return sv; // expected-note {{returned here}}
+}
+
 } // namespace track_origins_for_lifetimebound_record_type

>From 10da73123e068c57f80fd012102caa8c10c617b2 Mon Sep 17 00:00:00 2001
From: Alex Wang <[email protected]>
Date: Wed, 25 Mar 2026 17:26:12 -0400
Subject: [PATCH 6/8] support gsl::owner return type origin tracking

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

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 03b297c30f008..f0507ebc9f04a 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -707,9 +707,9 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
         ArgList = getRValueOrigins(Args[I], ArgList);
       }
       if (isGslOwnerType(Args[I]->getType())) {
-        // GSL construction creates a view that borrows from arguments.
-        // This implies flowing origins through the list structure.
-        flow(CallList, ArgList, KillSrc);
+        CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+            CallList->getOuterOriginID(), ArgList->getOuterOriginID(),
+            KillSrc));
         KillSrc = false;
       }
     } else if (shouldTrackPointerImplicitObjectArg(I)) {
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp 
b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 9e67476174e17..fc61b1e260eba 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -306,9 +306,7 @@ void OriginManager::collectLifetimeboundOriginTypes(
 }
 
 void OriginManager::registerLifetimeboundOriginType(QualType QT) {
-  // TODO: Support [[gsl::Owner]] return types. For now, skip them because they
-  // change owner origin-list shape and can break GSL construction flow.
-  if (!QT->getAsCXXRecordDecl() || isGslOwnerType(QT) || hasOrigins(QT))
+  if (!QT->getAsCXXRecordDecl() || hasOrigins(QT))
     return;
 
   LifetimeboundOriginTypes.insert(
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 1c461dd960936..ecc0844b73a9d 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2360,7 +2360,7 @@ void dangling_view_from_non_pointer_param() {
 
 MyObj getMyObj(const MyObj &obj [[clang::lifetimebound]]);
 
-void gsl_owner_return_does_not_crash() {
+void gsl_owner_return() {
   MyObj obj;
   View v = obj;
   getMyObj(obj);
@@ -2369,15 +2369,14 @@ void gsl_owner_return_does_not_crash() {
 
 std::unique_ptr<S> getUniqueS(const std::string &s [[clang::lifetimebound]]);
 
-// FIXME: GSL Owner return types of lifetimebound calls are not yet tracked.
 void owner_return_unique_ptr_s() {
-  auto ptr = getUniqueS(std::string("temp"));
-  (void)ptr; // Should warn.
+  auto ptr = getUniqueS(std::string("temp")); // expected-warning {{object 
whose reference is captured does not live long enough}} \
+                                              // expected-note {{destroyed 
here}}
+  (void)ptr;                                  // expected-note {{later used 
here}}
 }
 
 // FIXME: The warning here is from the local unique_ptr being destroyed on
-// return, not from lifetimebound origin tracking. GSL Owner return types are
-// not yet tracked.
+// return. The chain breaks and doesn't trace back to `auto ups = 
getUniqueS(local)`.
 std::string_view return_dangling_view_through_owner() {
   std::string local;
   auto ups = getUniqueS(local);

>From 52b6562b3330794105d7e6dc387954e85f62fa3c Mon Sep 17 00:00:00 2001
From: Zhijie Wang <[email protected]>
Date: Wed, 25 Mar 2026 18:19:53 -0400
Subject: [PATCH 7/8] add test

---
 clang/test/Sema/Inputs/lifetime-analysis.h |  1 +
 clang/test/Sema/warn-lifetime-safety.cpp   | 14 ++++++++++++++
 2 files changed, 15 insertions(+)

diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h 
b/clang/test/Sema/Inputs/lifetime-analysis.h
index 0b6bdaef83f9d..2cb9d010e4743 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -191,6 +191,7 @@ template<typename T>
 struct unique_ptr {
   unique_ptr();
   unique_ptr(unique_ptr<T>&&);
+  unique_ptr& operator=(unique_ptr<T>&&);
   ~unique_ptr();
   T* release();
   T &operator*();
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index ecc0844b73a9d..40714077c51e3 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2385,4 +2385,18 @@ std::string_view return_dangling_view_through_owner() {
   return sv; // expected-note {{returned here}}
 }
 
+// FIXME: False negative. The loan on `local` doesn't reach `s->getData()`:
+// (1) move assignment is not defaulted, so origins don't propagate to `ups`,
+// and (2) even if they did, `ups.get()` connects to the owner's storage
+// origin, not the value origin holding the loan.
+void owner_outlives_lifetimebound_source() {
+  std::unique_ptr<S> ups;
+  {
+    std::string local;
+    ups = getUniqueS(local);
+  }
+  S* s = ups.get();
+  use(s->getData()); // Should warn.
+}
+
 } // namespace track_origins_for_lifetimebound_record_type

>From dcc72178be8843a77a445145b15bc1a924c89c42 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <[email protected]>
Date: Wed, 25 Mar 2026 20:55:38 -0400
Subject: [PATCH 8/8] update comment

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

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 40714077c51e3..699bff3a463bd 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2238,10 +2238,10 @@ S multiple_lifetimebound_params() {
                                          // expected-note {{later used here}}
 }
 
-int getInt(const std::string &s [[clang::lifetimebound]]);
-
 // TODO: Diagnose [[clang::lifetimebound]] on functions whose return value
 // cannot refer to any object (e.g., returning int or enum).
+int getInt(const std::string &s [[clang::lifetimebound]]);
+
 void primitive_return() {
   int i = getInt(std::string("temp"));
   use(i);

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

Reply via email to