[clang] [LifetimeSafety] Add fix-it for misplaced lifetimebound attributes (PR #199149)
David Meng via cfe-commits
cfe-commits at lists.llvm.org
Mon May 25 07:55:07 PDT 2026
https://github.com/davidmenggx updated https://github.com/llvm/llvm-project/pull/199149
>From 8b45c1b928f158b610764da31ffbbc5ad069fdcd Mon Sep 17 00:00:00 2001
From: David Meng <davidmenggx at gmail.com>
Date: Thu, 21 May 2026 19:17:06 -0700
Subject: [PATCH 1/4] [LifetimeSafety] Add fix-it for misplaced lifetimebound
attributes
This patch adds a fix-it hint for `warn_lifetime_safety_intra_tu_misplaced_lifetimebound`
and `warn_lifetime_safety_cross_tu_misplaced_lifetimebound` to the appropriate declaration.
The fix-it attribute is emitted in the correct location, accounting for pure virtual
functions, overrides, trailing return types, and default arguments. The message is suppressed
for macros.
Resolves #198634
---
clang/lib/Sema/SemaLifetimeSafety.h | 44 ++++++++++++++--
...afety-misplaced-lifetimebound-cross-tu.cpp | 3 ++
...afety-misplaced-lifetimebound-intra-tu.cpp | 52 ++++++++++++++++++-
3 files changed, 94 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index af5202c33fed0..eb1854548f97c 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -297,9 +297,25 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
Scope == WarningScope::CrossTU
? diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound
: diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound;
- S.Diag(Lexer::getLocForEndOfToken(FDecl->getEndLoc(), 0,
- S.getSourceManager(), S.getLangOpts()),
- DiagID);
+
+ SourceLocation DiagLoc = Lexer::getLocForEndOfToken(
+ FDecl->getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
+
+ // Scope so diagnostic emits first.
+ {
+ auto DB = S.Diag(DiagLoc, DiagID);
+
+ SourceLocation FixItLoc;
+ if (const TypeSourceInfo *TSI = FDecl->getTypeSourceInfo())
+ FixItLoc =
+ Lexer::getLocForEndOfToken(TSI->getTypeLoc().getEndLoc(), 0,
+ S.getSourceManager(), S.getLangOpts());
+ else
+ FixItLoc = DiagLoc;
+
+ if (FixItLoc.isValid() && !FixItLoc.isMacroID())
+ DB << FixItHint::CreateInsertion(FixItLoc, " [[clang::lifetimebound]]");
+ }
S.Diag(Attr->getLocation(), diag::note_lifetime_safety_lifetimebound_here)
<< Attr->getRange();
@@ -315,7 +331,27 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
Scope == WarningScope::CrossTU
? diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound
: diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound;
- S.Diag(PVDDecl->getBeginLoc(), DiagID) << PVDDecl->getSourceRange();
+
+ // Scope so diagnostic emits first.
+ {
+ auto DB = S.Diag(PVDDecl->getBeginLoc(), DiagID)
+ << PVDDecl->getSourceRange();
+
+ SourceLocation FixItLoc;
+ if (PVDDecl->getIdentifier())
+ FixItLoc = Lexer::getLocForEndOfToken(
+ PVDDecl->getLocation(), 0, S.getSourceManager(), S.getLangOpts());
+ else if (const TypeSourceInfo *TSI = PVDDecl->getTypeSourceInfo())
+ FixItLoc =
+ Lexer::getLocForEndOfToken(TSI->getTypeLoc().getEndLoc(), 0,
+ S.getSourceManager(), S.getLangOpts());
+ else
+ FixItLoc = Lexer::getLocForEndOfToken(
+ PVDDecl->getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
+
+ if (FixItLoc.isValid() && !FixItLoc.isMacroID())
+ DB << FixItHint::CreateInsertion(FixItLoc, " [[clang::lifetimebound]]");
+ }
S.Diag(Attr->getLocation(), diag::note_lifetime_safety_lifetimebound_here)
<< Attr->getRange();
diff --git a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-cross-tu.cpp b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-cross-tu.cpp
index 5fd49023a042a..c763f4a422943 100644
--- a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-cross-tu.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-cross-tu.cpp
@@ -1,6 +1,7 @@
// RUN: rm -rf %t
// RUN: split-file %s %t
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-cross-tu-misplaced-lifetimebound -Wno-dangling -I%t -verify %t/cross.cpp
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-cross-tu-misplaced-lifetimebound -Wno-dangling -I%t -fdiagnostics-parseable-fixits %t/cross.cpp 2>&1 | FileCheck %s
//--- cross.h
struct HeaderObj {
@@ -8,10 +9,12 @@ struct HeaderObj {
};
HeaderObj &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}}
+// CHECK: fix-it:"{{.*}}cross.h":{[[#]]:{{[0-9]+}}-[[#]]:{{[0-9]+}}}:" {{\[\[}}clang::lifetimebound{{\]\]}}"
struct HeaderS {
HeaderObj data;
HeaderObj &header_this(); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers in other translation units; add it to the declaration instead}}
+ // CHECK: fix-it:"{{.*}}cross.h":{[[#]]:{{[0-9]+}}-[[#]]:{{[0-9]+}}}:" {{\[\[}}clang::lifetimebound{{\]\]}}"
};
//--- cross.cpp
diff --git a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp
index caf805cea5416..29ab6244d7826 100644
--- a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp
@@ -1,10 +1,13 @@
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-intra-tu-misplaced-lifetimebound -Wno-dangling -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-intra-tu-misplaced-lifetimebound -Wno-dangling -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
struct MyObj {
~MyObj() {}
};
MyObj &free_param(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\]\]}}"
+
MyObj &free_param(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}}
return obj;
}
@@ -12,8 +15,14 @@ MyObj &free_param(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lif
struct S {
MyObj data;
const MyObj &implicit_this_only(); // 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\]\]}}"
+
const MyObj ¶m_only(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\]\]}}"
+
const MyObj &both(const MyObj &obj, bool); // expected-warning 2 {{'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\]\]}}"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:{{[0-9]+}}-[[@LINE-2]]:{{[0-9]+}}}:" {{\[\[clang::lifetimebound\]\]}}"
};
const MyObj &S::implicit_this_only() [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute appears here on the definition}}
@@ -35,6 +44,8 @@ template <class T>
struct MixedSpecializations {
T data;
T &both(T &arg, bool); // expected-warning 2 {{'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\]\]}}"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:{{[0-9]+}}-[[@LINE-2]]:{{[0-9]+}}}:" {{\[\[clang::lifetimebound\]\]}}"
};
template <>
@@ -50,6 +61,8 @@ struct InternalObj {
namespace {
InternalObj &anon_param(InternalObj &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\]\]}}"
+
InternalObj &anon_param(InternalObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}}
return obj;
}
@@ -57,6 +70,7 @@ InternalObj &anon_param(InternalObj &obj [[clang::lifetimebound]]) { // expected
struct AnonS {
InternalObj data;
InternalObj &anon_this(); // 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\]\]}}"
};
InternalObj &AnonS::anon_this() [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute appears here on the definition}}
@@ -65,6 +79,8 @@ InternalObj &AnonS::anon_this() [[clang::lifetimebound]] { // expected-note {{'l
} // namespace
static InternalObj &static_param(InternalObj &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\]\]}}"
+
static InternalObj &static_param(InternalObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}}
return obj;
}
@@ -74,6 +90,8 @@ struct IntraSuppressedObj {
};
IntraSuppressedObj &intra_suppressed(IntraSuppressedObj &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\]\]}}"
+
IntraSuppressedObj &intra_suppressed(
IntraSuppressedObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}}
return obj;
@@ -81,11 +99,11 @@ IntraSuppressedObj &intra_suppressed(
struct View {
friend View friend_redecl(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\]\]}}"
};
// FIXME: This diagnoses an attribute inherited from another redeclaration, not one written on the definition. Once we enforce that redeclarations agree on lifetimebound, handle this with a dedicated warning and note.
View friend_redecl(MyObj &obj [[clang::lifetimebound]]); // expected-note {{'lifetimebound' attribute appears here on the definition}}
-
View friend_redecl(MyObj &obj) {
return View{};
}
@@ -93,6 +111,7 @@ 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\]\]}}"
template <>
// FIXME: Attribute is inhetired, diagnostic's wording is not correct.
@@ -100,3 +119,34 @@ MyObj &spec_func<MyObj>(MyObj &obj [[clang::lifetimebound]]); // expected-note {
template <>
MyObj &spec_func<MyObj>(MyObj &obj) { return obj; }
+
+MyObj get_default_obj();
+
+const MyObj &default_arg_param(const MyObj &obj = get_default_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]]:48-[[@LINE-1]]:48}:" {{\[\[clang::lifetimebound\]\]}}"
+
+const MyObj &default_arg_param(const MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}}
+ return obj;
+}
+
+struct Base {
+ virtual const MyObj& virtual_get(const MyObj& obj) const = 0;
+};
+
+struct Derived : Base {
+ auto virtual_get(const MyObj& obj) const -> const MyObj& override; // 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]]:36-[[@LINE-1]]:36}:" {{\[\[clang::lifetimebound\]\]}}"
+};
+
+auto Derived::virtual_get(const MyObj& obj [[clang::lifetimebound]]) const -> const MyObj& { // expected-note {{'lifetimebound' attribute appears here on the definition}}
+ return obj;
+}
+
+#define REF_PARAM MyObj &obj
+
+MyObj ¯o_param(REF_PARAM); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}}
+// Fix-it suppressed for macro.
+
+MyObj ¯o_param(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}}
+ return obj;
+}
>From 30b95ef40a91b8c6a615f24aae4a64af783ae9ee Mon Sep 17 00:00:00 2001
From: David Meng <davidmenggx at gmail.com>
Date: Fri, 22 May 2026 08:20:58 -0700
Subject: [PATCH 2/4] Apply NeKon69s feedback
---
clang/lib/Sema/SemaLifetimeSafety.h | 75 ++++++++++---------
...afety-misplaced-lifetimebound-cross-tu.cpp | 2 +
...afety-misplaced-lifetimebound-intra-tu.cpp | 5 +-
3 files changed, 45 insertions(+), 37 deletions(-)
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index eb1854548f97c..63898d496891e 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -298,25 +298,29 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
? diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound
: diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound;
- SourceLocation DiagLoc = Lexer::getLocForEndOfToken(
- FDecl->getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
-
- // Scope so diagnostic emits first.
- {
- auto DB = S.Diag(DiagLoc, DiagID);
-
- SourceLocation FixItLoc;
- if (const TypeSourceInfo *TSI = FDecl->getTypeSourceInfo())
- FixItLoc =
- Lexer::getLocForEndOfToken(TSI->getTypeLoc().getEndLoc(), 0,
- S.getSourceManager(), S.getLangOpts());
- else
- FixItLoc = DiagLoc;
-
- if (FixItLoc.isValid() && !FixItLoc.isMacroID())
- DB << FixItHint::CreateInsertion(FixItLoc, " [[clang::lifetimebound]]");
+ const auto MDL = FDecl->getTypeSourceInfo()->getTypeLoc();
+ SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
+ MDL.getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
+
+ if (const auto *FPT = FDecl->getType()->getAs<FunctionProtoType>();
+ FPT && FPT->hasTrailingReturn()) {
+ // For trailing return types, 'getEndLoc()' includes the return type
+ // after '->', placing the attribute in an invalid position.
+ // Instead use 'getLocalRangeEnd()' which gives the '->' location
+ // for trailing returns, so find the last token before it.
+ const auto FTL = MDL.getAs<FunctionTypeLoc>();
+ assert(FTL);
+ InsertionPoint = Lexer::getLocForEndOfToken(
+ Lexer::findPreviousToken(FTL.getLocalRangeEnd(), S.getSourceManager(),
+ S.getLangOpts(),
+ /*IncludeComments=*/false)
+ ->getLocation(),
+ 0, S.getSourceManager(), S.getLangOpts());
}
+ S.Diag(InsertionPoint, DiagID) << FixItHint::CreateInsertion(
+ InsertionPoint, " [[clang::lifetimebound]]");
+
S.Diag(Attr->getLocation(), diag::note_lifetime_safety_lifetimebound_here)
<< Attr->getRange();
}
@@ -332,27 +336,26 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
? diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound
: diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound;
- // Scope so diagnostic emits first.
- {
- auto DB = S.Diag(PVDDecl->getBeginLoc(), DiagID)
- << PVDDecl->getSourceRange();
-
- SourceLocation FixItLoc;
- if (PVDDecl->getIdentifier())
- FixItLoc = Lexer::getLocForEndOfToken(
- PVDDecl->getLocation(), 0, S.getSourceManager(), S.getLangOpts());
- else if (const TypeSourceInfo *TSI = PVDDecl->getTypeSourceInfo())
- FixItLoc =
- Lexer::getLocForEndOfToken(TSI->getTypeLoc().getEndLoc(), 0,
- S.getSourceManager(), S.getLangOpts());
- else
- FixItLoc = Lexer::getLocForEndOfToken(
- PVDDecl->getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
-
- if (FixItLoc.isValid() && !FixItLoc.isMacroID())
- DB << FixItHint::CreateInsertion(FixItLoc, " [[clang::lifetimebound]]");
+ SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
+ PVDDecl->getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
+ StringRef FixItText = " [[clang::lifetimebound]]";
+
+ if (!PVDDecl->getIdentifier()) {
+ // For unnamed parameters, placing attributes after the type would be
+ // parsed as a type attribute, not a parameter attribute.
+ InsertionPoint = PVDDecl->getBeginLoc();
+ FixItText = "[[clang::lifetimebound]] ";
+ } else if (PVDDecl->hasDefaultArg()) {
+ // If the parameter has a default argument, place the attribute after the
+ // named argument.
+ InsertionPoint = Lexer::getLocForEndOfToken(
+ PVDDecl->getLocation(), 0, S.getSourceManager(), S.getLangOpts());
}
+ S.Diag(PVDDecl->getBeginLoc(), DiagID)
+ << PVDDecl->getSourceRange()
+ << FixItHint::CreateInsertion(InsertionPoint, FixItText);
+
S.Diag(Attr->getLocation(), diag::note_lifetime_safety_lifetimebound_here)
<< Attr->getRange();
}
diff --git a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-cross-tu.cpp b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-cross-tu.cpp
index c763f4a422943..d227b8d8656c7 100644
--- a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-cross-tu.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-cross-tu.cpp
@@ -2,6 +2,8 @@
// RUN: split-file %s %t
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-cross-tu-misplaced-lifetimebound -Wno-dangling -I%t -verify %t/cross.cpp
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-cross-tu-misplaced-lifetimebound -Wno-dangling -I%t -fdiagnostics-parseable-fixits %t/cross.cpp 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -Wlifetime-safety-cross-tu-misplaced-lifetimebound -Wno-dangling -I%t -fixit %t/cross.cpp
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-cross-tu-misplaced-lifetimebound -Wno-dangling -I%t -Werror %t/cross.cpp
//--- cross.h
struct HeaderObj {
diff --git a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp
index 29ab6244d7826..9ee15ecac9ae3 100644
--- a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp
@@ -1,5 +1,8 @@
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-intra-tu-misplaced-lifetimebound -Wno-dangling -verify %s
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-intra-tu-misplaced-lifetimebound -Wno-dangling -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+// RUN: cp %s %t.intra.cpp
+// RUN: %clang_cc1 -Wlifetime-safety-intra-tu-misplaced-lifetimebound -Wno-dangling -fixit %t.intra.cpp
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-intra-tu-misplaced-lifetimebound -Wno-dangling -Werror %t.intra.cpp
struct MyObj {
~MyObj() {}
@@ -145,7 +148,7 @@ auto Derived::virtual_get(const MyObj& obj [[clang::lifetimebound]]) const -> co
#define REF_PARAM MyObj &obj
MyObj ¯o_param(REF_PARAM); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}}
-// Fix-it suppressed for macro.
+// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:{{[0-9]+}}-[[@LINE-1]]:{{[0-9]+}}}:" {{\[\[clang::lifetimebound\]\]}}"
MyObj ¯o_param(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}}
return obj;
>From f14a811ba3ce5a9546aa818a3636b25f15e9837b Mon Sep 17 00:00:00 2001
From: David Meng <davidmenggx at gmail.com>
Date: Fri, 22 May 2026 10:40:11 -0700
Subject: [PATCH 3/4] Extract fix-it text and location into helper functions
---
clang/lib/Sema/SemaLifetimeSafety.h | 127 ++++++++++++----------------
1 file changed, 56 insertions(+), 71 deletions(-)
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index 63898d496891e..c075829cef92e 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -239,21 +239,9 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
(Scope == WarningScope::CrossTU)
? diag::warn_lifetime_safety_cross_tu_param_suggestion
: diag::warn_lifetime_safety_intra_tu_param_suggestion;
- SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
- ParmToAnnotate->getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
- StringRef FixItText = " [[clang::lifetimebound]]";
- if (!ParmToAnnotate->getIdentifier()) {
- // For unnamed parameters, placing attributes after the type would be
- // parsed as a type attribute, not a parameter attribute.
- InsertionPoint = ParmToAnnotate->getBeginLoc();
- FixItText = "[[clang::lifetimebound]] ";
- } else if (ParmToAnnotate->hasDefaultArg()) {
- // If the parameter has a default argument, place the attribute after the
- // named argument.
- InsertionPoint =
- Lexer::getLocForEndOfToken(ParmToAnnotate->getLocation(), 0,
- S.getSourceManager(), S.getLangOpts());
- }
+
+ auto [InsertionPoint, FixItText] = getLifetimeBoundFixIt(ParmToAnnotate);
+
S.Diag(ParmToAnnotate->getBeginLoc(), DiagID)
<< ParmToAnnotate->getSourceRange()
<< FixItHint::CreateInsertion(InsertionPoint, FixItText);
@@ -298,28 +286,10 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
? diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound
: diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound;
- const auto MDL = FDecl->getTypeSourceInfo()->getTypeLoc();
- SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
- MDL.getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
+ auto [InsertionPoint, FixItText] = getLifetimeBoundFixIt(FDecl);
- if (const auto *FPT = FDecl->getType()->getAs<FunctionProtoType>();
- FPT && FPT->hasTrailingReturn()) {
- // For trailing return types, 'getEndLoc()' includes the return type
- // after '->', placing the attribute in an invalid position.
- // Instead use 'getLocalRangeEnd()' which gives the '->' location
- // for trailing returns, so find the last token before it.
- const auto FTL = MDL.getAs<FunctionTypeLoc>();
- assert(FTL);
- InsertionPoint = Lexer::getLocForEndOfToken(
- Lexer::findPreviousToken(FTL.getLocalRangeEnd(), S.getSourceManager(),
- S.getLangOpts(),
- /*IncludeComments=*/false)
- ->getLocation(),
- 0, S.getSourceManager(), S.getLangOpts());
- }
-
- S.Diag(InsertionPoint, DiagID) << FixItHint::CreateInsertion(
- InsertionPoint, " [[clang::lifetimebound]]");
+ S.Diag(InsertionPoint, DiagID)
+ << FixItHint::CreateInsertion(InsertionPoint, FixItText);
S.Diag(Attr->getLocation(), diag::note_lifetime_safety_lifetimebound_here)
<< Attr->getRange();
@@ -336,21 +306,7 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
? diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound
: diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound;
- SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
- PVDDecl->getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
- StringRef FixItText = " [[clang::lifetimebound]]";
-
- if (!PVDDecl->getIdentifier()) {
- // For unnamed parameters, placing attributes after the type would be
- // parsed as a type attribute, not a parameter attribute.
- InsertionPoint = PVDDecl->getBeginLoc();
- FixItText = "[[clang::lifetimebound]] ";
- } else if (PVDDecl->hasDefaultArg()) {
- // If the parameter has a default argument, place the attribute after the
- // named argument.
- InsertionPoint = Lexer::getLocForEndOfToken(
- PVDDecl->getLocation(), 0, S.getSourceManager(), S.getLangOpts());
- }
+ auto [InsertionPoint, FixItText] = getLifetimeBoundFixIt(PVDDecl);
S.Diag(PVDDecl->getBeginLoc(), DiagID)
<< PVDDecl->getSourceRange()
@@ -366,28 +322,13 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
unsigned DiagID = (Scope == WarningScope::CrossTU)
? diag::warn_lifetime_safety_cross_tu_this_suggestion
: diag::warn_lifetime_safety_intra_tu_this_suggestion;
- const auto MDL = MD->getTypeSourceInfo()->getTypeLoc();
- SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
- MDL.getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
- if (const auto *FPT = MD->getType()->getAs<FunctionProtoType>();
- FPT && FPT->hasTrailingReturn()) {
- // For trailing return types, 'getEndLoc()' includes the return type
- // after '->', placing the attribute in an invalid position.
- // Instead use 'getLocalRangeEnd()' which gives the '->' location
- // for trailing returns, so find the last token before it.
- const auto FTL = MDL.getAs<FunctionTypeLoc>();
- assert(FTL);
- InsertionPoint = Lexer::getLocForEndOfToken(
- Lexer::findPreviousToken(FTL.getLocalRangeEnd(), S.getSourceManager(),
- S.getLangOpts(),
- /*IncludeComments=*/false)
- ->getLocation(),
- 0, S.getSourceManager(), S.getLangOpts());
- }
+
+ auto [InsertionPoint, FixItText] = getLifetimeBoundFixIt(MD);
+
S.Diag(InsertionPoint, DiagID)
<< MD->getNameInfo().getSourceRange()
- << FixItHint::CreateInsertion(InsertionPoint,
- " [[clang::lifetimebound]]");
+ << FixItHint::CreateInsertion(InsertionPoint, FixItText);
+
S.Diag(EscapeExpr->getBeginLoc(),
diag::note_lifetime_safety_suggestion_returned_here)
<< EscapeExpr->getSourceRange();
@@ -436,6 +377,50 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
private:
Sema &S;
+
+ std::pair<SourceLocation, StringRef>
+ getLifetimeBoundFixIt(const ParmVarDecl *Decl) {
+ SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
+ Decl->getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
+ StringRef FixItText = " [[clang::lifetimebound]]";
+
+ if (!Decl->getIdentifier()) {
+ // For unnamed parameters, placing attributes after the type would be
+ // parsed as a type attribute, not a parameter attribute.
+ InsertionPoint = Decl->getBeginLoc();
+ FixItText = "[[clang::lifetimebound]] ";
+ } else if (Decl->hasDefaultArg()) {
+ // If the parameter has a default argument, place the attribute after the
+ // named argument.
+ InsertionPoint = Lexer::getLocForEndOfToken(
+ Decl->getLocation(), 0, S.getSourceManager(), S.getLangOpts());
+ }
+ return {InsertionPoint, FixItText};
+ }
+
+ std::pair<SourceLocation, StringRef>
+ getLifetimeBoundFixIt(const CXXMethodDecl *MD) {
+ const auto MDL = MD->getTypeSourceInfo()->getTypeLoc();
+ SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
+ MDL.getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
+
+ if (const auto *FPT = MD->getType()->getAs<FunctionProtoType>();
+ FPT && FPT->hasTrailingReturn()) {
+ // For trailing return types, 'getEndLoc()' includes the return type
+ // after '->', placing the attribute in an invalid position.
+ // Instead use 'getLocalRangeEnd()' which gives the '->' location
+ // for trailing returns, so find the last token before it.
+ const auto FTL = MDL.getAs<FunctionTypeLoc>();
+ assert(FTL);
+ InsertionPoint = Lexer::getLocForEndOfToken(
+ Lexer::findPreviousToken(FTL.getLocalRangeEnd(), S.getSourceManager(),
+ S.getLangOpts(),
+ /*IncludeComments=*/false)
+ ->getLocation(),
+ 0, S.getSourceManager(), S.getLangOpts());
+ }
+ return {InsertionPoint, " [[clang::lifetimebound]]"};
+ }
};
} // namespace clang::lifetimes
>From 031c191e25f7be0f0b9166d31a35b00fc6a91c06 Mon Sep 17 00:00:00 2001
From: David Meng <davidmenggx at gmail.com>
Date: Mon, 25 May 2026 07:50:32 -0700
Subject: [PATCH 4/4] Apply usx95s feedback
---
clang/lib/Sema/SemaLifetimeSafety.h | 4 ++--
...etime-safety-misplaced-lifetimebound-intra-tu.cpp | 12 +++++++-----
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index c075829cef92e..7a3baa9adf2e3 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -376,8 +376,6 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
}
private:
- Sema &S;
-
std::pair<SourceLocation, StringRef>
getLifetimeBoundFixIt(const ParmVarDecl *Decl) {
SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
@@ -421,6 +419,8 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
}
return {InsertionPoint, " [[clang::lifetimebound]]"};
}
+
+ Sema &S;
};
} // namespace clang::lifetimes
diff --git a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp
index 9ee15ecac9ae3..801e10e289f31 100644
--- a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp
@@ -107,6 +107,7 @@ struct View {
// FIXME: This diagnoses an attribute inherited from another redeclaration, not one written on the definition. Once we enforce that redeclarations agree on lifetimebound, handle this with a dedicated warning and note.
View friend_redecl(MyObj &obj [[clang::lifetimebound]]); // expected-note {{'lifetimebound' attribute appears here on the definition}}
+
View friend_redecl(MyObj &obj) {
return View{};
}
@@ -124,9 +125,9 @@ template <>
MyObj &spec_func<MyObj>(MyObj &obj) { return obj; }
MyObj get_default_obj();
-
-const MyObj &default_arg_param(const MyObj &obj = get_default_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]]:48-[[@LINE-1]]:48}:" {{\[\[clang::lifetimebound\]\]}}"
+const MyObj &default_arg_param(const MyObj&
+ obj // CHECK: fix-it:"{{.*}}":{[[@LINE]]:{{[0-9]+}}-[[@LINE]]:{{[0-9]+}}}:" {{\[\[clang::lifetimebound\]\]}}"
+ = get_default_obj()); // expected-warning at -2 {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}}
const MyObj &default_arg_param(const MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}}
return obj;
@@ -137,8 +138,9 @@ struct Base {
};
struct Derived : Base {
- auto virtual_get(const MyObj& obj) const -> const MyObj& override; // 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]]:36-[[@LINE-1]]:36}:" {{\[\[clang::lifetimebound\]\]}}"
+ auto virtual_get(const MyObj&
+ obj // CHECK: fix-it:"{{.*}}":{[[@LINE]]:{{[0-9]+}}-[[@LINE]]:{{[0-9]+}}}:" {{\[\[clang::lifetimebound\]\]}}"
+ ) const -> const MyObj& override; // expected-warning at -2 {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}}
};
auto Derived::virtual_get(const MyObj& obj [[clang::lifetimebound]]) const -> const MyObj& { // expected-note {{'lifetimebound' attribute appears here on the definition}}
More information about the cfe-commits
mailing list