https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/191699
>From f859fd8e79999b5c650cb8a745d74f0b80c4d96e Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sat, 11 Apr 2026 13:11:00 +0000 Subject: [PATCH 01/11] 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@_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@3cqVXYCNjFp3}e$?qk**HY5s@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@_oivs5=Mq*!trT6 zq8sK+Vp<MGOgU`K$VMa_nTjOh8`Ft+VrFK;ke@-;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@K_~7h+SAZr8#^os`JWQ>;NGIW4y{|f+5`Wt z2mVnH9LwsK|4T;?zA@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@_Bkr70^28}lKy!XBL_kO` zW>aq!P0h0NFLN5jnU?ZqT9<6IY%{z_GiDY#$$YM8F<mk&3mCIo8F<Fii=_;cvN_w7 z&MmB-F4($pnMru@YI-~lmvoY8tJ$qoHm)t1g<{5JZf<i^OXPJNCJjR~%Z6#$nyqi7 z0f9Q82mVUN@l7$3b`fjK=$B2+l}@r>S@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@ILX{b)Jy!Qsl?`k=BzR1ajo>_XzlcC#2sM zKJ2)g`(Uw?%d=kQmjvz)8uH^Fd+T52{C@9x6a_~5-BC+FjeFI<!}Adz^5Z^H-Q;g_ zesBFz&QHH%#OKx1i~mE;Pwx-;u@Nu%`<y@EsehgGlYZj&j(>+ZCzogaF5iDTXUL6O zXg%TEfFYF(O|(tUpAkXmbGYe07cepOM+y|dF0ntknB|er?(+4!p7o@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 82b890b57817e..69a64d08e0385 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -484,7 +484,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 abf9890b08522..c0f98dffe8354 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 0ed151b9db136..c352cca2023a6 100644 --- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp @@ -176,6 +176,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 77d8e3370676d..190b93193e352 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 537f87e51c6d5fed7406fbbb54f1ee19d8c45544 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sat, 11 Apr 2026 13:26:11 +0000 Subject: [PATCH 02/11] 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@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@Bcr1q7`t2h z#%}Xq-vhVyJz?ij<jzCs%@5_DGCEs(M(2=xpO8qrdGH(R@ccTDy}hSKmtuFL68%dU z-6kPhd!A93Tz4L&?mQ&usnPkpLy@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@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@i)A+N=pnvuic-uWXj-nJZ;lWuaB7uA?noU%S|F zEqepeRk(Ca&lH7*i^a5ZIl6=j!d$Ct7ArY(#c4Flj>_e%oB6s0wc*xXD@*)dS@LJ4 zXy$FIIaand9MdbDi@v&&DeAhfp;c2~tle0RnpC-Mn@-zyYOYBkOV0C0!jX{osKZNN z(la+H<5D_(EMZJ_nX_C=Tl|QQH?Nsyq1`rXbTL;eR;lRTGh0!9ThGtGBiF%s;7B?y zJ)&<hkOSmXg+_b824@iFDLjbye(|*9vkeU2A?7YcYmg??Fmj9MXcIHa-}|e|_<iCp zDS40?Fec(V0sipi8E5_rVX}T@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@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@_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@3cqVXYCNjFp3}e$?qk**HY5s@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@_oivs5=Mq*!trT6 zq8sK+Vp<MGOgU`K$VMa_nTjOh8`Ft+VrFK;ke@-;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@K_~7h+SAZr8#^os`JWQ>;NGIW4y{|f+5`Wt z2mVnH9LwsK|4T;?zA@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@_Bkr70^28}lKy!XBL_kO` zW>aq!P0h0NFLN5jnU?ZqT9<6IY%{z_GiDY#$$YM8F<mk&3mCIo8F<Fii=_;cvN_w7 z&MmB-F4($pnMru@YI-~lmvoY8tJ$qoHm)t1g<{5JZf<i^OXPJNCJjR~%Z6#$nyqi7 z0f9Q82mVUN@l7$3b`fjK=$B2+l}@r>S@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@ILX{b)Jy!Qsl?`k=BzR1ajo>_XzlcC#2sM zKJ2)g`(Uw?%d=kQmjvz)8uH^Fd+T52{C@9x6a_~5-BC+FjeFI<!}Adz^5Z^H-Q;g_ zesBFz&QHH%#OKx1i~mE;Pwx-;u@Nu%`<y@EsehgGlYZj&j(>+ZCzogaF5iDTXUL6O zXg%TEfFYF(O|(tUpAkXmbGYe07cepOM+y|dF0ntknB|er?(+4!p7o@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 190b93193e352..667b0c4004483 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 edc2eff95b831c5216b0d74ec066e9daeed44be2 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sat, 11 Apr 2026 13:27:10 +0000 Subject: [PATCH 03/11] 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@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@Bcr1q7`t2h z#%}Xq-vhVyJz?ij<jzCs%@5_DGCEs(M(2=xpO8qrdGH(R@ccTDy}hSKmtuFL68%dU z-6kPhd!A93Tz4L&?mQ&usnPkpLy@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@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@i)A+N=pnvuic-uWXj-nJZ;lWuaB7uA?noU%S|F zEqepeRk(Ca&lH7*i^a5ZIl6=j!d$Ct7ArY(#c4Flj>_e%oB6s0wc*xXD@*)dS@LJ4 zXy$FIIaand9MdbDi@v&&DeAhfp;c2~tle0RnpC-Mn@-zyYOYBkOV0C0!jX{osKZNN z(la+H<5D_(EMZJ_nX_C=Tl|QQH?Nsyq1`rXbTL;eR;lRTGh0!9ThGtGBiF%s;7B?y zJ)&<hkOSmXg+_b824@iFDLjbye(|*9vkeU2A?7YcYmg??Fmj9MXcIHa-}|e|_<iCp zDS40?Fec(V0sipi8E5_rVX}T@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@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 d50a426d6f5f7800551fe19d8dea7afd9f208d7e Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sat, 11 Apr 2026 13:36:43 +0000 Subject: [PATCH 04/11] dangling field --- .../Sema/warn-lifetime-safety-dangling-field.cpp | 16 ++++++++++++++++ clang/test/Sema/warn-lifetime-safety.cpp | 2 ++ 2 files changed, 18 insertions(+) diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp index fa45458371012..e35a68ceae961 100644 --- a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp +++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp @@ -184,3 +184,19 @@ struct IndirectEscape2 { p = s.data(); } }; + +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 diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 667b0c4004483..b48a380e7b5ea 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 2411c1e5f90da1f4d9c0503f58df3859f2822f08 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sat, 11 Apr 2026 13:47:55 +0000 Subject: [PATCH 05/11] 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 19c3251b9c296..401f02b3bb788 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}} @@ -456,3 +457,23 @@ S forward(const MyObj &obj) { // expected-warning {{parameter in intra-TU functi } } // namespace track_origins_for_lifetimebound_record_type + +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 d194a7a5f2f362667ce03e4b98bee957d9fd12b6 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sat, 11 Apr 2026 14:47:51 +0000 Subject: [PATCH 06/11] 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 401f02b3bb788..5906170b0d146 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 b48a380e7b5ea..e0147d69459f5 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 36cc5af4d82c6a71281976cbbff16c9672d641e1 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sun, 12 Apr 2026 10:11:30 +0000 Subject: [PATCH 07/11] Annotation inference on constructor --- .../Analyses/LifetimeSafety/LifetimeSafety.h | 9 ++-- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 39 +++++++++++++----- clang/lib/Sema/AnalysisBasedWarnings.cpp | 4 ++ clang/lib/Sema/SemaLifetimeSafety.h | 18 +++++--- .../Sema/warn-lifetime-analysis-nocfg.cpp | 40 +++++++++--------- .../Sema/warn-lifetime-safety-suggestions.cpp | 41 ++++++++++++++++++- clang/test/Sema/warn-lifetime-safety.cpp | 12 +++--- 7 files changed, 114 insertions(+), 49 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 08038dd096685..412c28c6587eb 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -28,6 +28,7 @@ #include "clang/Analysis/Analyses/LifetimeSafety/MovedLoans.h" #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h" #include "clang/Analysis/AnalysisDeclContext.h" +#include "llvm/ADT/PointerUnion.h" #include <cstddef> #include <memory> @@ -88,10 +89,10 @@ class LifetimeSafetySemaHelper { const Expr *UseExpr, const Expr *InvalidationExpr) {} - // Suggests lifetime bound annotations for function paramters. - virtual void suggestLifetimeboundToParmVar(SuggestionScope Scope, - const ParmVarDecl *ParmToAnnotate, - const Expr *EscapeExpr) {} + // Suggests lifetime bound annotations for function parameters. + virtual void suggestLifetimeboundToParmVar( + SuggestionScope Scope, const ParmVarDecl *ParmToAnnotate, + llvm::PointerUnion<const Expr *, const FieldDecl *> Target) {} // Reports misuse of [[clang::noescape]] when parameter escapes through return virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape, diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 36477c6f67b52..3181caff4a5e8 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -59,7 +59,7 @@ using EscapingTarget = class LifetimeChecker { private: llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap; - llvm::DenseMap<AnnotationTarget, const Expr *> AnnotationWarningsMap; + llvm::DenseMap<AnnotationTarget, EscapingTarget> AnnotationWarningsMap; llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap; const LoanPropagationAnalysis &LoanPropagation; const MovedLoansAnalysis &MovedLoans; @@ -67,6 +67,7 @@ class LifetimeChecker { FactManager &FactMgr; LifetimeSafetySemaHelper *SemaHelper; ASTContext &AST; + const Decl *D; static SourceLocation GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) { @@ -89,7 +90,7 @@ class LifetimeChecker { LifetimeSafetySemaHelper *SemaHelper) : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans), LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper), - AST(ADC.getASTContext()) { + AST(ADC.getASTContext()), D(ADC.getDecl()) { for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) for (const Fact *F : FactMgr.getFacts(B)) if (const auto *EF = F->getAs<ExpireFact>()) @@ -126,9 +127,13 @@ class LifetimeChecker { return; } // Suggest lifetimebound for parameter escaping through return. - if (!PVD->hasAttr<LifetimeBoundAttr>()) + if (!PVD->hasAttr<LifetimeBoundAttr>()) { if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr()); + else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF)) + if (isa<CXXConstructorDecl>(D)) + AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl()); + } // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a // field! }; @@ -292,14 +297,22 @@ class LifetimeChecker { static void suggestWithScopeForParmVar(LifetimeSafetySemaHelper *SemaHelper, const ParmVarDecl *PVD, SourceManager &SM, - const Expr *EscapeExpr) { + EscapingTarget EscapeTarget) { + llvm::PointerUnion<const Expr *, const FieldDecl *> Target; + if (const auto *E = EscapeTarget.dyn_cast<const Expr *>()) + Target = E; + else if (const auto *F = EscapeTarget.dyn_cast<const FieldDecl *>()) + Target = F; + else + return; + if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM)) SemaHelper->suggestLifetimeboundToParmVar( SuggestionScope::CrossTU, - CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), EscapeExpr); + CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), Target); else SemaHelper->suggestLifetimeboundToParmVar(SuggestionScope::IntraTU, PVD, - EscapeExpr); + Target); } static void @@ -319,11 +332,15 @@ class LifetimeChecker { if (!SemaHelper) return; SourceManager &SM = AST.getSourceManager(); - for (auto [Target, EscapeExpr] : AnnotationWarningsMap) { + for (auto [Target, EscapeTarget] : AnnotationWarningsMap) { if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>()) - suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeExpr); - else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) - suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr); + suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeTarget); + else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) { + if (const auto *EscapeExpr = EscapeTarget.dyn_cast<const Expr *>()) + suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr); + else + llvm_unreachable("Implicit this can only escape via Expr (return)"); + } } } @@ -341,7 +358,7 @@ class LifetimeChecker { } void inferAnnotations() { - for (auto [Target, EscapeExpr] : AnnotationWarningsMap) { + for (auto [Target, EscapeTarget] : AnnotationWarningsMap) { if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) { if (!implicitObjectParamIsLifetimeBound(MD)) SemaHelper->addLifetimeBoundToImplicitThis(cast<CXXMethodDecl>(MD)); diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 48957caf61e5a..c538e7cb2211e 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2925,6 +2925,10 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU, AC.getCFGBuildOptions().PruneTriviallyFalseEdges = true; AC.getCFGBuildOptions().AddLifetime = true; AC.getCFGBuildOptions().AddParameterLifetimes = true; + AC.getCFGBuildOptions().AddInitializers = true; + AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true; + AC.getCFGBuildOptions().AddImplicitDtors = true; + AC.getCFGBuildOptions().AddTemporaryDtors = true; AC.getCFGBuildOptions().setAllAlwaysAdd(); if (AC.getCFG()) runLifetimeSafetyAnalysis(AC, &SemaHelper, LSStats, S.CollectStats); diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index e6f7e3d929f61..add2490be497e 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -131,9 +131,9 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { << UseExpr->getSourceRange(); } - void suggestLifetimeboundToParmVar(SuggestionScope Scope, - const ParmVarDecl *ParmToAnnotate, - const Expr *EscapeExpr) override { + void suggestLifetimeboundToParmVar( + SuggestionScope Scope, const ParmVarDecl *ParmToAnnotate, + llvm::PointerUnion<const Expr *, const FieldDecl *> Target) override { unsigned DiagID = (Scope == SuggestionScope::CrossTU) ? diag::warn_lifetime_safety_cross_tu_param_suggestion @@ -150,9 +150,15 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { S.Diag(ParmToAnnotate->getBeginLoc(), DiagID) << ParmToAnnotate->getSourceRange() << FixItHint::CreateInsertion(InsertionPoint, FixItText); - S.Diag(EscapeExpr->getBeginLoc(), - diag::note_lifetime_safety_suggestion_returned_here) - << EscapeExpr->getSourceRange(); + + if (const auto *EscapeExpr = Target.dyn_cast<const Expr *>()) + S.Diag(EscapeExpr->getBeginLoc(), + diag::note_lifetime_safety_suggestion_returned_here) + << EscapeExpr->getSourceRange(); + else if (const auto *EscapeField = Target.dyn_cast<const FieldDecl *>()) + S.Diag(EscapeField->getLocation(), + diag::note_lifetime_safety_escapes_to_field_here) + << EscapeField->getSourceRange(); } void suggestLifetimeboundToImplicitThis(SuggestionScope Scope, diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp index c352cca2023a6..e07c90f248764 100644 --- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp @@ -1,7 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field -Wreturn-stack-address -verify %s -// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=cfg,cfg-field %s - -// FIXME: cfg-field should be detected in end-of-TU analysis but it doesn't work for constructors! +// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=cfg %s // RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling -verify=cfg %s #include "Inputs/lifetime-analysis.h" @@ -84,17 +82,17 @@ void dangligGslPtrFromTemporary() { struct DanglingGslPtrField { MyIntPointer p; // expected-note {{pointer member declared here}} \ - // cfg-field-note 3 {{this field dangles}} + // cfg-note 3 {{this field dangles}} MyLongPointerFromConversion p2; // expected-note {{pointer member declared here}} \ - // cfg-field-note 2 {{this field dangles}} + // cfg-note 2 {{this field dangles}} - DanglingGslPtrField(int i) : p(&i) {} // cfg-field-warning {{address of stack memory escapes to a field}} + DanglingGslPtrField(int i) : p(&i) {} // cfg-warning {{address of stack memory escapes to a field}} DanglingGslPtrField() : p2(MyLongOwnerWithConversion{}) {} // expected-warning {{initializing pointer member 'p2' to point to a temporary object whose lifetime is shorter than the lifetime of the constructed object}} \ - // cfg-field-warning {{address of stack memory escapes to a field}} + // cfg-warning {{address of stack memory escapes to a field}} DanglingGslPtrField(double) : p(MyIntOwner{}) {} // expected-warning {{initializing pointer member 'p' to point to a temporary object whose lifetime is shorter than the lifetime of the constructed object}} \ - // cfg-field-warning {{address of stack memory escapes to a field}} - DanglingGslPtrField(MyIntOwner io) : p(io) {} // cfg-field-warning {{address of stack memory escapes to a field}} - DanglingGslPtrField(MyLongOwnerWithConversion lo) : p2(lo) {} // cfg-field-warning {{address of stack memory escapes to a field}} + // cfg-warning {{address of stack memory escapes to a field}} + DanglingGslPtrField(MyIntOwner io) : p(io) {} // cfg-warning {{address of stack memory escapes to a field}} + DanglingGslPtrField(MyLongOwnerWithConversion lo) : p2(lo) {} // cfg-warning {{address of stack memory escapes to a field}} }; MyIntPointer danglingGslPtrFromLocal() { @@ -361,11 +359,11 @@ const char *trackThroughMultiplePointer() { struct X { X(std::unique_ptr<int> up) : - pointee(*up), // cfg-field-warning {{may have been moved.}} - pointee2(up.get()), // cfg-field-warning {{may have been moved.}} - pointer(std::move(up)) {} // cfg-field-note 2 {{potentially moved here}} - int &pointee; // cfg-field-note {{this field dangles}} - int *pointee2; // cfg-field-note {{this field dangles}} + pointee(*up), // cfg-warning {{may have been moved.}} + pointee2(up.get()), // cfg-warning {{may have been moved.}} + pointer(std::move(up)) {} // cfg-note 2 {{potentially moved here}} + int &pointee; // cfg-note {{this field dangles}} + int *pointee2; // cfg-note {{this field dangles}} std::unique_ptr<int> pointer; }; @@ -376,9 +374,9 @@ struct X2 { // A common usage that moves the passing owner to the class. // verify a strict warning on this case. X2(XOwner owner) : - pointee(owner.get()), // cfg-field-warning {{may have been moved.}} - owner(std::move(owner)) {} // cfg-field-note {{potentially moved here}} - int* pointee; // cfg-field-note {{this field dangles}} + pointee(owner.get()), // cfg-warning {{may have been moved.}} + owner(std::move(owner)) {} // cfg-note {{potentially moved here}} + int* pointee; // cfg-note {{this field dangles}} XOwner owner; }; @@ -1127,10 +1125,10 @@ struct Foo2 { }; struct Test { - Test(Foo2 foo) : bar(foo.bar.get()), // cfg-field-warning-re {{address of stack memory escapes to a field. {{.*}} may have been moved}} - storage(std::move(foo.bar)) {}; // cfg-field-note {{potentially moved here}} + Test(Foo2 foo) : bar(foo.bar.get()), // cfg-warning-re {{address of stack memory escapes to a field. {{.*}} may have been moved}} + storage(std::move(foo.bar)) {}; // cfg-note {{potentially moved here}} - Bar* bar; // cfg-field-note {{this field dangles}} + Bar* bar; // cfg-note {{this field dangles}} std::unique_ptr<Bar> storage; }; diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp index 5906170b0d146..4f62dd608e814 100644 --- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp +++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp @@ -462,7 +462,8 @@ S forward(const MyObj &obj) { // expected-warning {{parameter in intra-TU functi namespace make_unique_suggestion { struct LifetimeBoundCtor { - LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]); + const MyObj& obj; + LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]) : obj(obj) {} }; std::unique_ptr<LifetimeBoundCtor> create_target(const MyObj& obj) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} @@ -478,3 +479,41 @@ void test_inference() { (void)ptr; // expected-note {{later used here}} } } // namespace make_unique_suggestion + +namespace capturing_constructor { +struct A { + View v; // expected-note {{escapes to this field}} + A(const MyObj& obj) : v(obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; + +struct B { + const int* p; // expected-note {{escapes to this field}} + B(const int& r) : p(&r) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; + +struct C { + View v; // expected-note {{escapes to this field}} + C(View v_param) : v(v_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; + +struct D { + const int* p; // expected-note {{escapes to this field}} + D(const int* p_param) : p(p_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; +} // namespace capturing_constructor + +namespace capturing_constructor_inference { +struct B { + const MyObj* p; // expected-note {{escapes to this field}} + B(const MyObj& obj) : p(&obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; + +void test(B& b_out) { + { + MyObj obj; + B b(obj); // expected-warning {{object whose reference is captured does not live long enough}} + b_out = b; + } // expected-note {{destroyed here}} + (void)b_out; // expected-note {{later used here}} +} +} // namespace capturing_constructor_inference diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index e0147d69459f5..07562c4b33f8e 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -2361,8 +2361,8 @@ void from_template_instantiation() { } 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 value; // expected-note {{this field dangles}} + FieldInitFromLifetimebound() : value(getS(std::string("temp"))) {} // expected-warning {{address of stack memory escapes to a field}} }; S S::return_self_after_registration() const { @@ -2551,13 +2551,13 @@ void nested_local_pointer() { } struct PFieldFromParam { - Pointer<Bar> value; // function-note {{this field dangles}} - PFieldFromParam(Bar bar) : value(bar) {} // function-warning {{address of stack memory escapes to a field}} + Pointer<Bar> value; // expected-note {{this field dangles}} + PFieldFromParam(Bar bar) : value(bar) {} // expected-warning {{address of stack memory escapes to a field}} }; struct PFieldFromTemp { - Pointer<Bar> value; // function-note {{this field dangles}} - PFieldFromTemp() : value(Bar{}) {} // function-warning {{address of stack memory escapes to a field}} + Pointer<Bar> value; // expected-note {{this field dangles}} + PFieldFromTemp() : value(Bar{}) {} // expected-warning {{address of stack memory escapes to a field}} }; } // namespace gslpointer_construction_from_lifetimebound >From 2dc6dd7f6a61a9e7af37025fbec84c8b3844143e Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sun, 12 Apr 2026 11:25:11 +0000 Subject: [PATCH 08/11] refactor --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 3181caff4a5e8..2f92622da33ad 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -67,7 +67,7 @@ class LifetimeChecker { FactManager &FactMgr; LifetimeSafetySemaHelper *SemaHelper; ASTContext &AST; - const Decl *D; + const Decl *FD; static SourceLocation GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) { @@ -90,7 +90,7 @@ class LifetimeChecker { LifetimeSafetySemaHelper *SemaHelper) : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans), LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper), - AST(ADC.getASTContext()), D(ADC.getDecl()) { + AST(ADC.getASTContext()), FD(ADC.getDecl()) { for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) for (const Fact *F : FactMgr.getFacts(B)) if (const auto *EF = F->getAs<ExpireFact>()) @@ -130,9 +130,9 @@ class LifetimeChecker { if (!PVD->hasAttr<LifetimeBoundAttr>()) { if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr()); - else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF)) - if (isa<CXXConstructorDecl>(D)) - AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl()); + else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF); + FieldEsc && isa<CXXConstructorDecl>(FD)) + AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl()); } // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a // field! >From 17d50549d173a4904bf9c08c82599ba09e4a7d5d Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sun, 12 Apr 2026 11:34:46 +0000 Subject: [PATCH 09/11] Refactor EscapingTarget --- .../Analyses/LifetimeSafety/LifetimeSafety.h | 9 ++++++--- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 15 +++++---------- clang/lib/Sema/SemaLifetimeSafety.h | 6 +++--- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 412c28c6587eb..83c3c455c4c81 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -89,10 +89,13 @@ class LifetimeSafetySemaHelper { const Expr *UseExpr, const Expr *InvalidationExpr) {} + using EscapingTarget = + llvm::PointerUnion<const Expr *, const FieldDecl *, const VarDecl *>; + // Suggests lifetime bound annotations for function parameters. - virtual void suggestLifetimeboundToParmVar( - SuggestionScope Scope, const ParmVarDecl *ParmToAnnotate, - llvm::PointerUnion<const Expr *, const FieldDecl *> Target) {} + virtual void suggestLifetimeboundToParmVar(SuggestionScope Scope, + const ParmVarDecl *ParmToAnnotate, + EscapingTarget Target) {} // Reports misuse of [[clang::noescape]] when parameter escapes through return virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape, diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 2f92622da33ad..f82caedcbe044 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -53,8 +53,7 @@ struct PendingWarning { using AnnotationTarget = llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>; -using EscapingTarget = - llvm::PointerUnion<const Expr *, const FieldDecl *, const VarDecl *>; +using EscapingTarget = LifetimeSafetySemaHelper::EscapingTarget; class LifetimeChecker { private: @@ -298,21 +297,17 @@ class LifetimeChecker { const ParmVarDecl *PVD, SourceManager &SM, EscapingTarget EscapeTarget) { - llvm::PointerUnion<const Expr *, const FieldDecl *> Target; - if (const auto *E = EscapeTarget.dyn_cast<const Expr *>()) - Target = E; - else if (const auto *F = EscapeTarget.dyn_cast<const FieldDecl *>()) - Target = F; - else + if (llvm::isa<const VarDecl *>(EscapeTarget)) return; if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM)) SemaHelper->suggestLifetimeboundToParmVar( SuggestionScope::CrossTU, - CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), Target); + CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), + EscapeTarget); else SemaHelper->suggestLifetimeboundToParmVar(SuggestionScope::IntraTU, PVD, - Target); + EscapeTarget); } static void diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index add2490be497e..c56a9692abe1a 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -131,9 +131,9 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { << UseExpr->getSourceRange(); } - void suggestLifetimeboundToParmVar( - SuggestionScope Scope, const ParmVarDecl *ParmToAnnotate, - llvm::PointerUnion<const Expr *, const FieldDecl *> Target) override { + void suggestLifetimeboundToParmVar(SuggestionScope Scope, + const ParmVarDecl *ParmToAnnotate, + EscapingTarget Target) override { unsigned DiagID = (Scope == SuggestionScope::CrossTU) ? diag::warn_lifetime_safety_cross_tu_param_suggestion >From c73e396dc8330e3aaa5a0540b28ac8ca51b0985e Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sun, 12 Apr 2026 11:38:15 +0000 Subject: [PATCH 10/11] doc update --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index f82caedcbe044..fe103739dbcb0 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -125,7 +125,8 @@ class LifetimeChecker { NoescapeWarningsMap.try_emplace(PVD, GlobalEsc->getGlobal()); return; } - // Suggest lifetimebound for parameter escaping through return. + // Suggest lifetimebound for parameter escaping through return or a field + // in constructor. if (!PVD->hasAttr<LifetimeBoundAttr>()) { if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr()); >From f2e9ba03a24e9bc58528e5ad0f753efdcb016d49 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sun, 12 Apr 2026 11:48:52 +0000 Subject: [PATCH 11/11] more tests --- .../Sema/warn-lifetime-safety-suggestions.cpp | 66 ++++++++++++------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp index 4f62dd608e814..ee3ed7f385faf 100644 --- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp +++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp @@ -481,39 +481,59 @@ void test_inference() { } // namespace make_unique_suggestion namespace capturing_constructor { -struct A { +struct CaptureRefToView { View v; // expected-note {{escapes to this field}} - A(const MyObj& obj) : v(obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} + CaptureRefToView(const MyObj& obj) : v(obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} }; -struct B { - const int* p; // expected-note {{escapes to this field}} - B(const int& r) : p(&r) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +CaptureRefToView test_ref_to_view() { + MyObj obj; + CaptureRefToView x(obj); // expected-warning {{address of stack memory is returned later}} + return x; // expected-note {{returned here}} +} + +struct CaptureRefToPtr { + const MyObj* p; // expected-note {{escapes to this field}} + CaptureRefToPtr(const MyObj& obj) : p(&obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} }; -struct C { +CaptureRefToPtr test_ref_to_ptr() { + MyObj obj; + CaptureRefToPtr x(obj); // expected-warning {{address of stack memory is returned later}} + return x; // expected-note {{returned here}} +} + +struct CaptureViewToView { View v; // expected-note {{escapes to this field}} - C(View v_param) : v(v_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} + CaptureViewToView(View v_param) : v(v_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} }; -struct D { - const int* p; // expected-note {{escapes to this field}} - D(const int* p_param) : p(p_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} -}; -} // namespace capturing_constructor +CaptureViewToView test_view_to_view() { + MyObj obj; + View v(obj); // expected-warning {{address of stack memory is returned later}} + CaptureViewToView x(v); + return x; // expected-note {{returned here}} +} -namespace capturing_constructor_inference { -struct B { +struct CapturePtrToPtr { const MyObj* p; // expected-note {{escapes to this field}} - B(const MyObj& obj) : p(&obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} + CapturePtrToPtr(const MyObj* p_param) : p(p_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} }; -void test(B& b_out) { - { - MyObj obj; - B b(obj); // expected-warning {{object whose reference is captured does not live long enough}} - b_out = b; - } // expected-note {{destroyed here}} - (void)b_out; // expected-note {{later used here}} +CapturePtrToPtr test_ptr_to_ptr() { + MyObj obj; + CapturePtrToPtr x(&obj); // expected-warning {{address of stack memory is returned later}} + return x; // expected-note {{returned here}} +} + +struct CaptureRefToRef { + const MyObj& r; // expected-note {{escapes to this field}} + CaptureRefToRef(const MyObj& obj) : r(obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; + +CaptureRefToRef test_ref_to_ref() { + MyObj obj; + CaptureRefToRef x(obj); // expected-warning {{address of stack memory is returned later}} + return x; // expected-note {{returned here}} } -} // namespace capturing_constructor_inference +} // namespace capturing_constructor _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
