https://github.com/NeKon69 created
https://github.com/llvm/llvm-project/pull/203866
The core issue with templates is that Clang can create implicit template
specialization for the primary template. To address this,
`IsImplicitTemplateSpecialization` skips those declarations by checking whether
the declaration has the same source location as the primary template pattern.
Note that this will still warn on the primary template when there is no
separate explicit specialization declaration:
```cpp
template <typename T>
T &foo(T &obj); // suggests here!
template <>
int &foo<int>(int &x [[clang::lifetimebound]]) { return x; }
```
I kept this behavior to avoid complicating the diagnostic wording. (Probably
can come as a future patch?)
Fixes #198632
>From 408c266a04ba245a0c003a710f834d7ec6804a1c Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Mon, 15 Jun 2026 12:24:14 +0300
Subject: [PATCH 1/3] [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 d41d6f43f837b..47379a7737246 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -82,6 +82,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,
@@ -342,11 +385,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.
@@ -354,6 +403,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 cade7c7a81105..c819173a021cd 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 & // 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 & //
expected-warning {{'lifetimebound'
obj // CHECK:
fix-it:"{{.*}}cross_1.h":{[[#PARAM_WARN_LINE+2]]:{{[0-9]+}}-[[#PARAM_WARN_LINE+2]]:{{[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 & // 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 & //
expected-warning {{'lifetimebound'
obj // CHECK:
fix-it:"{{.*}}cross_2.h":{[[#PARAM_WARN_LINE+2]]:{{[0-9]+}}-[[#PARAM_WARN_LINE+2]]:{{[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 7fa4cae100509..c2157ef0ca2ad 100644
--- a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp
+++ b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp
@@ -133,18 +133,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 & // expected-warning {{'lifetimebound' attribute on this
definition is not visible to callers before the definition; add it to the
declaration instead}}
- obj // CHECK:
fix-it:"{{.*}}":{[[@LINE]]:{{[0-9]+}}-[[@LINE]]:{{[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& // expected-warning
{{'lifetimebound' attribute on this definition is not visible to callers before
the definition; add it to the declaration instead}}
obj // CHECK:
fix-it:"{{.*}}":{[[@LINE]]:{{[0-9]+}}-[[@LINE]]:{{[0-9]+}}}:"
{{\[\[clang::lifetimebound\]\]}}"
>From d4b4dc8a855687d40c0bad694e766c2948bb29f1 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Mon, 15 Jun 2026 12:40:10 +0300
Subject: [PATCH 2/3] 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 47379a7737246..2083ecf781a95 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -108,19 +108,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 51c8ad076aeb61426468a6a304ba915d84bc9029 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Mon, 15 Jun 2026 12:49:09 +0300
Subject: [PATCH 3/3] 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 2083ecf781a95..2ab271c65b85d 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -86,21 +86,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());
@@ -113,7 +107,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;
@@ -399,8 +393,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() !=
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits