[clang] [LifetimeSafety] Avoid suggesting lifetimebound attribute on primary templates declarations (PR #203866)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 28 02:36:18 PDT 2026
https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/203866
>From 434605bd9d9625e6f78f161ae42b2360240408bb Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
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 <nobodqwe at gmail.com>
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 <nobodqwe at gmail.com>
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 <nobodqwe at gmail.com>
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();
More information about the cfe-commits
mailing list