https://github.com/kashika0112 updated https://github.com/llvm/llvm-project/pull/219423
>From 578069cc984ca04d7eb3e1e11a0014bf996b86d1 Mon Sep 17 00:00:00 2001 From: Kashika Akhouri <[email protected]> Date: Fri, 28 Aug 2026 09:07:38 +0000 Subject: [PATCH] Add support for capture_by on method declarations --- .../LifetimeSafety/LifetimeAnnotations.h | 6 +++ .../LifetimeSafety/FactsGenerator.cpp | 40 +++++++------- .../LifetimeSafety/LifetimeAnnotations.cpp | 18 +++++++ clang/lib/Analysis/LifetimeSafety/Origins.cpp | 36 +++++++------ clang/test/Sema/LifetimeSafety/capture-by.cpp | 53 +++++++++++++++++-- 5 files changed, 115 insertions(+), 38 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h index 6f1259fa867dc..dd60ea2ee0660 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h @@ -60,6 +60,12 @@ getImplicitObjectParamLifetimeBoundAttr(const FunctionDecl *FD); /// method or because it's a normal assignment operator. bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD); +/// Check if a function has a lifetime_capture_by attribute on its declaration +/// or its function type (which represents the implicit 'this' parameter for +/// methods). Returns the attribute if found, nullptr otherwise. +const LifetimeCaptureByAttr * +getCaptureByAttrFromFunctionType(const FunctionDecl *FD); + using LifetimeBoundParamInfo = llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>; diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index ce3039528b283..0c2f414409be4 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -1033,22 +1033,31 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, const auto *Method = dyn_cast<CXXMethodDecl>(FD); bool IsInstance = Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD); - auto getParamDeclAt = [FD, IsInstance](unsigned I) -> const ParmVarDecl * { + auto getArgCaptureByAttr = + [FD, IsInstance]( + unsigned I) -> std::pair<const LifetimeCaptureByAttr *, QualType> { if (IsInstance) { - // FIXME: Add support for I == 0 i.e. capture_by on function declarations - if (I > 0 && I - 1 < FD->getNumParams()) - return FD->getParamDecl(I - 1); + if (I == 0) { + auto *MethodDecl = cast<CXXMethodDecl>(FD); + QualType ParamType = MethodDecl->getFunctionObjectParameterType(); + return {getCaptureByAttrFromFunctionType(FD), ParamType}; + } + if (I > 0 && I - 1 < FD->getNumParams()) { + const ParmVarDecl *PVD = FD->getParamDecl(I - 1); + return {PVD ? PVD->getAttr<LifetimeCaptureByAttr>() : nullptr, + PVD->getType()}; + } } else { - if (I < FD->getNumParams()) - return FD->getParamDecl(I); + if (I < FD->getNumParams()) { + const ParmVarDecl *PVD = FD->getParamDecl(I); + return {PVD ? PVD->getAttr<LifetimeCaptureByAttr>() : nullptr, + PVD->getType()}; + } } - return nullptr; + return {nullptr, QualType()}; }; for (unsigned I = 0; I < Args.size(); ++I) { - const ParmVarDecl *PVD = getParamDeclAt(I); - if (!PVD) - continue; - const auto *Attr = PVD->getAttr<LifetimeCaptureByAttr>(); + auto [Attr, ParamType] = getArgCaptureByAttr(I); if (!Attr) continue; OriginList *CapturedOriginList = getOriginsList(*Args[I]); @@ -1056,7 +1065,7 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, continue; // For references to pointer-like types, peel the outer origin (the pointer // object itself) so that we capture the underlying data (the inner origin). - if (QualType ParamType = PVD->getType(); + if (!ParamType.isNull() && (ParamType->isReferenceType() && isPointerLikeType(ParamType->getPointeeType())) && CapturedOriginList->getLength() > 1) @@ -1067,13 +1076,8 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, CapturingArgIdx == LifetimeCaptureByAttr::Unknown || CapturingArgIdx == LifetimeCaptureByAttr::Invalid) continue; - ArrayRef<const Expr *> CallArgs = IsInstance ? Args.drop_front() : Args; - const Expr *CapturedByArg = - (CapturingArgIdx == LifetimeCaptureByAttr::This) - ? Args[0] - : CallArgs[CapturingArgIdx]; + const Expr *CapturedByArg = Args[CapturingArgIdx]; assert(CapturedByArg && "Capturer expression must be valid"); - OriginList *CapturingOriginList = getOriginsList(*CapturedByArg); OriginList *Dest = getRValueOrigins(CapturedByArg, CapturingOriginList); if (!Dest) diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp index 154657193cabc..bef5d2ad3ec72 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp @@ -136,6 +136,24 @@ FunctionCallInfo getFunctionCallInfo(const Expr *Call) { return Info; } +const LifetimeCaptureByAttr * +getCaptureByAttrFromFunctionType(const FunctionDecl *FD) { + const TypeSourceInfo *TSI = FD->getTypeSourceInfo(); + if (!TSI) + return nullptr; + // Walk through the type layers looking for a capture_by attribute. + TypeLoc TL = TSI->getTypeLoc(); + while (true) { + auto ATL = TL.getAsAdjusted<AttributedTypeLoc>(); + if (!ATL) + break; + if (auto *Attr = ATL.getAttrAs<LifetimeCaptureByAttr>()) + return Attr; + TL = ATL.getModifiedLoc(); + } + return nullptr; +} + std::optional<LifetimeBoundParamInfo> getTrackedArgInfo(const FunctionDecl *FD, llvm::ArrayRef<const Expr *> Args, unsigned I) { diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp index 0c0c280d73cb6..4b528f463db7e 100644 --- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp @@ -107,23 +107,29 @@ class LifetimeAnnotatedOriginTypeCollector const auto *MD = dyn_cast<CXXMethodDecl>(FD); bool IsInstance = MD && MD->isInstance(); int Offset = (MD && MD->isImplicitObjectMemberFunction()) ? 1 : 0; + auto ProcessAttr = [&](const LifetimeCaptureByAttr *Attr) { + for (int Idx : Attr->params()) { + if (Idx == LifetimeCaptureByAttr::Global || + Idx == LifetimeCaptureByAttr::Unknown || + Idx == LifetimeCaptureByAttr::Invalid) + continue; + if (Idx == LifetimeCaptureByAttr::This) { + if (IsInstance) + CollectedTypes.push_back(MD->getFunctionObjectParameterType()); + } else if (int LogicalIdx = Idx - Offset; + LogicalIdx >= 0 && + (unsigned)LogicalIdx < FD->getNumParams()) { + CollectedTypes.push_back( + FD->getParamDecl(LogicalIdx)->getType().getNonReferenceType()); + } + } + }; + if (const auto *MethodAttr = + IsInstance ? getCaptureByAttrFromFunctionType(FD) : nullptr) + ProcessAttr(MethodAttr); for (const auto *Param : FD->parameters()) { if (auto *Attr = Param->getAttr<LifetimeCaptureByAttr>()) { - for (int Idx : Attr->params()) { - if (Idx == LifetimeCaptureByAttr::Global || - Idx == LifetimeCaptureByAttr::Unknown || - Idx == LifetimeCaptureByAttr::Invalid) - continue; - if (Idx == LifetimeCaptureByAttr::This) { - if (IsInstance) - CollectedTypes.push_back(MD->getFunctionObjectParameterType()); - } else if (int LogicalIdx = Idx - Offset; - LogicalIdx >= 0 && - (unsigned)LogicalIdx < FD->getNumParams()) { - CollectedTypes.push_back( - FD->getParamDecl(LogicalIdx)->getType().getNonReferenceType()); - } - } + ProcessAttr(Attr); } } } diff --git a/clang/test/Sema/LifetimeSafety/capture-by.cpp b/clang/test/Sema/LifetimeSafety/capture-by.cpp index e9e745b52ec0b..e6b0cb2e24e36 100644 --- a/clang/test/Sema/LifetimeSafety/capture-by.cpp +++ b/clang/test/Sema/LifetimeSafety/capture-by.cpp @@ -252,19 +252,62 @@ void initializer_list_capture() { // Implicit object param 'this' is captured // **************************************************************************** namespace this_is_captured { -struct X {} x; +struct X {} x; // cfg-note {{this global dangles}} struct S { void capture(X &x) [[clang::lifetime_capture_by(x)]]; }; -// FIXME: Add support for capture of method declarations in -Wlifetime-safety void use() { - S{}.capture(x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} - S s; - s.capture(x); + S{}.capture(x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} \ + // cfg-warning {{temporary object does not live long enough}} \ + // cfg-note {{temporary object is destroyed here}} + (void)x; // cfg-note {{later used here}} + S s; + s.capture(x); // cfg-warning {{stack memory associated with local variable 's' escapes to the global variable 'x' which will dangle}} } } // namespace this_is_captured +namespace method_decl_capture { +struct Container { + const void* stored = nullptr; + void add(const void* s) { stored = s; } +}; +struct Obj { + void register_into(Container& c) const [[clang::lifetime_capture_by(c)]] { + c.add(this); + } +}; +void test() { + Container c; + { + Obj local_obj; + local_obj.register_into(c); // cfg-warning {{local variable 'local_obj' does not live long enough}} + } // cfg-note {{local variable 'local_obj' is destroyed here}} + (void)c.stored; // cfg-note {{later used here}} +} +} // namespace method_decl_capture + +namespace method_capture_chaining { +struct Container { + const void* stored = nullptr; + void capture(const void* v [[clang::lifetime_capture_by_this]]); +}; +struct Obj { + const void* data = nullptr; + void addTo(Container& c) const [[clang::lifetime_capture_by(c)]] { + c.capture(data); + } +}; +void test() { + Container c; + { + Obj local_obj; + local_obj.addTo(c); // cfg-warning {{local variable 'local_obj' does not live long enough}} + } // cfg-note {{local variable 'local_obj' is destroyed here}} + (void)c.stored; // cfg-note {{later used here}} +} +} // namespace method_capture_chaining + namespace temporary_capturing_object { struct S { void add(const int& x [[clang::lifetime_capture_by_this]]); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
