https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/203866
>From 434605bd9d9625e6f78f161ae42b2360240408bb Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Mon, 15 Jun 2026 12:24:14 +0300 Subject: [PATCH 1/4] [LifetimeSafety] Avoid suggesting lifetimbound attribute on primary templates --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 64 ++++++++++- .../misplaced-lifetimebound-cross-tu.cpp | 28 +++++ .../misplaced-lifetimebound-intra-tu.cpp | 104 +++++++++++++++++- 3 files changed, 186 insertions(+), 10 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 155c6072a33a5..0940dabbb856d 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -84,6 +84,49 @@ class LifetimeChecker { llvm_unreachable("unhandled causing fact in PointerUnion"); } + /// For an explicit specialization, returns the source-level specialization + /// declaration to target for attribute placement, if one exists. Skips + /// implicit specialization redeclarations that are backed by the template + /// pattern. In other cases, returns nullptr. + const FunctionDecl * + getExplicitSpecializationDeclForAttr(const FunctionDecl *FDef) { + if (FDef->getTemplateSpecializationKindForInstantiation() != + TSK_ExplicitSpecialization) + return nullptr; + + const SourceManager &SM = AST.getSourceManager(); + auto IsImplicitTemplateSpecialization = [&SM](const FunctionDecl *Redecl, + const FunctionDecl *Pattern) { + return Pattern && Redecl->getTypeSourceInfo() && + Pattern->getTypeSourceInfo() && + SM.getFileLoc( + Redecl->getTypeSourceInfo()->getTypeLoc().getBeginLoc()) == + SM.getFileLoc( + Pattern->getTypeSourceInfo()->getTypeLoc().getBeginLoc()); + }; + + auto redecls = llvm::to_vector(FDef->redecls()); + for (const FunctionDecl *Redecl : llvm::reverse(redecls)) { + if (Redecl == FDef) + continue; + if (auto *MSI = Redecl->getMemberSpecializationInfo(); + MSI && MSI->isExplicitSpecialization()) { + const auto *From = dyn_cast<FunctionDecl>(MSI->getInstantiatedFrom()); + if (IsImplicitTemplateSpecialization(Redecl, From)) + continue; + return Redecl; + } + if (auto *FTSI = Redecl->getTemplateSpecializationInfo(); + FTSI && FTSI->isExplicitInstantiationOrSpecialization()) { + const FunctionDecl *Pattern = FTSI->getTemplate()->getTemplatedDecl(); + if (IsImplicitTemplateSpecialization(Redecl, Pattern)) + continue; + return Redecl; + } + } + return nullptr; + } + public: LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation, const MovedLoansAnalysis &MovedLoans, @@ -358,11 +401,17 @@ class LifetimeChecker { }; const FileID DefFile = GetFile(FDef); - const FunctionDecl *CanonicalDecl = FDef->getCanonicalDecl(); + const FunctionDecl *TargetDecl = FDef->getCanonicalDecl(); + + // For explicit specializations, the canonical decl is the primary template. + // We should target the explicit specialization declaration instead. + if (const FunctionDecl *SpecDecl = + getExplicitSpecializationDeclForAttr(FDef)) + TargetDecl = SpecDecl; + llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2> Targets{ - {CanonicalDecl, GetFile(CanonicalDecl) == DefFile - ? WarningScope::IntraTU - : WarningScope::CrossTU}}; + {TargetDecl, GetFile(TargetDecl) == DefFile ? WarningScope::IntraTU + : WarningScope::CrossTU}}; // Find the earliest redeclaration in each file other than the definition // file. @@ -370,6 +419,13 @@ class LifetimeChecker { FileID File = GetFile(FD); if (File == DefFile) return; + // For explicit specializations, skip the primary template in the redecl + // chain. + if (FDef->getTemplateSpecializationKindForInstantiation() == + TSK_ExplicitSpecialization && + FD->getTemplateSpecializationKindForInstantiation() != + TSK_ExplicitSpecialization) + return; for (auto [SeenFD, _] : Targets) if (GetFile(SeenFD) == File) return; diff --git a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-cross-tu.cpp b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-cross-tu.cpp index 1ccdb5253bf0c..3f910e212a29e 100644 --- a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-cross-tu.cpp +++ b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-cross-tu.cpp @@ -23,6 +23,12 @@ struct HeaderS { // CHECK: fix-it:"{{.*}}cross.h":{[[#THIS_WARN_LINE]]:{{[0-9]+}}-[[#THIS_WARN_LINE]]:{{[0-9]+}}}:" {{\[\[}}clang::lifetimebound{{\]\]}}" }; +template <typename T> +HeaderObj &spec_cross_tu(HeaderObj &obj); + +template <> +HeaderObj &spec_cross_tu<HeaderObj>(HeaderObj &obj); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers in other translation units; add it to the declaration instead}} + //--- cross_1.h struct HeaderObj; HeaderObj &multi_header_param(HeaderObj & obj // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers in other translation units; add it to the declaration instead}} @@ -30,6 +36,15 @@ HeaderObj &multi_header_param(HeaderObj & obj // expected-warning {{'lifetimeb // CHECK: fix-it:"{{.*}}cross_1.h":{[[#PARAM_WARN_LINE]]:{{[0-9]+}}-[[#PARAM_WARN_LINE]]:{{[0-9]+}}}:" {{\[\[}}clang::lifetimebound{{\]\]}}" ); +template <typename T> +struct HeaderMemberSpec { + T data; + T &get(); +}; + +template <> +HeaderObj &HeaderMemberSpec<HeaderObj>::get(); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers in other translation units; add it to the declaration instead}} + //--- cross_2.h struct HeaderObj; HeaderObj &multi_header_param(HeaderObj & obj // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers in other translation units; add it to the declaration instead}} @@ -37,6 +52,9 @@ HeaderObj &multi_header_param(HeaderObj & obj // expected-warning {{'lifetimeb // CHECK: fix-it:"{{.*}}cross_2.h":{[[#PARAM_WARN_LINE]]:{{[0-9]+}}-[[#PARAM_WARN_LINE]]:{{[0-9]+}}}:" {{\[\[}}clang::lifetimebound{{\]\]}}" ); +template <> +HeaderObj &HeaderMemberSpec<HeaderObj>::get(); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers in other translation units; add it to the declaration instead}} + //--- cross.cpp #include "cross.h" #include "cross_1.h" @@ -53,3 +71,13 @@ HeaderObj &HeaderS::header_this() [[clang::lifetimebound]] { // expected-note {{ HeaderObj &multi_header_param(HeaderObj &obj [[clang::lifetimebound]]) { // expected-note 2 {{'lifetimebound' attribute appears here on the definition}} return obj; } + +template <> +HeaderObj &spec_cross_tu<HeaderObj>(HeaderObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + +template <> +HeaderObj &HeaderMemberSpec<HeaderObj>::get() [[clang::lifetimebound]] { // expected-note 2 {{'lifetimebound' attribute appears here on the definition}} + return data; +} diff --git a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp index 28dd65211b268..a49bb3fa84ab8 100644 --- a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp +++ b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp @@ -135,18 +135,110 @@ View friend_redecl(MyObj &obj) { } template <typename T> -// FIXME: Current analysis suggests adding to the primary template declaration, which is not ideal, as it will affect all specializations. -MyObj &spec_func(T & obj // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}} - // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:{{[0-9]+}}-[[@LINE-1]]:{{[0-9]+}}}:" {{\[\[clang::lifetimebound\]\]}}" - ); +MyObj &spec_func(T &obj); template <> -// FIXME: Attribute is inhetired, diagnostic's wording is not correct. -MyObj &spec_func<MyObj>(MyObj &obj [[clang::lifetimebound]]); // expected-note {{'lifetimebound' attribute appears here on the definition}} +MyObj &spec_func<MyObj>(MyObj &obj [[clang::lifetimebound]]); template <> MyObj &spec_func<MyObj>(MyObj &obj) { return obj; } +template <typename T> +MyObj &spec_misplaced(T &obj); + +template <> +MyObj &spec_misplaced<MyObj>(MyObj &obj); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}} + +template <> +MyObj &spec_misplaced<MyObj>(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + +template <class T> +struct MemberSpec { + T data; + T &get(); +}; + +template <> +MyObj &MemberSpec<MyObj>::get(); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}} + +template <> +MyObj &MemberSpec<MyObj>::get() [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute appears here on the definition}} + return data; +} + +template <class T> +struct MemberSpecOk { + T data; + T &get(); +}; + +template <> +MyObj &MemberSpecOk<MyObj>::get() [[clang::lifetimebound]]; + +template <> +MyObj &MemberSpecOk<MyObj>::get() { + return data; +} + +template <class T> +struct NestedMemberSpecBoth { + template <class U> + struct Inner { + U data; + template <class V> + U &both(U &arg, V); + }; +}; + +template <> +template <> +template <> +MyObj &NestedMemberSpecBoth<int>::Inner<MyObj>::both( + MyObj &arg, bool); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}} \ + // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}} + +template <> +template <> +template <> +MyObj &NestedMemberSpecBoth<int>::Inner<MyObj>::both( + MyObj &arg [[clang::lifetimebound]], bool use_arg) // expected-note {{'lifetimebound' attribute appears here on the definition}} + [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute appears here on the definition}} + return use_arg ? arg : data; +} + +template <typename T> +T &multi_spec(T &obj); + +template <> +int &multi_spec<int>(int &obj); + +template <> +MyObj &multi_spec<MyObj>(MyObj &obj); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}} + +template <> +MyObj &multi_spec<MyObj>(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + +template <> +int &multi_spec<int>(int &obj) { return obj; } + +template <typename T> +T &multi_redecl_spec(T &obj); + +template <> +MyObj &multi_redecl_spec<MyObj>(MyObj &obj); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}} + +template <> +MyObj &multi_redecl_spec<MyObj>(MyObj &obj); + +template <> +MyObj &multi_redecl_spec<MyObj>(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + MyObj get_default_obj(); const MyObj &default_arg_param(const MyObj& obj // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}} // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:{{[0-9]+}}-[[@LINE-1]]:{{[0-9]+}}}:" {{\[\[clang::lifetimebound\]\]}}" >From 69e6d88230c3628979f3b1476fdf7cb12540ad57 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Mon, 15 Jun 2026 12:40:10 +0300 Subject: [PATCH 2/4] simplify --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 0940dabbb856d..ffa5da0d0ae92 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -110,19 +110,15 @@ class LifetimeChecker { if (Redecl == FDef) continue; if (auto *MSI = Redecl->getMemberSpecializationInfo(); - MSI && MSI->isExplicitSpecialization()) { - const auto *From = dyn_cast<FunctionDecl>(MSI->getInstantiatedFrom()); - if (IsImplicitTemplateSpecialization(Redecl, From)) - continue; - return Redecl; - } + MSI && MSI->isExplicitSpecialization()) + if (!IsImplicitTemplateSpecialization( + Redecl, dyn_cast<FunctionDecl>(MSI->getInstantiatedFrom()))) + return Redecl; if (auto *FTSI = Redecl->getTemplateSpecializationInfo(); - FTSI && FTSI->isExplicitInstantiationOrSpecialization()) { - const FunctionDecl *Pattern = FTSI->getTemplate()->getTemplatedDecl(); - if (IsImplicitTemplateSpecialization(Redecl, Pattern)) - continue; - return Redecl; - } + FTSI && FTSI->isExplicitInstantiationOrSpecialization()) + if (!IsImplicitTemplateSpecialization( + Redecl, FTSI->getTemplate()->getTemplatedDecl())) + return Redecl; } return nullptr; } >From 5e25fa7ad63b5f2b5767fe974b5d6b10e8d5bb7f Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Mon, 15 Jun 2026 12:49:09 +0300 Subject: [PATCH 3/4] cleanup --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index ffa5da0d0ae92..8f4266eb7977b 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -88,21 +88,15 @@ class LifetimeChecker { /// declaration to target for attribute placement, if one exists. Skips /// implicit specialization redeclarations that are backed by the template /// pattern. In other cases, returns nullptr. - const FunctionDecl * + static const FunctionDecl * getExplicitSpecializationDeclForAttr(const FunctionDecl *FDef) { if (FDef->getTemplateSpecializationKindForInstantiation() != TSK_ExplicitSpecialization) return nullptr; - const SourceManager &SM = AST.getSourceManager(); - auto IsImplicitTemplateSpecialization = [&SM](const FunctionDecl *Redecl, - const FunctionDecl *Pattern) { - return Pattern && Redecl->getTypeSourceInfo() && - Pattern->getTypeSourceInfo() && - SM.getFileLoc( - Redecl->getTypeSourceInfo()->getTypeLoc().getBeginLoc()) == - SM.getFileLoc( - Pattern->getTypeSourceInfo()->getTypeLoc().getBeginLoc()); + auto IsImplicitTemplateSpecialization = [](const FunctionDecl *Redecl, + const FunctionDecl *Pattern) { + return Pattern && Redecl->getBeginLoc() == Pattern->getBeginLoc(); }; auto redecls = llvm::to_vector(FDef->redecls()); @@ -115,7 +109,7 @@ class LifetimeChecker { Redecl, dyn_cast<FunctionDecl>(MSI->getInstantiatedFrom()))) return Redecl; if (auto *FTSI = Redecl->getTemplateSpecializationInfo(); - FTSI && FTSI->isExplicitInstantiationOrSpecialization()) + FTSI && FTSI->isExplicitSpecialization()) if (!IsImplicitTemplateSpecialization( Redecl, FTSI->getTemplate()->getTemplatedDecl())) return Redecl; @@ -415,8 +409,8 @@ class LifetimeChecker { FileID File = GetFile(FD); if (File == DefFile) return; - // For explicit specializations, skip the primary template in the redecl - // chain. + // For explicit specializations, skip redeclarations that do not belong to + // the same explicit-specialization instantiation path. if (FDef->getTemplateSpecializationKindForInstantiation() == TSK_ExplicitSpecialization && FD->getTemplateSpecializationKindForInstantiation() != >From 513f6ae174900cc2d6edb9207a7ae98237806dd6 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Tue, 28 Jul 2026 12:24:17 +0300 Subject: [PATCH 4/4] add comment, rename --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 8f4266eb7977b..8d019d3e85f81 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -94,13 +94,15 @@ class LifetimeChecker { TSK_ExplicitSpecialization) return nullptr; + // FIXME: Remove this source-location heuristic once + // https://github.com/llvm/llvm-project/issues/206790 is fixed. auto IsImplicitTemplateSpecialization = [](const FunctionDecl *Redecl, const FunctionDecl *Pattern) { return Pattern && Redecl->getBeginLoc() == Pattern->getBeginLoc(); }; - auto redecls = llvm::to_vector(FDef->redecls()); - for (const FunctionDecl *Redecl : llvm::reverse(redecls)) { + auto Redecls = llvm::to_vector(FDef->redecls()); + for (const FunctionDecl *Redecl : llvm::reverse(Redecls)) { if (Redecl == FDef) continue; if (auto *MSI = Redecl->getMemberSpecializationInfo(); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
