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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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@-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@-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}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
