https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/196635
>From f80b7128ca6e4c83bcf23e5c1eba3ff24c658911 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Wed, 8 Jul 2026 21:19:12 +0300 Subject: [PATCH 1/3] [LifetimeSafety] Add standalone lifetime_capture_by spellings --- clang/docs/ReleaseNotes.md | 8 +++ clang/include/clang/Basic/Attr.td | 12 +++- clang/include/clang/Basic/AttrDocs.td | 15 +++-- .../clang/Basic/DiagnosticSemaKinds.td | 9 ++- clang/lib/Sema/CheckExprLifetime.cpp | 14 +++-- clang/lib/Sema/SemaChecking.cpp | 5 +- clang/lib/Sema/SemaDeclAttr.cpp | 62 ++++++++++++++----- clang/test/Sema/LifetimeSafety/capture-by.cpp | 22 +++---- clang/test/Sema/LifetimeSafety/safety.cpp | 4 +- .../test/SemaCXX/attr-lifetime-capture-by.cpp | 21 ++++--- 10 files changed, 117 insertions(+), 55 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index b49a9db3d5fca..665642c23bfd5 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -553,6 +553,14 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the - The `modular_format` attribute now supports the `fixed` aspect for C ISO 18037 fixed-point `printf` specifiers. +- The `lifetime_capture_by` attribute now accepts three new spelling forms: + `lifetime_capture_by_this`, `lifetime_capture_by_global`, and + `lifetime_capture_by_unknown`. These replace passing `this`, `global`, and + `unknown` as arguments to `lifetime_capture_by`; that argument form is now + deprecated because those names can conflict with user-defined parameters. + Multiple `lifetime_capture_by` attributes may also be written on the same + declaration. + - The `const` and `pure` attributes only apply to functions; they are now diagnosed and ignored when applied to anything else. diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 3f57104d474a7..78ca15d564063 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2137,9 +2137,19 @@ def LifetimeBound : DeclOrTypeAttr { } def LifetimeCaptureBy : DeclOrTypeAttr { - let Spellings = [Clang<"lifetime_capture_by", 1>]; + let Spellings = [Clang<"lifetime_capture_by">, + Clang<"lifetime_capture_by_this">, + Clang<"lifetime_capture_by_global">, + Clang<"lifetime_capture_by_unknown">]; let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>; let Args = [VariadicParamOrParamIdxArgument<"Params">]; + let Accessors = [Accessor<"isThis", [Clang<"lifetime_capture_by_this">]>, + Accessor<"isGlobal", [Clang<"lifetime_capture_by_global">]>, + Accessor<"isUnknown", [Clang<"lifetime_capture_by_unknown">]>, + Accessor<"isStandaloneSpecial", + [Clang<"lifetime_capture_by_this">, + Clang<"lifetime_capture_by_global">, + Clang<"lifetime_capture_by_unknown">]>]; let Documentation = [LifetimeCaptureByDocs]; let AdditionalMembers = [{ private: diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index b67a5b076237c..9cc5ed698a78c 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -4701,6 +4701,7 @@ have their lifetimes extended. def LifetimeCaptureByDocs : Documentation { let Category = DocCatFunction; + let Heading = "lifetime_capture_by, lifetime_capture_by_this, lifetime_capture_by_global, lifetime_capture_by_unknown"; let Content = [{ Similar to `lifetimebound`_, the ``lifetime_capture_by(X)`` attribute on a function parameter or implicit object parameter indicates that the capturing @@ -4746,28 +4747,30 @@ The capturing entity ``X`` can be one of the following: s.insert(a); } -- ``this`` (in case of member functions). +- ``this`` (in case of member functions), written as + ``lifetime_capture_by_this``. .. code-block:: c++ class S { - void addToSet(std::string_view a [[clang::lifetime_capture_by(this)]]) { + void addToSet(std::string_view a [[clang::lifetime_capture_by_this]]) { s.insert(a); } std::set<std::string_view> s; }; - Note: When applied to a constructor parameter, `[[clang::lifetime_capture_by(this)]]` is just an alias of `[[clang::lifetimebound]]`. + Note: When applied to a constructor parameter, `[[clang::lifetime_capture_by_this]]` is just an alias of `[[clang::lifetimebound]]`. -- `global`, `unknown`. +- ``global`` and ``unknown``, written as ``lifetime_capture_by_global`` and + ``lifetime_capture_by_unknown`` respectively. .. code-block:: c++ std::set<std::string_view> s; - void addToSet(std::string_view a [[clang::lifetime_capture_by(global)]]) { + void addToSet(std::string_view a [[clang::lifetime_capture_by_global]]) { s.insert(a); } - void addSomewhere(std::string_view a [[clang::lifetime_capture_by(unknown)]]); + void addSomewhere(std::string_view a [[clang::lifetime_capture_by_unknown]]); The attribute can be applied to the implicit ``this`` parameter of a member function by writing the attribute after the function type: diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 3fa5bce9fb04f..00250dbe5d22d 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3556,18 +3556,21 @@ def err_callback_callee_is_variadic : Error< def err_callback_implicit_this_not_available : Error< "'callback' argument at position %0 references unavailable implicit 'this'">; -def err_capture_by_attribute_multiple : Error< - "multiple 'lifetime_capture' attributes specified">; def err_capture_by_attribute_no_entity : Error< "'lifetime_capture_by' attribute specifies no capturing entity">; def err_capture_by_implicit_this_not_available : Error< "'lifetime_capture_by' argument references unavailable implicit 'this'">; +def err_capture_by_this_attr_without_implicit_this : Error< + "'lifetime_capture_by_this' attribute requires an implicit object parameter">; def err_capture_by_attribute_argument_unknown : Error< "'lifetime_capture_by' attribute argument %0 is not a known function parameter" "; must be a function parameter, 'this', 'global' or 'unknown'">; def err_capture_by_references_itself : Error<"'lifetime_capture_by' argument references itself">; def err_capture_by_param_uses_reserved_name : Error< - "parameter cannot be named '%select{global|unknown}0' while using 'lifetime_capture_by(%select{global|unknown}0)'">; + "parameter cannot be named '%0' while using 'lifetime_capture_by(%0)'">; +def warn_deprecated_capture_by_special_entity : Warning< + "'lifetime_capture_by(%0)' is deprecated; use '%1' instead">, + InGroup<DeprecatedAttributes>; def err_init_method_bad_return_type : Error< "init methods must return an object pointer type, not %0">; diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index d15b2c6518cec..fafee76ec18c3 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -502,12 +502,14 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call, if (CheckCoroCall || CanonCallee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>()) VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg); - else if (const auto *CaptureAttr = - CanonCallee->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>(); - CaptureAttr && isa<CXXConstructorDecl>(CanonCallee) && - llvm::any_of(CaptureAttr->params(), [](int ArgIdx) { - return ArgIdx == LifetimeCaptureByAttr::This; - })) + else if (isa<CXXConstructorDecl>(CanonCallee) && + llvm::any_of(CanonCallee->getParamDecl(I) + ->specific_attrs<LifetimeCaptureByAttr>(), + [](const LifetimeCaptureByAttr *CaptureAttr) { + return llvm::is_contained( + CaptureAttr->params(), + LifetimeCaptureByAttr::This); + })) // `lifetime_capture_by(this)` in a class constructor has the same // semantics as `lifetimebound`: // diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index d52c12670a57b..d122b9050925e 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -4367,8 +4367,9 @@ void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction, } }; for (unsigned I = 0; I < FD->getNumParams(); ++I) - HandleCaptureByAttr(FD->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>(), - I + IsMemberFunction); + for (const auto *A : + FD->getParamDecl(I)->specific_attrs<LifetimeCaptureByAttr>()) + HandleCaptureByAttr(A, I + IsMemberFunction); // Check when the implicit object param is captured. if (IsMemberFunction) { TypeSourceInfo *TSI = FD->getTypeSourceInfo(); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index d3a1786bb8dbe..ebad76969fafb 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4404,17 +4404,41 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) { LifetimeCaptureByAttr *Sema::ParseLifetimeCaptureByAttr(const ParsedAttr &AL, StringRef ParamName) { + StringRef AttrName = AL.getAttrName()->getName(); + StringRef SpecialEntity; + if (AttrName == "lifetime_capture_by_this") + SpecialEntity = "this"; + else if (AttrName == "lifetime_capture_by_global") + SpecialEntity = "global"; + else if (AttrName == "lifetime_capture_by_unknown") + SpecialEntity = "unknown"; + + if (!SpecialEntity.empty() && AL.getNumArgs() != 0) { + Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 0; + return nullptr; + } + // Atleast one capture by is required. - if (AL.getNumArgs() == 0) { + if (SpecialEntity.empty() && AL.getNumArgs() == 0) { Diag(AL.getLoc(), diag::err_capture_by_attribute_no_entity) << AL.getRange(); return nullptr; } - unsigned N = AL.getNumArgs(); + unsigned N = SpecialEntity.empty() ? AL.getNumArgs() : 1; auto ParamIdents = MutableArrayRef<IdentifierInfo *>(new (Context) IdentifierInfo *[N], N); auto ParamLocs = MutableArrayRef<SourceLocation>(new (Context) SourceLocation[N], N); + if (!SpecialEntity.empty()) { + ParamIdents[0] = &Context.Idents.get(SpecialEntity); + ParamLocs[0] = AL.getRange().getEnd(); + int FakeParamIndices[] = {LifetimeCaptureByAttr::Invalid}; + auto *CapturedBy = + LifetimeCaptureByAttr::Create(Context, FakeParamIndices, 1, AL); + CapturedBy->setArgs(ParamIdents, ParamLocs); + return CapturedBy; + } + bool IsValid = true; for (unsigned I = 0; I < N; ++I) { if (AL.isArgExpr(I)) { @@ -4426,6 +4450,17 @@ LifetimeCaptureByAttr *Sema::ParseLifetimeCaptureByAttr(const ParsedAttr &AL, } assert(AL.isArgIdent(I)); IdentifierLoc *IdLoc = AL.getArgAsIdent(I); + StringRef Name = IdLoc->getIdentifierInfo()->getName(); + StringRef Replacement; + if (Name == "this") + Replacement = "lifetime_capture_by_this"; + else if (Name == "global") + Replacement = "lifetime_capture_by_global"; + else if (Name == "unknown") + Replacement = "lifetime_capture_by_unknown"; + if (!Replacement.empty()) + Diag(IdLoc->getLoc(), diag::warn_deprecated_capture_by_special_entity) + << Name << Replacement << IdLoc->getLoc(); if (IdLoc->getIdentifierInfo()->getName() == ParamName) { Diag(IdLoc->getLoc(), diag::err_capture_by_references_itself) << IdLoc->getLoc(); @@ -4446,12 +4481,6 @@ LifetimeCaptureByAttr *Sema::ParseLifetimeCaptureByAttr(const ParsedAttr &AL, static void handleLifetimeCaptureByAttr(Sema &S, Decl *D, const ParsedAttr &AL) { - // Do not allow multiple attributes. - if (D->hasAttr<LifetimeCaptureByAttr>()) { - S.Diag(AL.getLoc(), diag::err_capture_by_attribute_multiple) - << AL.getRange(); - return; - } auto *PVD = dyn_cast<ParmVarDecl>(D); assert(PVD); auto *CaptureByAttr = S.ParseLifetimeCaptureByAttr(AL, PVD->getName()); @@ -4463,7 +4492,7 @@ void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) { bool HasImplicitThisParam = hasImplicitObjectParameter(FD); SmallVector<LifetimeCaptureByAttr *, 1> Attrs; for (ParmVarDecl *PVD : FD->parameters()) - if (auto *A = PVD->getAttr<LifetimeCaptureByAttr>()) + for (auto *A : PVD->specific_attrs<LifetimeCaptureByAttr>()) Attrs.push_back(A); if (HasImplicitThisParam) { TypeSourceInfo *TSI = FD->getTypeSourceInfo(); @@ -4493,7 +4522,7 @@ void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) { for (const ParmVarDecl *PVD : FD->parameters()) if (PVD->getName() == Reserved) Diag(PVD->getLocation(), diag::err_capture_by_param_uses_reserved_name) - << (PVD->getName() == "unknown"); + << PVD->getName(); }; for (auto *CapturedBy : Attrs) { const auto &Entities = CapturedBy->getArgIdents(); @@ -4502,14 +4531,19 @@ void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) { auto It = NameIdxMapping.find(Name); if (It == NameIdxMapping.end()) { auto Loc = CapturedBy->getArgLocs()[I]; - if (!HasImplicitThisParam && Name == "this") - Diag(Loc, diag::err_capture_by_implicit_this_not_available) << Loc; - else + if (!HasImplicitThisParam && Name == "this") { + unsigned DiagID = + CapturedBy->isStandaloneSpecial() + ? diag::err_capture_by_this_attr_without_implicit_this + : diag::err_capture_by_implicit_this_not_available; + Diag(Loc, DiagID) << Loc; + } else Diag(Loc, diag::err_capture_by_attribute_argument_unknown) << Entities[I] << Loc; continue; } - if (Name == "unknown" || Name == "global") + if ((Name == "unknown" || Name == "global") && + !CapturedBy->isStandaloneSpecial()) DisallowReservedParams(Name); CapturedBy->setParamIdx(I, It->second); } diff --git a/clang/test/Sema/LifetimeSafety/capture-by.cpp b/clang/test/Sema/LifetimeSafety/capture-by.cpp index 2877d8d6cd5f9..285f50d61fa1d 100644 --- a/clang/test/Sema/LifetimeSafety/capture-by.cpp +++ b/clang/test/Sema/LifetimeSafety/capture-by.cpp @@ -145,7 +145,7 @@ void use() { namespace temporary_capturing_object { struct S { - void add(const int& x [[clang::lifetime_capture_by(this)]]); + void add(const int& x [[clang::lifetime_capture_by_this]]); }; void test() { @@ -161,8 +161,8 @@ void test() { // Capture by Global and Unknown. // **************************************************************************** namespace capture_by_global_unknown { -void captureByGlobal(std::string_view s [[clang::lifetime_capture_by(global)]]); -void captureByUnknown(std::string_view s [[clang::lifetime_capture_by(unknown)]]); +void captureByGlobal(std::string_view s [[clang::lifetime_capture_by_global]]); +void captureByUnknown(std::string_view s [[clang::lifetime_capture_by_unknown]]); std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]); @@ -188,8 +188,8 @@ void use() { // **************************************************************************** namespace capture_by_this { struct S { - void captureInt(const int& x [[clang::lifetime_capture_by(this)]]); - void captureView(std::string_view sv [[clang::lifetime_capture_by(this)]]); + void captureInt(const int& x [[clang::lifetime_capture_by_this]]); + void captureView(std::string_view sv [[clang::lifetime_capture_by_this]]); }; std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]); std::string_view getNotLifetimeBoundView(const std::string& s); @@ -248,8 +248,8 @@ void useCaptureDefaultArg() { namespace containers_no_distinction { template<class T> struct MySet { - void insert(T&& t [[clang::lifetime_capture_by(this)]]); - void insert(const T& t [[clang::lifetime_capture_by(this)]]); + void insert(T&& t [[clang::lifetime_capture_by_this]]); + void insert(const T& t [[clang::lifetime_capture_by_this]]); }; void user_defined_containers() { MySet<int> set_of_int; @@ -269,8 +269,8 @@ template<> struct IsPointerLikeTypeImpl<std::string_view> : std::true_type {}; template<typename T> concept IsPointerLikeType = std::is_pointer<T>::value || IsPointerLikeTypeImpl<T>::value; template<class T> struct MyVector { - void push_back(T&& t [[clang::lifetime_capture_by(this)]]) requires IsPointerLikeType<T>; - void push_back(const T& t [[clang::lifetime_capture_by(this)]]) requires IsPointerLikeType<T>; + void push_back(T&& t [[clang::lifetime_capture_by_this]]) requires IsPointerLikeType<T>; + void push_back(const T& t [[clang::lifetime_capture_by_this]]) requires IsPointerLikeType<T>; void push_back(T&& t) requires (!IsPointerLikeType<T>); void push_back(const T& t) requires (!IsPointerLikeType<T>); @@ -428,13 +428,13 @@ void use() { namespace on_constructor { struct T { - T(const int& t [[clang::lifetime_capture_by(this)]]); + T(const int& t [[clang::lifetime_capture_by_this]]); }; struct T2 { T2(const int& t [[clang::lifetime_capture_by(x)]], int& x); }; struct T3 { - T3(const T& t [[clang::lifetime_capture_by(this)]]); + T3(const T& t [[clang::lifetime_capture_by_this]]); }; int foo(const T& t); diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index 3c9b58c4e4957..0776fcdf05773 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -3978,7 +3978,7 @@ void test_reference_to_pointer() { struct [[gsl::Pointer]] MyContainer { View stored; - void set(View s [[clang::lifetime_capture_by(this)]]); + void set(View s [[clang::lifetime_capture_by_this]]); }; void member_capture() { @@ -3993,7 +3993,7 @@ void member_capture() { // FIXME: Add support for simple containers without annotations. struct SimpleContainer { View stored; - void set(View s [[clang::lifetime_capture_by(this)]]); + void set(View s [[clang::lifetime_capture_by_this]]); }; void member_capture_simple_container() { diff --git a/clang/test/SemaCXX/attr-lifetime-capture-by.cpp b/clang/test/SemaCXX/attr-lifetime-capture-by.cpp index 6b3726a88c8b5..01a242199c5c4 100644 --- a/clang/test/SemaCXX/attr-lifetime-capture-by.cpp +++ b/clang/test/SemaCXX/attr-lifetime-capture-by.cpp @@ -2,7 +2,7 @@ struct S { const int *x; - void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x = &x; } + void captureInt(const int&x [[clang::lifetime_capture_by_this]]) { this->x = &x; } }; /////////////////////////// @@ -16,12 +16,13 @@ void nonMember( const int &x2 [[clang::lifetime_capture_by(12345 + 12)]], // expected-error {{'lifetime_capture_by' attribute argument '12345 + 12' is not a known function parameter; must be a function parameter, 'this', 'global' or 'unknown'}} const int &x3 [[clang::lifetime_capture_by(abcdefgh)]], // expected-error {{'lifetime_capture_by' attribute argument 'abcdefgh' is not a known function parameter; must be a function parameter, 'this', 'global' or 'unknown'}} const int &x4 [[clang::lifetime_capture_by("abcdefgh")]], // expected-error {{'lifetime_capture_by' attribute argument '"abcdefgh"' is not a known function parameter; must be a function parameter, 'this', 'global' or 'unknown'}} - const int &x5 [[clang::lifetime_capture_by(this)]], // expected-error {{'lifetime_capture_by' argument references unavailable implicit 'this'}} + const int &x5 [[clang::lifetime_capture_by_this]], // expected-error {{'lifetime_capture_by_this' attribute requires an implicit object parameter}} const int &x6 [[clang::lifetime_capture_by()]], // expected-error {{'lifetime_capture_by' attribute specifies no capturing entity}} const int& x7 [[clang::lifetime_capture_by(u, - x7)]], // expected-error {{'lifetime_capture_by' argument references itself}} - const int &x8 [[clang::lifetime_capture_by(global)]], - const int &x9 [[clang::lifetime_capture_by(unknown)]], + x7)]], // expected-error {{'lifetime_capture_by' argument references itself}} + const int &x8 [[clang::lifetime_capture_by(global)]], // expected-warning {{'lifetime_capture_by(global)' is deprecated; use 'lifetime_capture_by_global' instead}} + const int &x9 [[clang::lifetime_capture_by(unknown)]], // expected-warning {{'lifetime_capture_by(unknown)' is deprecated; use 'lifetime_capture_by_unknown' instead}} + const int &x10 [[clang::lifetime_capture_by_global(s)]], // expected-error {{'clang::lifetime_capture_by_global' attribute takes no arguments}} const int &test_memory_leak[[clang::lifetime_capture_by(x1,x2, x3, x4, x5, x6, x7, x8, x9)]], const S& u ) @@ -30,9 +31,9 @@ void nonMember( } void unknown_param_name(const int& unknown, // expected-error {{parameter cannot be named 'unknown' while using 'lifetime_capture_by(unknown)'}} - const int& s [[clang::lifetime_capture_by(unknown)]]); + const int& s [[clang::lifetime_capture_by(unknown)]]); // expected-warning {{'lifetime_capture_by(unknown)' is deprecated; use 'lifetime_capture_by_unknown' instead}} void global_param_name(const int& global, // expected-error {{parameter cannot be named 'global' while using 'lifetime_capture_by(global)'}} - const int& s [[clang::lifetime_capture_by(global)]]); + const int& s [[clang::lifetime_capture_by(global)]]); // expected-warning {{'lifetime_capture_by(global)' is deprecated; use 'lifetime_capture_by_global' instead}} void no_such_param(int i [[clang::lifetime_capture_by(no_such_param)]]); // expected-error {{'lifetime_capture_by' attribute argument 'no_such_param' is not a known function parameter; must be a function parameter, 'this', 'global' or 'unknown'}} void use_no_such_param() { no_such_param(0); } struct T { @@ -41,12 +42,12 @@ struct T { S &s, S &t, const int &y [[clang::lifetime_capture_by(s)]], - const int &z [[clang::lifetime_capture_by(this, x, y)]], - const int &u [[clang::lifetime_capture_by(global, unknown, x, s)]]) + const int &z [[clang::lifetime_capture_by(x, y), clang::lifetime_capture_by_this]], + const int &u [[clang::lifetime_capture_by(global, unknown, x, s)]]) // expected-warning 2 {{deprecated}} { s.captureInt(x); } void explicit_this1(this T& self, const int &x [[clang::lifetime_capture_by(self)]]); - void explicit_this2(this T& self, const int &x [[clang::lifetime_capture_by(this)]]); // expected-error {{argument references unavailable implicit 'this'}} + void explicit_this2(this T& self, const int &x [[clang::lifetime_capture_by(this)]]); // expected-warning {{'lifetime_capture_by(this)' is deprecated; use 'lifetime_capture_by_this' instead}} expected-error {{argument references unavailable implicit 'this'}} }; >From 85fe259a3ab8062b4dc0c4c02e49794a78ebb695 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Wed, 8 Jul 2026 22:51:16 +0300 Subject: [PATCH 2/3] add note, add 2 test cases --- clang/docs/ReleaseNotes.md | 4 ++-- clang/test/Sema/LifetimeSafety/capture-by.cpp | 12 ++++++++++++ clang/test/SemaCXX/attr-lifetime-capture-by.cpp | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 665642c23bfd5..e1b3b75086f3a 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -558,8 +558,8 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the `lifetime_capture_by_unknown`. These replace passing `this`, `global`, and `unknown` as arguments to `lifetime_capture_by`; that argument form is now deprecated because those names can conflict with user-defined parameters. - Multiple `lifetime_capture_by` attributes may also be written on the same - declaration. + They will be removed in the next release. Multiple `lifetime_capture_by` + attributes may also be written on the same declaration. - The `const` and `pure` attributes only apply to functions; they are now diagnosed and ignored when applied to anything else. diff --git a/clang/test/Sema/LifetimeSafety/capture-by.cpp b/clang/test/Sema/LifetimeSafety/capture-by.cpp index 285f50d61fa1d..bfe2a3a5589bc 100644 --- a/clang/test/Sema/LifetimeSafety/capture-by.cpp +++ b/clang/test/Sema/LifetimeSafety/capture-by.cpp @@ -85,6 +85,18 @@ void use() { } } // namespace capture_string_view +namespace multiple_capture_by_attrs { +struct X {} x1, x2; +void capture(std::string_view s [[clang::lifetime_capture_by(x1), + clang::lifetime_capture_by(x2)]], + X &x1, X &x2); + +void use() { + capture(std::string(), // expected-warning {{object whose reference is captured by 'x1' will be destroyed at the end of the full-expression}} expected-warning {{object whose reference is captured by 'x2' will be destroyed at the end of the full-expression}} + x1, x2); +} +} // namespace multiple_capture_by_attrs + // **************************************************************************** // Capture pointer (eg: std::string*) // **************************************************************************** diff --git a/clang/test/SemaCXX/attr-lifetime-capture-by.cpp b/clang/test/SemaCXX/attr-lifetime-capture-by.cpp index 01a242199c5c4..6a4e450294ba2 100644 --- a/clang/test/SemaCXX/attr-lifetime-capture-by.cpp +++ b/clang/test/SemaCXX/attr-lifetime-capture-by.cpp @@ -23,6 +23,7 @@ void nonMember( const int &x8 [[clang::lifetime_capture_by(global)]], // expected-warning {{'lifetime_capture_by(global)' is deprecated; use 'lifetime_capture_by_global' instead}} const int &x9 [[clang::lifetime_capture_by(unknown)]], // expected-warning {{'lifetime_capture_by(unknown)' is deprecated; use 'lifetime_capture_by_unknown' instead}} const int &x10 [[clang::lifetime_capture_by_global(s)]], // expected-error {{'clang::lifetime_capture_by_global' attribute takes no arguments}} + const int &x11 [[clang::lifetime_capture_by(this)]], // expected-error {{'lifetime_capture_by' argument references unavailable implicit 'this'}} // expected-warning {{'lifetime_capture_by(this)' is deprecated; use 'lifetime_capture_by_this' instead}} const int &test_memory_leak[[clang::lifetime_capture_by(x1,x2, x3, x4, x5, x6, x7, x8, x9)]], const S& u ) >From 986023273b2db33514c1fee5b3ecec56b5694a63 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Thu, 9 Jul 2026 18:59:13 +0300 Subject: [PATCH 3/3] do not allow same spellings multiple time --- clang/docs/ReleaseNotes.md | 5 ++- clang/include/clang/Basic/AttrDocs.td | 19 ++++++--- .../clang/Basic/DiagnosticSemaKinds.td | 2 + clang/lib/Sema/SemaDeclAttr.cpp | 39 ++++++++++++++++++- clang/test/Sema/LifetimeSafety/capture-by.cpp | 10 ++--- .../test/SemaCXX/attr-lifetime-capture-by.cpp | 30 ++++++++++++++ 6 files changed, 91 insertions(+), 14 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index e1b3b75086f3a..b9b68a7b8683a 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -558,8 +558,9 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the `lifetime_capture_by_unknown`. These replace passing `this`, `global`, and `unknown` as arguments to `lifetime_capture_by`; that argument form is now deprecated because those names can conflict with user-defined parameters. - They will be removed in the next release. Multiple `lifetime_capture_by` - attributes may also be written on the same declaration. + They will be removed in the next release. Distinct `lifetime_capture_by` + spellings may also be combined on the same declaration, but each spelling may + appear at most once. - The `const` and `pure` attributes only apply to functions; they are now diagnosed and ignored when applied to anything else. diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 9cc5ed698a78c..5faec4c284d09 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -4703,9 +4703,11 @@ def LifetimeCaptureByDocs : Documentation { let Category = DocCatFunction; let Heading = "lifetime_capture_by, lifetime_capture_by_this, lifetime_capture_by_global, lifetime_capture_by_unknown"; let Content = [{ -Similar to `lifetimebound`_, the ``lifetime_capture_by(X)`` attribute on a -function parameter or implicit object parameter indicates that the capturing -entity ``X`` may refer to the object referred by that parameter. +Similar to `lifetimebound`_, the ``lifetime_capture_by`` attribute family on a +function parameter or implicit object parameter indicates that a capturing +entity may refer to the object referred to by that parameter. The capturing +entity can be named in ``lifetime_capture_by(X)`` or selected by one of the +standalone special forms listed below. Below is a list of types of the parameters and what they're considered to refer to: @@ -4737,7 +4739,7 @@ temporary object. addToSet(local, s); // Ok. } -The capturing entity ``X`` can be one of the following: +The capturing entity can be one of the following: - Another (named) function parameter. @@ -4783,7 +4785,7 @@ function by writing the attribute after the function type: } }; -The attribute supports specifying more than one capturing entities: +The parameter-list form supports specifying more than one capturing entity: .. code-block:: c++ @@ -4794,6 +4796,13 @@ The attribute supports specifying more than one capturing entities: s2.insert(a); } +Distinct ``lifetime_capture_by`` forms can also be combined on the same +declaration, but each form can appear at most once. For example, +``[[clang::lifetime_capture_by(s), clang::lifetime_capture_by_this]]`` is +allowed, but two ``[[clang::lifetime_capture_by(...)]]`` attributes or two +``[[clang::lifetime_capture_by_this]]`` attributes on the same declaration are +rejected. + Limitation: The capturing entity ``X`` is not used by the analysis and is used for documentation purposes only. This is because the analysis is statement-local and only detects use of a temporary as an argument to the diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 00250dbe5d22d..6c2caacac9fd6 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3568,6 +3568,8 @@ def err_capture_by_attribute_argument_unknown : Error< def err_capture_by_references_itself : Error<"'lifetime_capture_by' argument references itself">; def err_capture_by_param_uses_reserved_name : Error< "parameter cannot be named '%0' while using 'lifetime_capture_by(%0)'">; +def err_capture_by_attribute_multiple : Error< + "multiple '%0' attributes specified">; def warn_deprecated_capture_by_special_entity : Warning< "'lifetime_capture_by(%0)' is deprecated; use '%1' instead">, InGroup<DeprecatedAttributes>; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index ebad76969fafb..0d5b0f5fa2898 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4484,8 +4484,43 @@ static void handleLifetimeCaptureByAttr(Sema &S, Decl *D, auto *PVD = dyn_cast<ParmVarDecl>(D); assert(PVD); auto *CaptureByAttr = S.ParseLifetimeCaptureByAttr(AL, PVD->getName()); - if (CaptureByAttr) - D->addAttr(CaptureByAttr); + if (!CaptureByAttr) + return; + + enum class SpellingKind { ParameterList, This, Global, Unknown }; + auto GetSpellingKind = [](const LifetimeCaptureByAttr *A) { + if (A->isThis()) + return SpellingKind::This; + if (A->isGlobal()) + return SpellingKind::Global; + if (A->isUnknown()) + return SpellingKind::Unknown; + return SpellingKind::ParameterList; + }; + auto GetSpellingName = [](SpellingKind Kind) -> StringRef { + switch (Kind) { + case SpellingKind::ParameterList: + return "lifetime_capture_by"; + case SpellingKind::This: + return "lifetime_capture_by_this"; + case SpellingKind::Global: + return "lifetime_capture_by_global"; + case SpellingKind::Unknown: + return "lifetime_capture_by_unknown"; + } + llvm_unreachable("unknown lifetime_capture_by spelling kind"); + }; + + SpellingKind NewKind = GetSpellingKind(CaptureByAttr); + for (const auto *Existing : D->specific_attrs<LifetimeCaptureByAttr>()) { + if (GetSpellingKind(Existing) == NewKind) { + S.Diag(AL.getLoc(), diag::err_capture_by_attribute_multiple) + << GetSpellingName(NewKind) << AL.getRange(); + return; + } + } + + D->addAttr(CaptureByAttr); } void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) { diff --git a/clang/test/Sema/LifetimeSafety/capture-by.cpp b/clang/test/Sema/LifetimeSafety/capture-by.cpp index bfe2a3a5589bc..e81669fe30782 100644 --- a/clang/test/Sema/LifetimeSafety/capture-by.cpp +++ b/clang/test/Sema/LifetimeSafety/capture-by.cpp @@ -86,14 +86,14 @@ void use() { } // namespace capture_string_view namespace multiple_capture_by_attrs { -struct X {} x1, x2; +struct X {} x1; void capture(std::string_view s [[clang::lifetime_capture_by(x1), - clang::lifetime_capture_by(x2)]], - X &x1, X &x2); + clang::lifetime_capture_by_global]], + X &x1); void use() { - capture(std::string(), // expected-warning {{object whose reference is captured by 'x1' will be destroyed at the end of the full-expression}} expected-warning {{object whose reference is captured by 'x2' will be destroyed at the end of the full-expression}} - x1, x2); + capture(std::string(), // expected-warning {{object whose reference is captured by 'x1' will be destroyed at the end of the full-expression}} expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}} + x1); } } // namespace multiple_capture_by_attrs diff --git a/clang/test/SemaCXX/attr-lifetime-capture-by.cpp b/clang/test/SemaCXX/attr-lifetime-capture-by.cpp index 6a4e450294ba2..0848cf98c160b 100644 --- a/clang/test/SemaCXX/attr-lifetime-capture-by.cpp +++ b/clang/test/SemaCXX/attr-lifetime-capture-by.cpp @@ -52,3 +52,33 @@ struct T { void explicit_this1(this T& self, const int &x [[clang::lifetime_capture_by(self)]]); void explicit_this2(this T& self, const int &x [[clang::lifetime_capture_by(this)]]); // expected-warning {{'lifetime_capture_by(this)' is deprecated; use 'lifetime_capture_by_this' instead}} expected-error {{argument references unavailable implicit 'this'}} }; + +void duplicate_parameter_list( + const int &x [[clang::lifetime_capture_by(s), + clang::lifetime_capture_by(t)]], // expected-error {{multiple 'lifetime_capture_by' attributes specified}} + S &s, S &t); + +void duplicate_global( + const int &x [[clang::lifetime_capture_by_global, + clang::lifetime_capture_by_global]]); // expected-error {{multiple 'lifetime_capture_by_global' attributes specified}} + +void duplicate_unknown( + const int &x [[clang::lifetime_capture_by_unknown, + clang::lifetime_capture_by_unknown]]); // expected-error {{multiple 'lifetime_capture_by_unknown' attributes specified}} + +struct U { + void duplicate_this( + const int &x [[clang::lifetime_capture_by_this, + clang::lifetime_capture_by_this]]); // expected-error {{multiple 'lifetime_capture_by_this' attributes specified}} + + void different_spellings( + const int &x [[clang::lifetime_capture_by(s), + clang::lifetime_capture_by_this, + clang::lifetime_capture_by_global, + clang::lifetime_capture_by_unknown]], + S &s); + + void deprecated_this_spelling_is_distinct( + const int &x [[clang::lifetime_capture_by(this), // expected-warning {{'lifetime_capture_by(this)' is deprecated; use 'lifetime_capture_by_this' instead}} + clang::lifetime_capture_by_this]]); +}; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
