[clang] [llvm] [LifetimeSafety] Add lifetimebound inference for std::make_unique (PR #191632)

Utkarsh Saxena via cfe-commits cfe-commits at lists.llvm.org
Wed Apr 15 10:47:58 PDT 2026


https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/191632

>From ea1e700d3c41ca48563e27c1a57fd7a86be50d8d Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sat, 11 Apr 2026 13:11:00 +0000
Subject: [PATCH 1/9] Make Unique inference

---
 a.o                                           | Bin 0 -> 3448 bytes
 .../LifetimeSafety/FactsGenerator.cpp         |   3 +-
 clang/lib/Analysis/LifetimeSafety/Origins.cpp |   1 +
 clang/lib/Sema/SemaAttr.cpp                   |  55 ++++++++++++++
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |   2 +
 clang/test/Sema/Inputs/lifetime-analysis.h    |   3 +
 .../Sema/warn-lifetime-analysis-nocfg.cpp     |  11 +++
 clang/test/Sema/warn-lifetime-safety.cpp      |  70 +++++++++++++++++-
 8 files changed, 141 insertions(+), 4 deletions(-)
 create mode 100644 a.o

diff --git a/a.o b/a.o
new file mode 100644
index 0000000000000000000000000000000000000000..62582c83f3bfdb00d8640bee02912561d1d29b77
GIT binary patch
literal 3448
zcmb_eO>7%Q6dv16V$viHAqP+qq^J at _qMLP$9D-1B<ZQdpG(`C!qN3Vt)@#SqKa0I~
z+#;)$A|WhODToV*100aZp$Ei;8w9lojyV*mNE|Bhqe$i89*}YXnfG?*+2gg%1)enf
zX1;mzzVFS>j(ts;KkN7T7 at 3cqVXYCNjFp3}e$?qk**HY5s at 31r?LTDoMx}9pxLQ^A
z?=!Wg>_e{(@LlyrvF=O(3_~}n8NPj6zI02iep`k33kOx@*H)%l<-iGUG<?C}$+PU)
z=Na)u)oNu=tsVl$56}f)tbc>LxrXoExqZ-4S8FKhK&=(sbf~TfGnG9zxCSY<Z%08z
ztpXxkCHJbz&tJC^)++a$U<O)-V2jw9zBgFxMXrYn0J1~1cA>6TgO_eOdn7!Jw4O~)
zl+1h~mCH^{g(X>%lPP=R)TV7)g%>6#VX#@;kc?brGMz5DkJDB at _oivs5=Mq*!trT6
zq8sK+Vp<MGOgU`K$VMa_nTjOh8`Ft+VrFK;ke at -;aZc^Ky2O0tQQz^Qf#43*^Elwx
z&>q9b?8xXl{@8H;3IxEA=6qKd=be=UA7j*UT7MD-*wEt8j$ns!_-7%>>`?!x8#9k{
zVAr6v$E`KvaEtq)4M0PF91n6o1b!IWzu5;cd4xS^bc+cve>a!~Ts3WT!%nE9vJJ;U
zz){!%1CEN@?G6^u1xGaCREE8lJptV at K_~7h+SAZr8#^os`JWQ>;NGIW4y{|f+5`Wt
z2mVnH9LwsK|4T;?zA at LFGX>pGpmmFX0P!dM_-0+B9XtfQTRgpgd^5=MSHrbad_Cev
z1w1U^$U^b(sXB)7n3M1|7j{mBh_4Iyh!B55z>f*|O9GB{Q2qr0e^|it0zM|-Zwt7{
z%VFpHpl=(o2|j_xm-Z5&^8)?|@1tAieT)~sj+B50cpu%n0^TR!?+ZA7zhr~sM~{t-
zFzw<(WJS*>O*<A+mhDieVCPfWq*h9qTQK>YZ5HejMx9 at _Bkr70^28}lKy!XBL_kO`
zW>aq!P0h0NFLN5jnU?ZqT9<6IY%{z_GiDY#$$YM8F<mk&3mCIo8F<Fii=_;cvN_w7
z&MmB-F4($pnMru at YI-~lmvoY8tJ$qoHm)t1g<{5JZf<i^OXPJNCJjR~%Z6#$nyqi7
z0f9Q82mVUN at l7$3b`fjK=$B2+l}@r>S at IT*yCv4QqA7FMt1GhB$@o8|#qEMr3m#(H
z*w~b^xD52?VzIV*LwrZ#x~)rdYp^{DNz;<$vSz`LTrR7pQ}z|DBzG*7>8$)0|0gUK
z&ne}4jUUD*#lsDgaJ{ryg=6B`#egV2z~~-!N1b<587A;9iBbUfpCSmh<2r`l9!jBa
z^HGlf0v<JaWU|lreCvjE0iKr6GWH(y at ILX{b)Jy!Qsl?`k=BzR1ajo>_XzlcC#2sM
zKJ2)g`(Uw?%d=kQmjvz)8uH^Fd+T52{C at 9x6a_~5-BC+FjeFI<!}Adz^5Z^H-Q;g_
zesBFz&QHH%#OKx1i~mE;Pwx-;u at Nu%`<y at EsehgGlYZj&j(>+ZCzogaF5iDTXUL6O
zXg%TEfFYF(O|(tUpAkXmbGYe07cepOM+y|dF0ntknB|er?(+4!p7o at k;5*R4`lAGJ
zzsKv};bW{H-yN-|`o97!FyF|5yAG{si~K^MdjZ#@dCU46Ft2uu+vOnspFu<S5Hv6U
EKZPRj`v3p{

literal 0
HcmV?d00001

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 2a2ef88987286..be9dd03dfe3da 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -489,7 +489,8 @@ void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
       hasOrigins(OCE->getArg(0)->getType())) {
     // Pointer-like types: assignment inherently propagates origins.
     QualType LHSTy = OCE->getArg(0)->getType();
-    if (LHSTy->isPointerOrReferenceType() || isGslPointerType(LHSTy)) {
+    if (LHSTy->isPointerOrReferenceType() || isGslPointerType(LHSTy) ||
+        isGslOwnerType(LHSTy)) {
       handleAssignment(OCE->getArg(0), OCE->getArg(1));
       return;
     }
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index fdd2671dee2e0..72c61042afb3a 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -68,6 +68,7 @@ class LifetimeAnnotatedOriginTypeCollector
   }
 
   bool shouldVisitLambdaBody() const { return false; }
+  bool shouldVisitTemplateInstantiations() const { return true; }
 
   const llvm::SmallVector<QualType> &getCollectedTypes() const {
     return CollectedTypes;
diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index 7c79f954e6743..d173b1ba58d8e 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -250,6 +250,61 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
     }
     return;
   }
+
+  // Handle std::make_unique to propagate lifetimebound attributes from the
+  // constructed type's constructor to make_unique's parameters.
+  if (FD->isInStdNamespace() && FD->getDeclName().isIdentifier() &&
+      FD->getName() == "make_unique") {
+    if (!FD->isFunctionTemplateSpecialization())
+      return;
+
+    const TemplateArgumentList *TAL = FD->getTemplateSpecializationArgs();
+    if (!TAL || TAL->size() < 1)
+      return;
+
+    // make_unique's first template argument is the type being constructed.
+    TemplateArgument TA = TAL->get(0);
+    if (TA.getKind() != TemplateArgument::Type)
+      return;
+
+    QualType T = TA.getAsType();
+    const auto *RD = T->getAsCXXRecordDecl();
+    if (!RD)
+      return;
+
+    // Find the constructor that matches make_unique's arguments.
+    for (const auto *Ctor : RD->ctors()) {
+      if (Ctor->getNumParams() != FD->getNumParams())
+        continue;
+
+      bool Compatible = true;
+      for (unsigned i = 0; i < Ctor->getNumParams(); ++i) {
+        QualType CtorParamType = Ctor->getParamDecl(i)->getType();
+        QualType FDParamType = FD->getParamDecl(i)->getType();
+        // Compare types ignoring references.
+        if (!Context.hasSameUnqualifiedType(
+                CtorParamType.getNonReferenceType(),
+                FDParamType.getNonReferenceType())) {
+          Compatible = false;
+          break;
+        }
+      }
+
+      if (!Compatible)
+        continue;
+      // Propagate lifetimebound attributes only if the constructor parameter is
+      // a reference. This avoids incorrect loan tracking when a by-value view
+      // (like string_view) is passed by reference to make_unique.
+      for (unsigned i = 0; i < Ctor->getNumParams(); ++i)
+        if (Ctor->getParamDecl(i)->hasAttr<LifetimeBoundAttr>() &&
+            Ctor->getParamDecl(i)->getType()->isReferenceType())
+          FD->getParamDecl(i)->addAttr(
+              LifetimeBoundAttr::CreateImplicit(Context, FD->getLocation()));
+      break; // Found matching constructor, done.
+    }
+    return;
+  }
+
   if (auto *CMD = dyn_cast<CXXMethodDecl>(FD)) {
     const auto *CRD = CMD->getParent();
     if (!CRD->isInStdNamespace() || !CRD->getIdentifier())
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 09c2482168ab7..954160d4112a5 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -5473,6 +5473,8 @@ TemplateDeclInstantiator::InitFunctionInstantiation(FunctionDecl *New,
   SemaRef.InstantiateAttrs(TemplateArgs, Definition, New,
                            LateAttrs, StartingScope);
 
+  SemaRef.inferLifetimeBoundAttribute(New);
+
   return false;
 }
 
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index d1e847d20cc50..67e6c0f18de5c 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -205,6 +205,9 @@ struct unique_ptr {
   T *get() const;
 };
 
+template<typename T, typename... Args>
+unique_ptr<T> make_unique(Args&&... args);
+
 template<typename T>
 struct optional {
   optional();
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 2b6c2d3d8bdb6..e07c90f248764 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -174,6 +174,17 @@ void initLocalGslPtrWithTempOwner() {
   use(global2, p2);                       // cfg-note 2 {{later used here}}
 }
 
+struct LifetimeBoundCtor {
+  LifetimeBoundCtor(const MyIntOwner& obj1 [[clang::lifetimebound]]);
+  LifetimeBoundCtor(std::string_view sv [[clang::lifetimebound]]);
+};
+
+auto lifetimebound_make_unique_single_param() {
+  return std::make_unique<LifetimeBoundCtor>(MyIntOwner{}); // expected-warning {{returning address of local temporary object}} \
+                                                            // cfg-warning {{address of stack memory is returned later}} cfg-note {{returned here}}
+}
+
+
 
 struct Unannotated {
   typedef std::vector<int>::iterator iterator;
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 2aca44daeb0aa..29f4880993b42 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -936,6 +936,70 @@ void lifetimebound_ctor() {
   (void)v;   // expected-note {{later used here}}
 }
 
+void lifetimebound_make_unique() {
+  std::unique_ptr<LifetimeBoundCtor> ptr;
+  {
+    MyObj obj;
+    ptr = std::make_unique<LifetimeBoundCtor>(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
+}
+
+void lifetimebound_make_unique_temp() {
+  std::unique_ptr<LifetimeBoundCtor> ptr = std::make_unique<LifetimeBoundCtor>(MyObj()); // expected-warning {{object whose reference is captured does not live long enough}} \
+                                                                                         // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
+}
+
+struct MultiLifetimeBoundCtor {
+  MultiLifetimeBoundCtor(const MyObj& obj1 [[clang::lifetimebound]], const MyObj& obj2);
+  MultiLifetimeBoundCtor(const MyObj& obj1, const MyObj& obj2 [[clang::lifetimebound]], int);
+  MultiLifetimeBoundCtor(const MyObj& obj1 [[clang::lifetimebound]], const MyObj& obj2 [[clang::lifetimebound]], double);
+  MultiLifetimeBoundCtor(std::string_view sv [[clang::lifetimebound]]);
+};
+
+void lifetimebound_make_unique_multi_params() {
+  std::unique_ptr<MultiLifetimeBoundCtor> ptr;
+  MyObj obj_long;
+  {
+    MyObj obj_short;
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
+}
+
+void lifetimebound_make_unique_multi_params2() {
+  std::unique_ptr<MultiLifetimeBoundCtor> ptr;
+  MyObj obj_long;
+  {
+    MyObj obj_short;
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
+}
+
+void lifetimebound_make_unique_multi_params3_1() {
+  std::unique_ptr<MultiLifetimeBoundCtor> ptr;
+  MyObj obj_long;
+  {
+    MyObj obj_short;
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long, 1.0); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
+}
+
+void lifetimebound_make_unique_multi_params3_2() {
+  std::unique_ptr<MultiLifetimeBoundCtor> ptr;
+  MyObj obj_long;
+  {
+    MyObj obj_short;
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1.0); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
+}
+
+
+
 View lifetimebound_return_of_local() {
   MyObj stack;
   return Identity(stack); // expected-warning {{address of stack memory is returned later}}
@@ -2425,9 +2489,9 @@ void owner_outlives_lifetimebound_source() {
   std::unique_ptr<S> ups;
   {
     std::string local;
-    ups = getUniqueS(local);
-  }
-  (void)ups; // Should warn.
+    ups = getUniqueS(local); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ups; // expected-note {{later used here}}
 }
 
 } // namespace track_origins_for_lifetimebound_record_type

>From 93a273357ae251dddcdbe2f17e0c24159c922dc3 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sat, 11 Apr 2026 13:26:11 +0000
Subject: [PATCH 2/9] string_view and pointer parameters

---
 a.o                                      | Bin 3448 -> 2984 bytes
 clang/test/Sema/warn-lifetime-safety.cpp |  24 ++++++++++++++++++++++-
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/a.o b/a.o
index 62582c83f3bfdb00d8640bee02912561d1d29b77..6a53ddbada9cbc2494763f0d57e07746d0d02737 100644
GIT binary patch
literal 2984
zcmbVO&2Jl35P!DYBrQoWg#)M at T*Ltm-Po<;IDE;Gv&~Yat(7LCs#GoO_1bacFYLAB
z7DYsgRLQa;aX~_GK?o!c95`^{0z$3S9DD2^08%74gcCVLGV^=;rgk<qhmQ1iW`1uz
z-_Ab!Gky6=Fc1(-0r7$8Ym5r9Kh~F1QcQ`L$#Tu;{Auj`9eJ!D0X25 at Bcr1q7`t2h
z#%}Xq-vhVyJz?ij<jzCs%@5_DGCEs(M(2=xpO8qrdGH(R at ccTDy}hSKmtuFL68%dU
z-6kPhd!A93Tz4L&?mQ&usnPkpLy at lScl1Ag?FZ_rN<^<0m=p;i5~o+iyYF!|!s|yB
zK*<h`?zID>GvTY6?Cp|ODMVUMy-}=IB9|^}Q7u|1x{<fmUANYFXJ&?!^=4MHtL2$e
zspUN`*6P)}j_qo&%qM5(^SPumH&2wEwG*>ZJDarT;<LHAcs`m<M(ucRKH<EL>RwSL
z2JWwjKsy{bKRG_}Ira5A;aH%{cq~kZ9|Y4=V-+$W6?@#?YlHi%{e8fJ`<Z1KdHidr
z=Q;kHq+o$IX`O<=If6GR$in1UnEVMoO$%c(P7C at O#a|#kN$dY;kv30>e|p1gLLxW}
z79pAO<W@*la2zk9pdmQWkgOC^pJ0A&Ag?@I!p_jf8v}W9CZMyl=on7W)e-pm2z*J#
z$H{pl-x-M+TEpT$7==@u3qhQ`N0<fBF5$!CMC7jbeqnpm^GFUK>3Hv`it!61gMg!V
z#^(SM0Y`SmuK*+hj#*&*E<hsSQ?wXw03-rFO^b10AZp`S9}$2=n3Tb3NdY7RHgJWB
z4T*aAi%JIDRx<2EN(SpF88~WUnm8Mtnil5mxa&0B775pHy0K`pQv9Upm^HWlVPe&)
z7aUh#jOi_*)t!>1xlY>^xERV#g at i)A+N=pnvuic-uWXj-nJZ;lWuaB7uA?noU%S|F
zEqepeRk(Ca&lH7*i^a5ZIl6=j!d$Ct7ArY(#c4Flj>_e%oB6s0wc*xXD@*)dS at LJ4
zXy$FIIaand9MdbDi at v&&DeAhfp;c2~tle0RnpC-Mn at -zyYOYBkOV0C0!jX{osKZNN
z(la+H<5D_(EMZJ_nX_C=Tl|QQH?Nsyq1`rXbTL;eR;lRTGh0!9ThGtGBiF%s;7B?y
zJ)&<hkOSmXg+_b824 at iFDLjbye(|*9vkeU2A?7YcYmg??Fmj9MXcIHa-}|e|_<iCp
zDS40?Fec(V0sipi8E5_rVX}T at PpYJC#rYmo>a%#P|4f36<?~Of1Z`!A{5Vp7{r?h%
z{6U}pAz>h{-_!c_vp%Z-TPhysLw?*(+&A+DN<OUrFhC-{r#<atJ)`n}tn%~kBl6=u
z;=IhaRsN8#|ES8(^>co|{wdkXSU&%sR05t8<j1|u{*1>7Ln at v#5+x;7{xSoKD_Hsf
zx<!jIWg^-4!@wc7UwK%~SHAc``F|s$^&eb6BVUk!R#OZpzNh+cE1l1u{kebahn9O)
kV%y48vXuJ%6{G(Q9{Jn)kzjxAu;hlmR1RSH|K!jAACLKYRR910

literal 3448
zcmb_eO>7%Q6dv16V$viHAqP+qq^J at _qMLP$9D-1B<ZQdpG(`C!qN3Vt)@#SqKa0I~
z+#;)$A|WhODToV*100aZp$Ei;8w9lojyV*mNE|Bhqe$i89*}YXnfG?*+2gg%1)enf
zX1;mzzVFS>j(ts;KkN7T7 at 3cqVXYCNjFp3}e$?qk**HY5s at 31r?LTDoMx}9pxLQ^A
z?=!Wg>_e{(@LlyrvF=O(3_~}n8NPj6zI02iep`k33kOx@*H)%l<-iGUG<?C}$+PU)
z=Na)u)oNu=tsVl$56}f)tbc>LxrXoExqZ-4S8FKhK&=(sbf~TfGnG9zxCSY<Z%08z
ztpXxkCHJbz&tJC^)++a$U<O)-V2jw9zBgFxMXrYn0J1~1cA>6TgO_eOdn7!Jw4O~)
zl+1h~mCH^{g(X>%lPP=R)TV7)g%>6#VX#@;kc?brGMz5DkJDB at _oivs5=Mq*!trT6
zq8sK+Vp<MGOgU`K$VMa_nTjOh8`Ft+VrFK;ke at -;aZc^Ky2O0tQQz^Qf#43*^Elwx
z&>q9b?8xXl{@8H;3IxEA=6qKd=be=UA7j*UT7MD-*wEt8j$ns!_-7%>>`?!x8#9k{
zVAr6v$E`KvaEtq)4M0PF91n6o1b!IWzu5;cd4xS^bc+cve>a!~Ts3WT!%nE9vJJ;U
zz){!%1CEN@?G6^u1xGaCREE8lJptV at K_~7h+SAZr8#^os`JWQ>;NGIW4y{|f+5`Wt
z2mVnH9LwsK|4T;?zA at LFGX>pGpmmFX0P!dM_-0+B9XtfQTRgpgd^5=MSHrbad_Cev
z1w1U^$U^b(sXB)7n3M1|7j{mBh_4Iyh!B55z>f*|O9GB{Q2qr0e^|it0zM|-Zwt7{
z%VFpHpl=(o2|j_xm-Z5&^8)?|@1tAieT)~sj+B50cpu%n0^TR!?+ZA7zhr~sM~{t-
zFzw<(WJS*>O*<A+mhDieVCPfWq*h9qTQK>YZ5HejMx9 at _Bkr70^28}lKy!XBL_kO`
zW>aq!P0h0NFLN5jnU?ZqT9<6IY%{z_GiDY#$$YM8F<mk&3mCIo8F<Fii=_;cvN_w7
z&MmB-F4($pnMru at YI-~lmvoY8tJ$qoHm)t1g<{5JZf<i^OXPJNCJjR~%Z6#$nyqi7
z0f9Q82mVUN at l7$3b`fjK=$B2+l}@r>S at IT*yCv4QqA7FMt1GhB$@o8|#qEMr3m#(H
z*w~b^xD52?VzIV*LwrZ#x~)rdYp^{DNz;<$vSz`LTrR7pQ}z|DBzG*7>8$)0|0gUK
z&ne}4jUUD*#lsDgaJ{ryg=6B`#egV2z~~-!N1b<587A;9iBbUfpCSmh<2r`l9!jBa
z^HGlf0v<JaWU|lreCvjE0iKr6GWH(y at ILX{b)Jy!Qsl?`k=BzR1ajo>_XzlcC#2sM
zKJ2)g`(Uw?%d=kQmjvz)8uH^Fd+T52{C at 9x6a_~5-BC+FjeFI<!}Adz^5Z^H-Q;g_
zesBFz&QHH%#OKx1i~mE;Pwx-;u at Nu%`<y at EsehgGlYZj&j(>+ZCzogaF5iDTXUL6O
zXg%TEfFYF(O|(tUpAkXmbGYe07cepOM+y|dF0ntknB|er?(+4!p7o at k;5*R4`lAGJ
zzsKv};bW{H-yN-|`o97!FyF|5yAG{si~K^MdjZ#@dCU46Ft2uu+vOnspFu<S5Hv6U
EKZPRj`v3p{

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 29f4880993b42..678354341a1fb 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -925,6 +925,7 @@ void lifetimebound_return_reference() {
 struct LifetimeBoundCtor {
   LifetimeBoundCtor();
   LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
+  LifetimeBoundCtor(int* p [[clang::lifetimebound]]);
 };
 
 void lifetimebound_ctor() {
@@ -951,11 +952,32 @@ void lifetimebound_make_unique_temp() {
   (void)ptr; // expected-note {{later used here}}
 }
 
+void lifetimebound_make_unique_raw_ptr() {
+  std::unique_ptr<LifetimeBoundCtor> ptr;
+  int x = 0;
+  {
+    int* p = &x;
+    // FIXME: No warning expected with current implementation
+    ptr = std::make_unique<LifetimeBoundCtor>(p);
+  }
+  (void)ptr;
+}
+
+void lifetimebound_make_unique_string_view_local() {
+  std::unique_ptr<LifetimeBoundCtor> ptr;
+  {
+    std::string s;
+    std::string_view sv(s);
+    // FIXME: No warning expected with current implementation because of reference mismatch
+    ptr = std::make_unique<LifetimeBoundCtor>(sv);
+  }
+  (void)ptr;
+}
+
 struct MultiLifetimeBoundCtor {
   MultiLifetimeBoundCtor(const MyObj& obj1 [[clang::lifetimebound]], const MyObj& obj2);
   MultiLifetimeBoundCtor(const MyObj& obj1, const MyObj& obj2 [[clang::lifetimebound]], int);
   MultiLifetimeBoundCtor(const MyObj& obj1 [[clang::lifetimebound]], const MyObj& obj2 [[clang::lifetimebound]], double);
-  MultiLifetimeBoundCtor(std::string_view sv [[clang::lifetimebound]]);
 };
 
 void lifetimebound_make_unique_multi_params() {

>From 0913da75f7b756295eb3ba2ae2b9b82cc4d8518b Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sat, 11 Apr 2026 13:27:10 +0000
Subject: [PATCH 3/9] remove a.o

---
 a.o | Bin 2984 -> 0 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 a.o

diff --git a/a.o b/a.o
deleted file mode 100644
index 6a53ddbada9cbc2494763f0d57e07746d0d02737..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 2984
zcmbVO&2Jl35P!DYBrQoWg#)M at T*Ltm-Po<;IDE;Gv&~Yat(7LCs#GoO_1bacFYLAB
z7DYsgRLQa;aX~_GK?o!c95`^{0z$3S9DD2^08%74gcCVLGV^=;rgk<qhmQ1iW`1uz
z-_Ab!Gky6=Fc1(-0r7$8Ym5r9Kh~F1QcQ`L$#Tu;{Auj`9eJ!D0X25 at Bcr1q7`t2h
z#%}Xq-vhVyJz?ij<jzCs%@5_DGCEs(M(2=xpO8qrdGH(R at ccTDy}hSKmtuFL68%dU
z-6kPhd!A93Tz4L&?mQ&usnPkpLy at lScl1Ag?FZ_rN<^<0m=p;i5~o+iyYF!|!s|yB
zK*<h`?zID>GvTY6?Cp|ODMVUMy-}=IB9|^}Q7u|1x{<fmUANYFXJ&?!^=4MHtL2$e
zspUN`*6P)}j_qo&%qM5(^SPumH&2wEwG*>ZJDarT;<LHAcs`m<M(ucRKH<EL>RwSL
z2JWwjKsy{bKRG_}Ira5A;aH%{cq~kZ9|Y4=V-+$W6?@#?YlHi%{e8fJ`<Z1KdHidr
z=Q;kHq+o$IX`O<=If6GR$in1UnEVMoO$%c(P7C at O#a|#kN$dY;kv30>e|p1gLLxW}
z79pAO<W@*la2zk9pdmQWkgOC^pJ0A&Ag?@I!p_jf8v}W9CZMyl=on7W)e-pm2z*J#
z$H{pl-x-M+TEpT$7==@u3qhQ`N0<fBF5$!CMC7jbeqnpm^GFUK>3Hv`it!61gMg!V
z#^(SM0Y`SmuK*+hj#*&*E<hsSQ?wXw03-rFO^b10AZp`S9}$2=n3Tb3NdY7RHgJWB
z4T*aAi%JIDRx<2EN(SpF88~WUnm8Mtnil5mxa&0B775pHy0K`pQv9Upm^HWlVPe&)
z7aUh#jOi_*)t!>1xlY>^xERV#g at i)A+N=pnvuic-uWXj-nJZ;lWuaB7uA?noU%S|F
zEqepeRk(Ca&lH7*i^a5ZIl6=j!d$Ct7ArY(#c4Flj>_e%oB6s0wc*xXD@*)dS at LJ4
zXy$FIIaand9MdbDi at v&&DeAhfp;c2~tle0RnpC-Mn at -zyYOYBkOV0C0!jX{osKZNN
z(la+H<5D_(EMZJ_nX_C=Tl|QQH?Nsyq1`rXbTL;eR;lRTGh0!9ThGtGBiF%s;7B?y
zJ)&<hkOSmXg+_b824 at iFDLjbye(|*9vkeU2A?7YcYmg??Fmj9MXcIHa-}|e|_<iCp
zDS40?Fec(V0sipi8E5_rVX}T at PpYJC#rYmo>a%#P|4f36<?~Of1Z`!A{5Vp7{r?h%
z{6U}pAz>h{-_!c_vp%Z-TPhysLw?*(+&A+DN<OUrFhC-{r#<atJ)`n}tn%~kBl6=u
z;=IhaRsN8#|ES8(^>co|{wdkXSU&%sR05t8<j1|u{*1>7Ln at v#5+x;7{xSoKD_Hsf
zx<!jIWg^-4!@wc7UwK%~SHAc``F|s$^&eb6BVUk!R#OZpzNh+cE1l1u{kebahn9O)
kV%y48vXuJ%6{G(Q9{Jn)kzjxAu;hlmR1RSH|K!jAACLKYRR910


>From 5606ec85aa480ae247b18bc293fd55a43ccff1fb Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sat, 11 Apr 2026 13:36:43 +0000
Subject: [PATCH 4/9] dangling field

---
 .../warn-lifetime-safety-dangling-field.cpp    | 18 ++++++++++++++++++
 clang/test/Sema/warn-lifetime-safety.cpp       |  2 ++
 2 files changed, 20 insertions(+)

diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
index 79b0183ed91ec..604adfa0c8538 100644
--- a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
@@ -185,6 +185,7 @@ struct IndirectEscape2 {
   }
 };
 
+<<<<<<< HEAD
 namespace DanglingPointerFieldInBaseClass {
 struct BaseWithPointer {
   std::string_view view; // expected-note {{this field dangles}}
@@ -196,3 +197,20 @@ struct DerivedWithCtor : BaseWithPointer {
   }
 };
 } // namespace DanglingPointerFieldInBaseClass
+=======
+namespace MakeUnique {
+struct MyObj {};
+
+struct LifetimeBoundCtor {
+  LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
+};
+
+struct HasUniquePtrField {
+  std::unique_ptr<LifetimeBoundCtor> field; // expected-note {{this field dangles}}
+
+  void setWithParam(MyObj obj) {
+    field = std::make_unique<LifetimeBoundCtor>(obj); // expected-warning {{address of stack memory escapes to a field}}
+  }
+};
+} // namespace MakeUnique
+>>>>>>> 6f5985bd6945 (dangling field)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 678354341a1fb..31e62cc9f72a5 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -974,6 +974,8 @@ void lifetimebound_make_unique_string_view_local() {
   (void)ptr;
 }
 
+
+
 struct MultiLifetimeBoundCtor {
   MultiLifetimeBoundCtor(const MyObj& obj1 [[clang::lifetimebound]], const MyObj& obj2);
   MultiLifetimeBoundCtor(const MyObj& obj1, const MyObj& obj2 [[clang::lifetimebound]], int);

>From 0f33a12d7e2249f7b57b0d946f76c7d2611107cf Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sat, 11 Apr 2026 13:47:55 +0000
Subject: [PATCH 5/9] lifetime suggestion

---
 .../Sema/warn-lifetime-safety-suggestions.cpp | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index b3b13038dc344..8db96103b683d 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -67,6 +67,7 @@ struct ReturnThisPointer {
 //--- test_source.cpp
 
 #include "test_header.h"
+#include "Inputs/lifetime-analysis.h"
 
 View definition_before_header(View a) {
   return a;                               // expected-note {{param returned here}}
@@ -529,3 +530,23 @@ CaptureRefToBaseView test_ref_to_base_view() {
   return x; // expected-note {{returned here}}
 }
 } // namespace capturing_constructor
+
+namespace make_unique_suggestion {
+
+struct LifetimeBoundCtor {
+  LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
+};
+
+std::unique_ptr<LifetimeBoundCtor> create_target(const MyObj& obj) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+  return std::make_unique<LifetimeBoundCtor>(obj); // expected-note {{param returned here}}
+}
+
+void test_inference() {
+  std::unique_ptr<LifetimeBoundCtor> ptr;
+  {
+    MyObj obj;
+    ptr = create_target(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
+}
+} // namespace make_unique_suggestion

>From 954c2bf91d182b2f3aa4f2ac28e7d9ee5221d6ad Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sat, 11 Apr 2026 14:47:51 +0000
Subject: [PATCH 6/9] fix suggestions test

---
 clang/test/Sema/warn-lifetime-safety-suggestions.cpp | 9 +++++----
 clang/test/Sema/warn-lifetime-safety.cpp             | 4 ----
 2 files changed, 5 insertions(+), 8 deletions(-)

diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index 8db96103b683d..6688d80c8331b 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -1,8 +1,8 @@
 // RUN: rm -rf %t
 // RUN: split-file %s %t
-// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -verify %t/test_source.cpp
-// RUN: %clang_cc1 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -fixit %t/test_source.cpp
-// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wno-dangling -I%t -Werror=lifetime-safety-suggestions %t/test_source.cpp
+// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%S -I%t -verify %t/test_source.cpp
+// RUN: %clang_cc1 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%S -I%t -fixit %t/test_source.cpp
+// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wno-dangling -I%S -I%t -Werror=lifetime-safety-suggestions %t/test_source.cpp
 
 View definition_before_header(View a);
 
@@ -10,6 +10,8 @@ View definition_before_header(View a);
 #ifndef TEST_HEADER_H
 #define TEST_HEADER_H
 
+#include "Inputs/lifetime-analysis.h"
+
 struct View;
 
 struct [[gsl::Owner]] MyObj {
@@ -67,7 +69,6 @@ struct ReturnThisPointer {
 //--- test_source.cpp
 
 #include "test_header.h"
-#include "Inputs/lifetime-analysis.h"
 
 View definition_before_header(View a) {
   return a;                               // expected-note {{param returned here}}
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 31e62cc9f72a5..3d18c48be8d29 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -974,8 +974,6 @@ void lifetimebound_make_unique_string_view_local() {
   (void)ptr;
 }
 
-
-
 struct MultiLifetimeBoundCtor {
   MultiLifetimeBoundCtor(const MyObj& obj1 [[clang::lifetimebound]], const MyObj& obj2);
   MultiLifetimeBoundCtor(const MyObj& obj1, const MyObj& obj2 [[clang::lifetimebound]], int);
@@ -2507,8 +2505,6 @@ std::string_view return_dangling_view_through_owner() {
   return sv; // expected-note {{returned here}}
 }
 
-// FIXME: False negative. Move assignment of unique_ptr is not defaulted,
-// so origins from `local` don't propagate to `ups`.
 void owner_outlives_lifetimebound_source() {
   std::unique_ptr<S> ups;
   {

>From 8a697df3abc31e9e5e1979b9f9b056567665a8d6 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Wed, 15 Apr 2026 16:11:01 +0000
Subject: [PATCH 7/9] rebase

---
 clang/test/Sema/warn-lifetime-safety-dangling-field.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
index 604adfa0c8538..4ed4f535c2ae1 100644
--- a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
@@ -185,7 +185,7 @@ struct IndirectEscape2 {
   }
 };
 
-<<<<<<< HEAD
+
 namespace DanglingPointerFieldInBaseClass {
 struct BaseWithPointer {
   std::string_view view; // expected-note {{this field dangles}}
@@ -197,7 +197,7 @@ struct DerivedWithCtor : BaseWithPointer {
   }
 };
 } // namespace DanglingPointerFieldInBaseClass
-=======
+
 namespace MakeUnique {
 struct MyObj {};
 
@@ -213,4 +213,4 @@ struct HasUniquePtrField {
   }
 };
 } // namespace MakeUnique
->>>>>>> 6f5985bd6945 (dangling field)
+

>From bf480da67d50d06997774df7f4e743634e0e6833 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Wed, 15 Apr 2026 16:34:18 +0000
Subject: [PATCH 8/9] extract ctor from body in end-of-tu

---
 clang/lib/Sema/SemaAttr.cpp                   | 74 +++++++------------
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  5 ++
 clang/test/Sema/Inputs/lifetime-analysis.h    | 12 ++-
 clang/test/Sema/warn-lifetime-safety.cpp      | 57 +++++++-------
 4 files changed, 68 insertions(+), 80 deletions(-)

diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index d173b1ba58d8e..ef16cfcfbcdf6 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -221,7 +221,19 @@ void Sema::inferGslOwnerPointerAttribute(CXXRecordDecl *Record) {
   inferGslPointerAttribute(Record, Record);
 }
 
+static const CXXNewExpr *findCXXNewExpr(const Stmt *S) {
+  if (!S) return nullptr;
+  if (const auto *E = dyn_cast<CXXNewExpr>(S))
+    return E;
+  for (const Stmt *Child : S->children())
+    if (Child)
+      if (const CXXNewExpr *E = findCXXNewExpr(Child))
+        return E;
+  return nullptr;
+}
+
 void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
+  llvm::errs() << "JETSKI: inferLifetimeBoundAttribute for " << FD->getNameAsString() << "\n";
   if (FD->getNumParams() == 0)
     return;
   // Skip void returning functions (except constructors). This can occur in
@@ -252,56 +264,20 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
   }
 
   // Handle std::make_unique to propagate lifetimebound attributes from the
-  // constructed type's constructor to make_unique's parameters.
-  if (FD->isInStdNamespace() && FD->getDeclName().isIdentifier() &&
+  // constructed type's constructor to make_unique's parameters by looking
+  // into its body.
+  if (FD->getDeclName().isIdentifier() &&
       FD->getName() == "make_unique") {
-    if (!FD->isFunctionTemplateSpecialization())
-      return;
-
-    const TemplateArgumentList *TAL = FD->getTemplateSpecializationArgs();
-    if (!TAL || TAL->size() < 1)
-      return;
-
-    // make_unique's first template argument is the type being constructed.
-    TemplateArgument TA = TAL->get(0);
-    if (TA.getKind() != TemplateArgument::Type)
-      return;
-
-    QualType T = TA.getAsType();
-    const auto *RD = T->getAsCXXRecordDecl();
-    if (!RD)
-      return;
-
-    // Find the constructor that matches make_unique's arguments.
-    for (const auto *Ctor : RD->ctors()) {
-      if (Ctor->getNumParams() != FD->getNumParams())
-        continue;
-
-      bool Compatible = true;
-      for (unsigned i = 0; i < Ctor->getNumParams(); ++i) {
-        QualType CtorParamType = Ctor->getParamDecl(i)->getType();
-        QualType FDParamType = FD->getParamDecl(i)->getType();
-        // Compare types ignoring references.
-        if (!Context.hasSameUnqualifiedType(
-                CtorParamType.getNonReferenceType(),
-                FDParamType.getNonReferenceType())) {
-          Compatible = false;
-          break;
-        }
-      }
-
-      if (!Compatible)
-        continue;
-      // Propagate lifetimebound attributes only if the constructor parameter is
-      // a reference. This avoids incorrect loan tracking when a by-value view
-      // (like string_view) is passed by reference to make_unique.
-      for (unsigned i = 0; i < Ctor->getNumParams(); ++i)
-        if (Ctor->getParamDecl(i)->hasAttr<LifetimeBoundAttr>() &&
-            Ctor->getParamDecl(i)->getType()->isReferenceType())
-          FD->getParamDecl(i)->addAttr(
-              LifetimeBoundAttr::CreateImplicit(Context, FD->getLocation()));
-      break; // Found matching constructor, done.
-    }
+    const FunctionDecl *BodyDecl = nullptr;
+    if (FD->getBody(BodyDecl))
+      if (const CXXNewExpr *NewExpr = findCXXNewExpr(BodyDecl->getBody()))
+        if (const CXXConstructExpr *ConstructExpr = NewExpr->getConstructExpr())
+          if (const CXXConstructorDecl *Ctor = ConstructExpr->getConstructor())
+            for (unsigned i = 0; i < Ctor->getNumParams(); ++i)
+              if (Ctor->getParamDecl(i)->hasAttr<LifetimeBoundAttr>() &&
+                  i < FD->getNumParams())
+                FD->getParamDecl(i)->addAttr(
+                    LifetimeBoundAttr::CreateImplicit(Context, FD->getLocation()));
     return;
   }
 
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 954160d4112a5..6158f05bdba61 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -5552,6 +5552,8 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
   if (Function->isInvalidDecl() || isa<CXXDeductionGuideDecl>(Function))
     return;
 
+
+
   // Never instantiate an explicit specialization except if it is a class scope
   // explicit specialization.
   TemplateSpecializationKind TSK =
@@ -5969,6 +5971,9 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
     // context seems wrong. Investigate more.
     ActOnFinishFunctionBody(Function, Body.get(), /*IsInstantiation=*/true);
 
+
+    inferLifetimeBoundAttribute(Function);
+
     checkReferenceToTULocalFromOtherTU(Function, PointOfInstantiation);
 
     if (PatternDecl->isDependentContext())
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 67e6c0f18de5c..788a4ba104af3 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -194,8 +194,9 @@ struct basic_string {
 using string = basic_string<char>;
 
 template<typename T>
-struct unique_ptr {
+struct [[gsl::Owner]] unique_ptr {
   unique_ptr();
+  explicit unique_ptr(T*);
   unique_ptr(unique_ptr<T>&&);
   unique_ptr& operator=(unique_ptr<T>&&);
   ~unique_ptr();
@@ -205,8 +206,15 @@ struct unique_ptr {
   T *get() const;
 };
 
+#ifdef WITH_LIFETIME_SAFETY_BODY
 template<typename T, typename... Args>
-unique_ptr<T> make_unique(Args&&... args);
+unique_ptr<T> make_unique(Args&&... args) {
+  return unique_ptr<T>(new T(args...));
+}
+#else
+template<typename T, typename... Args>
+unique_ptr<T> make_unique(Args&&... args [[clang::lifetimebound]]);
+#endif
 
 template<typename T>
 struct optional {
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 3d18c48be8d29..b1ad35cbc8da2 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1,6 +1,6 @@
-// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=expected,function %s
-// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling -verify=expected,tu %s
-// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -fcxx-exceptions -verify=expected,function %s
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -DWITH_LIFETIME_SAFETY_BODY -verify=expected,function %s
+// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling -DWITH_LIFETIME_SAFETY_BODY -verify=expected,tu %s
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -fcxx-exceptions -DWITH_LIFETIME_SAFETY_BODY -verify=expected,function %s
 
 #include "Inputs/lifetime-analysis.h"
 
@@ -926,6 +926,7 @@ struct LifetimeBoundCtor {
   LifetimeBoundCtor();
   LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
   LifetimeBoundCtor(int* p [[clang::lifetimebound]]);
+  LifetimeBoundCtor(std::string_view sv [[clang::lifetimebound]]);
 };
 
 void lifetimebound_ctor() {
@@ -941,15 +942,15 @@ void lifetimebound_make_unique() {
   std::unique_ptr<LifetimeBoundCtor> ptr;
   {
     MyObj obj;
-    ptr = std::make_unique<LifetimeBoundCtor>(obj); // expected-warning {{object whose reference is captured does not live long enough}}
-  } // expected-note {{destroyed here}}
-  (void)ptr; // expected-note {{later used here}}
+    ptr = std::make_unique<LifetimeBoundCtor>(obj); // tu-warning {{object whose reference is captured does not live long enough}}
+  } // tu-note {{destroyed here}}
+  (void)ptr; // tu-note {{later used here}}
 }
 
 void lifetimebound_make_unique_temp() {
-  std::unique_ptr<LifetimeBoundCtor> ptr = std::make_unique<LifetimeBoundCtor>(MyObj()); // expected-warning {{object whose reference is captured does not live long enough}} \
-                                                                                         // expected-note {{destroyed here}}
-  (void)ptr; // expected-note {{later used here}}
+  std::unique_ptr<LifetimeBoundCtor> ptr = std::make_unique<LifetimeBoundCtor>(MyObj()); // tu-warning {{object whose reference is captured does not live long enough}} \
+                                                                                         // tu-note {{destroyed here}}
+  (void)ptr; // tu-note {{later used here}}
 }
 
 void lifetimebound_make_unique_raw_ptr() {
@@ -957,10 +958,9 @@ void lifetimebound_make_unique_raw_ptr() {
   int x = 0;
   {
     int* p = &x;
-    // FIXME: No warning expected with current implementation
-    ptr = std::make_unique<LifetimeBoundCtor>(p);
-  }
-  (void)ptr;
+    ptr = std::make_unique<LifetimeBoundCtor>(p); // tu-warning {{object whose reference is captured does not live long enough}}
+  } // tu-note {{destroyed here}}
+  (void)ptr; // tu-note {{later used here}}
 }
 
 void lifetimebound_make_unique_string_view_local() {
@@ -968,10 +968,9 @@ void lifetimebound_make_unique_string_view_local() {
   {
     std::string s;
     std::string_view sv(s);
-    // FIXME: No warning expected with current implementation because of reference mismatch
-    ptr = std::make_unique<LifetimeBoundCtor>(sv);
-  }
-  (void)ptr;
+    ptr = std::make_unique<LifetimeBoundCtor>(sv); // tu-warning {{object whose reference is captured does not live long enough}}
+  } // tu-note {{destroyed here}}
+  (void)ptr; // tu-note {{later used here}}
 }
 
 struct MultiLifetimeBoundCtor {
@@ -985,9 +984,9 @@ void lifetimebound_make_unique_multi_params() {
   MyObj obj_long;
   {
     MyObj obj_short;
-    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long); // expected-warning {{object whose reference is captured does not live long enough}}
-  } // expected-note {{destroyed here}}
-  (void)ptr; // expected-note {{later used here}}
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long); // tu-warning {{object whose reference is captured does not live long enough}}
+  } // tu-note {{destroyed here}}
+  (void)ptr; // tu-note {{later used here}}
 }
 
 void lifetimebound_make_unique_multi_params2() {
@@ -995,9 +994,9 @@ void lifetimebound_make_unique_multi_params2() {
   MyObj obj_long;
   {
     MyObj obj_short;
-    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1); // expected-warning {{object whose reference is captured does not live long enough}}
-  } // expected-note {{destroyed here}}
-  (void)ptr; // expected-note {{later used here}}
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1); // tu-warning {{object whose reference is captured does not live long enough}}
+  } // tu-note {{destroyed here}}
+  (void)ptr; // tu-note {{later used here}}
 }
 
 void lifetimebound_make_unique_multi_params3_1() {
@@ -1005,9 +1004,9 @@ void lifetimebound_make_unique_multi_params3_1() {
   MyObj obj_long;
   {
     MyObj obj_short;
-    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long, 1.0); // expected-warning {{object whose reference is captured does not live long enough}}
-  } // expected-note {{destroyed here}}
-  (void)ptr; // expected-note {{later used here}}
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long, 1.0); // tu-warning {{object whose reference is captured does not live long enough}}
+  } // tu-note {{destroyed here}}
+  (void)ptr; // tu-note {{later used here}}
 }
 
 void lifetimebound_make_unique_multi_params3_2() {
@@ -1015,9 +1014,9 @@ void lifetimebound_make_unique_multi_params3_2() {
   MyObj obj_long;
   {
     MyObj obj_short;
-    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1.0); // expected-warning {{object whose reference is captured does not live long enough}}
-  } // expected-note {{destroyed here}}
-  (void)ptr; // expected-note {{later used here}}
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1.0); // tu-warning {{object whose reference is captured does not live long enough}}
+  } // tu-note {{destroyed here}}
+  (void)ptr; // tu-note {{later used here}}
 }
 
 

>From 63f48f4baabe001a6508e6376e26a9a7bf99f38c Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Wed, 15 Apr 2026 17:46:36 +0000
Subject: [PATCH 9/9] extract ctor from CXXNew

---
 clang/lib/Sema/SemaAttr.cpp              | 61 ++++++++++++++++--------
 clang/test/Sema/warn-lifetime-safety.cpp | 48 +++++++++----------
 2 files changed, 65 insertions(+), 44 deletions(-)

diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index ef16cfcfbcdf6..b4d457c4d06b5 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -19,6 +19,7 @@
 #include "clang/Basic/TargetInfo.h"
 #include "clang/Lex/Preprocessor.h"
 #include "clang/Sema/Lookup.h"
+#include "clang/Sema/Initialization.h"
 #include <optional>
 using namespace clang;
 
@@ -221,19 +222,8 @@ void Sema::inferGslOwnerPointerAttribute(CXXRecordDecl *Record) {
   inferGslPointerAttribute(Record, Record);
 }
 
-static const CXXNewExpr *findCXXNewExpr(const Stmt *S) {
-  if (!S) return nullptr;
-  if (const auto *E = dyn_cast<CXXNewExpr>(S))
-    return E;
-  for (const Stmt *Child : S->children())
-    if (Child)
-      if (const CXXNewExpr *E = findCXXNewExpr(Child))
-        return E;
-  return nullptr;
-}
 
 void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
-  llvm::errs() << "JETSKI: inferLifetimeBoundAttribute for " << FD->getNameAsString() << "\n";
   if (FD->getNumParams() == 0)
     return;
   // Skip void returning functions (except constructors). This can occur in
@@ -264,20 +254,51 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
   }
 
   // Handle std::make_unique to propagate lifetimebound attributes from the
-  // constructed type's constructor to make_unique's parameters by looking
-  // into its body.
+  // constructed type's constructor to make_unique's parameters by performing
+  // constructor lookup.
   if (FD->getDeclName().isIdentifier() &&
       FD->getName() == "make_unique") {
-    const FunctionDecl *BodyDecl = nullptr;
-    if (FD->getBody(BodyDecl))
-      if (const CXXNewExpr *NewExpr = findCXXNewExpr(BodyDecl->getBody()))
-        if (const CXXConstructExpr *ConstructExpr = NewExpr->getConstructExpr())
-          if (const CXXConstructorDecl *Ctor = ConstructExpr->getConstructor())
-            for (unsigned i = 0; i < Ctor->getNumParams(); ++i)
+    QualType ReturnType = FD->getReturnType();
+    const auto *RT = ReturnType->getAs<RecordType>();
+    if (!RT) return;
+    const auto *Spec = dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl());
+    if (!Spec) return;
+    if (Spec->getTemplateArgs().size() == 0) return;
+    QualType T = Spec->getTemplateArgs()[0].getAsType();
+
+    SmallVector<Expr *, 4> Args;
+    for (auto *Parm : FD->parameters()) {
+      QualType ParmTy = Parm->getType();
+      ExprValueKind VK = VK_LValue;
+      QualType ArgTy = ParmTy;
+      if (const auto *RefTy = ParmTy->getAs<ReferenceType>()) {
+        ArgTy = RefTy->getPointeeType();
+        if (ParmTy->isRValueReferenceType())
+          VK = VK_XValue;
+      }
+      Args.push_back(new (Context) OpaqueValueExpr(FD->getLocation(), ArgTy, VK));
+    }
+
+    InitializedEntity Entity = InitializedEntity::InitializeTemporary(T);
+    InitializationKind Kind = InitializationKind::CreateDirect(FD->getLocation(), FD->getLocation(), FD->getLocation());
+    InitializationSequence InitSeq(*this, Entity, Kind, Args);
+
+    if (InitSeq) {
+      for (const auto &Step : InitSeq.steps()) {
+        if (Step.Kind == InitializationSequence::SK_ConstructorInitialization) {
+          if (const auto *Ctor = dyn_cast_or_null<CXXConstructorDecl>(Step.Function.Function)) {
+            for (unsigned i = 0; i < Ctor->getNumParams(); ++i) {
               if (Ctor->getParamDecl(i)->hasAttr<LifetimeBoundAttr>() &&
-                  i < FD->getNumParams())
+                  i < FD->getNumParams()) {
                 FD->getParamDecl(i)->addAttr(
                     LifetimeBoundAttr::CreateImplicit(Context, FD->getLocation()));
+              }
+            }
+          }
+          break;
+        }
+      }
+    }
     return;
   }
 
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index b1ad35cbc8da2..ba2747e07f4dd 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -942,15 +942,15 @@ void lifetimebound_make_unique() {
   std::unique_ptr<LifetimeBoundCtor> ptr;
   {
     MyObj obj;
-    ptr = std::make_unique<LifetimeBoundCtor>(obj); // tu-warning {{object whose reference is captured does not live long enough}}
-  } // tu-note {{destroyed here}}
-  (void)ptr; // tu-note {{later used here}}
+    ptr = std::make_unique<LifetimeBoundCtor>(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
 }
 
 void lifetimebound_make_unique_temp() {
-  std::unique_ptr<LifetimeBoundCtor> ptr = std::make_unique<LifetimeBoundCtor>(MyObj()); // tu-warning {{object whose reference is captured does not live long enough}} \
-                                                                                         // tu-note {{destroyed here}}
-  (void)ptr; // tu-note {{later used here}}
+  std::unique_ptr<LifetimeBoundCtor> ptr = std::make_unique<LifetimeBoundCtor>(MyObj()); // expected-warning {{object whose reference is captured does not live long enough}} \
+                                                                                         // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
 }
 
 void lifetimebound_make_unique_raw_ptr() {
@@ -958,9 +958,9 @@ void lifetimebound_make_unique_raw_ptr() {
   int x = 0;
   {
     int* p = &x;
-    ptr = std::make_unique<LifetimeBoundCtor>(p); // tu-warning {{object whose reference is captured does not live long enough}}
-  } // tu-note {{destroyed here}}
-  (void)ptr; // tu-note {{later used here}}
+    ptr = std::make_unique<LifetimeBoundCtor>(p); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
 }
 
 void lifetimebound_make_unique_string_view_local() {
@@ -968,9 +968,9 @@ void lifetimebound_make_unique_string_view_local() {
   {
     std::string s;
     std::string_view sv(s);
-    ptr = std::make_unique<LifetimeBoundCtor>(sv); // tu-warning {{object whose reference is captured does not live long enough}}
-  } // tu-note {{destroyed here}}
-  (void)ptr; // tu-note {{later used here}}
+    ptr = std::make_unique<LifetimeBoundCtor>(sv); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
 }
 
 struct MultiLifetimeBoundCtor {
@@ -984,9 +984,9 @@ void lifetimebound_make_unique_multi_params() {
   MyObj obj_long;
   {
     MyObj obj_short;
-    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long); // tu-warning {{object whose reference is captured does not live long enough}}
-  } // tu-note {{destroyed here}}
-  (void)ptr; // tu-note {{later used here}}
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
 }
 
 void lifetimebound_make_unique_multi_params2() {
@@ -994,9 +994,9 @@ void lifetimebound_make_unique_multi_params2() {
   MyObj obj_long;
   {
     MyObj obj_short;
-    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1); // tu-warning {{object whose reference is captured does not live long enough}}
-  } // tu-note {{destroyed here}}
-  (void)ptr; // tu-note {{later used here}}
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
 }
 
 void lifetimebound_make_unique_multi_params3_1() {
@@ -1004,9 +1004,9 @@ void lifetimebound_make_unique_multi_params3_1() {
   MyObj obj_long;
   {
     MyObj obj_short;
-    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long, 1.0); // tu-warning {{object whose reference is captured does not live long enough}}
-  } // tu-note {{destroyed here}}
-  (void)ptr; // tu-note {{later used here}}
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long, 1.0); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
 }
 
 void lifetimebound_make_unique_multi_params3_2() {
@@ -1014,9 +1014,9 @@ void lifetimebound_make_unique_multi_params3_2() {
   MyObj obj_long;
   {
     MyObj obj_short;
-    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1.0); // tu-warning {{object whose reference is captured does not live long enough}}
-  } // tu-note {{destroyed here}}
-  (void)ptr; // tu-note {{later used here}}
+    ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1.0); // expected-warning {{object whose reference is captured does not live long enough}}
+  } // expected-note {{destroyed here}}
+  (void)ptr; // expected-note {{later used here}}
 }
 
 



More information about the cfe-commits mailing list