Author: Zeyi Xu Date: 2026-06-20T11:23:47+08:00 New Revision: 359bfe62ed1b1cd498cee558ed8699b2b88ae58b
URL: https://github.com/llvm/llvm-project/commit/359bfe62ed1b1cd498cee558ed8699b2b88ae58b DIFF: https://github.com/llvm/llvm-project/commit/359bfe62ed1b1cd498cee558ed8699b2b88ae58b.diff LOG: [LifetimeSafety] Allow configuring lifetimebound fix-it spelling (#204045) When suggesting `[[clang::lifetimebound]]` fix-its, allow users to provide a project-specific macro spelling with `-lifetime-safety-lifetimebound-macro=...`. If no spelling is configured, use a visible macro whose replacement tokens spell the attribute, preferring the most recently defined matching macro, and fall back to `[[clang::lifetimebound]]` or `__attribute((lifetimebound))` otherwise. Closes https://github.com/llvm/llvm-project/issues/200232 Added: Modified: clang/docs/LifetimeSafety.rst clang/include/clang/Basic/LangOptions.h clang/include/clang/Options/Options.td clang/lib/Sema/SemaLifetimeSafety.h clang/test/Sema/LifetimeSafety/annotation-suggestions-fixits.cpp clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp Removed: ################################################################################ diff --git a/clang/docs/LifetimeSafety.rst b/clang/docs/LifetimeSafety.rst index 9ae2e6ee54826..be9b32f8d4b2d 100644 --- a/clang/docs/LifetimeSafety.rst +++ b/clang/docs/LifetimeSafety.rst @@ -462,6 +462,12 @@ more accurate checks in calling code. To enable annotation suggestions, use ``-Wlifetime-safety-suggestions``. +Fix-it hints normally insert ``[[clang::lifetimebound]]``. If a visible +object-like macro expands to ``[[clang::lifetimebound]]`` or +``__attribute__((lifetimebound))``, Clang will use the last such macro +visible at the insertion point. To force a project-specific macro spelling, +use ``-lifetime-safety-lifetimebound-macro=<macro>``. + .. code-block:: c++ #include <string_view> @@ -688,5 +694,5 @@ Performance Lifetime analysis relies on Clang's CFG (Control Flow Graph). For functions with very large or complex CFGs, analysis time can sometimes be significant. To mitigate this, the analysis allows to skip functions where the number of CFG blocks exceeds -a certain threshold, controlled by the ``-flifetime-safety-max-cfg-blocks=N`` language +a certain threshold, controlled by the ``-lifetime-safety-max-cfg-blocks=N`` language option. diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h index 9af036156b1ad..53c4c1084784a 100644 --- a/clang/include/clang/Basic/LangOptions.h +++ b/clang/include/clang/Basic/LangOptions.h @@ -549,6 +549,9 @@ class LangOptions : public LangOptionsBase { /// A prefix map for __FILE__, __BASE_FILE__ and __builtin_FILE(). std::map<std::string, std::string, std::greater<std::string>> MacroPrefixMap; + /// Macro name to use in lifetimebound fix-it suggestions. + std::string LifetimeSafetyLifetimeBoundMacro; + /// Triples of the OpenMP targets that the host code codegen should /// take into account in order to generate accurate offloading descriptors. std::vector<llvm::Triple> OMPTargetTriples; diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 5028684731b2d..c04280ca25528 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -2032,6 +2032,15 @@ def lifetime_safety_max_cfg_blocks "count exceeding this threshold. Specify 0 for no limit.">, MarshallingInfoInt<LangOpts<"LifetimeSafetyMaxCFGBlocks">>; +def lifetime_safety_lifetimebound_macro + : Joined<["-"], "lifetime-safety-lifetimebound-macro=">, + Group<m_Group>, + Visibility<[ClangOption, CC1Option]>, + MetaVarName<"<macro>">, + HelpText<"Use the given macro name when suggesting lifetimebound " + "attributes">, + MarshallingInfoString<LangOpts<"LifetimeSafetyLifetimeBoundMacro">>; + defm lifetime_safety_inference : BoolFOption<"lifetime-safety-inference", LangOpts<"EnableLifetimeSafetyInference">, DefaultFalse, diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index a8bde363e3397..4bde272fb40a1 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -19,6 +19,7 @@ #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" #include "clang/Sema/Sema.h" #include <string> @@ -441,27 +442,49 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { } private: - std::pair<SourceLocation, StringRef> + std::string getLifetimeBoundFixItText(SourceLocation Loc, bool LeadingSpace, + bool AllowGNUAttrMacro = true) { + StringRef Spelling = S.getLangOpts().LifetimeSafetyLifetimeBoundMacro; + if (Spelling.empty() && Loc.isValid()) { + const Preprocessor &PP = S.getPreprocessor(); + Spelling = PP.getLastMacroWithSpelling( + Loc, {tok::l_square, tok::l_square, PP.getIdentifierInfo("clang"), + tok::coloncolon, PP.getIdentifierInfo("lifetimebound"), + tok::r_square, tok::r_square}); + + if (Spelling.empty() && AllowGNUAttrMacro) + Spelling = PP.getLastMacroWithSpelling( + Loc, {tok::kw___attribute, tok::l_paren, tok::l_paren, + PP.getIdentifierInfo("lifetimebound"), tok::r_paren, + tok::r_paren}); + } + const std::string Text = + Spelling.empty() ? "[[clang::lifetimebound]]" : Spelling.str(); + return LeadingSpace ? " " + Text : Text + " "; + } + + std::pair<SourceLocation, std::string> getLifetimeBoundFixIt(const ParmVarDecl *Decl) { SourceLocation InsertionPoint = Lexer::getLocForEndOfToken( Decl->getEndLoc(), 0, S.getSourceManager(), S.getLangOpts()); - StringRef FixItText = " [[clang::lifetimebound]]"; + bool LeadingSpace = true; 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]] "; + LeadingSpace = false; } 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}; + return {InsertionPoint, + getLifetimeBoundFixItText(InsertionPoint, LeadingSpace)}; } - std::pair<SourceLocation, StringRef> + std::pair<SourceLocation, std::string> getLifetimeBoundFixIt(const CXXMethodDecl *MD) { const auto MDL = MD->getTypeSourceInfo()->getTypeLoc(); SourceLocation InsertionPoint = Lexer::getLocForEndOfToken( @@ -482,7 +505,9 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { ->getLocation(), 0, S.getSourceManager(), S.getLangOpts()); } - return {InsertionPoint, " [[clang::lifetimebound]]"}; + return {InsertionPoint, + getLifetimeBoundFixItText(InsertionPoint, /*LeadingSpace=*/true, + /*AllowGNUAttrMacro=*/false)}; } std::string getDiagSubjectDescription(const ValueDecl *VD) { diff --git a/clang/test/Sema/LifetimeSafety/annotation-suggestions-fixits.cpp b/clang/test/Sema/LifetimeSafety/annotation-suggestions-fixits.cpp index 18be627211975..99f0d16cd8e68 100644 --- a/clang/test/Sema/LifetimeSafety/annotation-suggestions-fixits.cpp +++ b/clang/test/Sema/LifetimeSafety/annotation-suggestions-fixits.cpp @@ -2,6 +2,12 @@ // RUN: -fexperimental-lifetime-safety-tu-analysis \ // RUN: -Wlifetime-safety-suggestions -Wlifetime-safety-annotation-placement -Wno-dangling \ // RUN: -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s +// RUN: %clang_cc1 -fsyntax-only -std=c++17 -flifetime-safety-inference \ +// RUN: -fexperimental-lifetime-safety-tu-analysis \ +// RUN: -Wlifetime-safety-suggestions -Wlifetime-safety-annotation-placement -Wno-dangling \ +// RUN: -DLIFETIMEBOUND_MACRO=[[clang::lifetimebound]] \ +// RUN: -lifetime-safety-lifetimebound-macro=LIFETIMEBOUND_MACRO \ +// RUN: -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s --check-prefix=CHECK-MACRO // RUN: cp %s %t.cpp // RUN: %clang_cc1 -std=c++17 -flifetime-safety-inference \ // RUN: -fexperimental-lifetime-safety-tu-analysis \ @@ -9,6 +15,14 @@ // RUN: %clang_cc1 -fsyntax-only -std=c++17 -flifetime-safety-inference \ // RUN: -fexperimental-lifetime-safety-tu-analysis \ // RUN: -Werror=lifetime-safety-suggestions -Wno-dangling %t.cpp +// RUN: cp %s %t.bad-macro.cpp +// RUN: %clang_cc1 -std=c++17 -flifetime-safety-inference \ +// RUN: -fexperimental-lifetime-safety-tu-analysis \ +// RUN: -Wlifetime-safety-suggestions -Wno-dangling \ +// RUN: -lifetime-safety-lifetimebound-macro=BAD_LIFETIMEBOUND_MACRO \ +// RUN: -fixit %t.bad-macro.cpp +// RUN: not %clang_cc1 -fsyntax-only -std=c++17 %t.bad-macro.cpp 2>&1 | \ +// RUN: FileCheck %s --check-prefix=CHECK-BAD-MACRO struct View; @@ -30,6 +44,10 @@ struct [[gsl::Pointer()]] View { View return_view(View a) { // CHECK: :[[@LINE-1]]:18: warning: parameter in intra-TU function should be marked {{\[\[}}clang::lifetimebound]] [-Wlifetime-safety-intra-tu-suggestions] // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:24-[[@LINE-2]]:24}:" {{\[\[}}clang::lifetimebound]]" + // CHECK-MACRO: :[[@LINE-3]]:18: warning: parameter in intra-TU function should be marked + // CHECK-MACRO: fix-it:"{{.*}}":{[[@LINE-4]]:24-[[@LINE-4]]:24}:" LIFETIMEBOUND_MACRO" + // CHECK-BAD-MACRO: :[[@LINE-5]]:25: error: expected ')' + // CHECK-BAD-MACRO: BAD_LIFETIMEBOUND_MACRO return a; } @@ -97,6 +115,7 @@ struct ViewMember { View get_view() { // CHECK: :[[@LINE-1]]:18: warning: implicit this in intra-TU function should be marked // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:18-[[@LINE-2]]:18}:" {{\[\[}}clang::lifetimebound]]" + // CHECK-BAD-MACRO: :[[@LINE-3]]:18: error: expected ';' at end of declaration list return data; } @@ -174,3 +193,60 @@ struct TrailingReturn { return data; } }; + +#define GNU_LIFETIMEBOUND_MACRO __attribute__((lifetimebound)) + +View return_view_with_gnu_macro(View a) { + // CHECK: :[[@LINE-1]]:33: warning: parameter in intra-TU function should be marked + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:39-[[@LINE-2]]:39}:" GNU_LIFETIMEBOUND_MACRO" + return a; +} + +struct OnlyGNUMember { + MyObj data; + + View get_view() { + // CHECK: :[[@LINE-1]]:18: warning: implicit this in intra-TU function should be marked + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:18-[[@LINE-2]]:18}:" {{\[\[}}clang::lifetimebound]]" + return data; + } +}; + +#define LIFETIMEBOUND_MACRO [[clang::lifetimebound]] +#define MY_LIFETIMEBOUND_MACRO [[clang::lifetimebound]] + +View unnamed_macro(View); +// CHECK: :[[@LINE-1]]:20: warning: parameter in intra-TU function should be marked +// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:20-[[@LINE-2]]:20}:"MY_LIFETIMEBOUND_MACRO " +View unnamed_macro(View a) { + return a; +} + +View return_view_with_macro(View a) { + // CHECK: :[[@LINE-1]]:29: warning: parameter in intra-TU function should be marked + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:35-[[@LINE-2]]:35}:" MY_LIFETIMEBOUND_MACRO" + return a; +} + +#define FIRST_LIFETIMEBOUND_MACRO [[clang::lifetimebound]] +#define SECOND_LIFETIMEBOUND_MACRO [[clang::lifetimebound]] + +View return_view_with_latest_macro(View a) { + // CHECK: :[[@LINE-1]]:36: warning: parameter in intra-TU function should be marked + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:42-[[@LINE-2]]:42}:" SECOND_LIFETIMEBOUND_MACRO" + // CHECK-MACRO: :[[@LINE-3]]:36: warning: parameter in intra-TU function should be marked + // CHECK-MACRO: fix-it:"{{.*}}":{[[@LINE-4]]:42-[[@LINE-4]]:42}:" LIFETIMEBOUND_MACRO" + return a; +} + +struct MacroMember { + MyObj data; + + View get_view() { + // CHECK: :[[@LINE-1]]:18: warning: implicit this in intra-TU function should be marked + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:18-[[@LINE-2]]:18}:" SECOND_LIFETIMEBOUND_MACRO" + // CHECK-MACRO: :[[@LINE-3]]:18: warning: implicit this in intra-TU function should be marked + // CHECK-MACRO: fix-it:"{{.*}}":{[[@LINE-4]]:18-[[@LINE-4]]:18}:" LIFETIMEBOUND_MACRO" + 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..25f5b6e94c28c 100644 --- a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp +++ b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp @@ -1,5 +1,7 @@ // RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-intra-tu-misplaced-lifetimebound -Wlifetime-safety-annotation-placement -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: %clang_cc1 -fsyntax-only -Wlifetime-safety-intra-tu-misplaced-lifetimebound -Wno-dangling -lifetime-safety-lifetimebound-macro=CONFIGURED_LIFETIMEBOUND_MACRO \ +// RUN: -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s --check-prefix=CHECK-CONFIG // 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 @@ -29,11 +31,11 @@ struct S { 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 & // 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\]\]}}" ); - + const MyObj &both(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-DAG: fix-it:"{{.*}}":{[[@LINE]]:{{[0-9]+}}-[[@LINE]]:{{[0-9]+}}}:" {{\[\[clang::lifetimebound\]\]}}" bool @@ -167,3 +169,48 @@ struct Derived : Base { auto Derived::virtual_get(const MyObj& obj [[clang::lifetimebound]]) const -> const MyObj& { // expected-note {{'lifetimebound' attribute appears here on the definition}} return obj; } + +#define GNU_LIFETIMEBOUND_MACRO __attribute__((lifetimebound)) + +MyObj &gnu_macro_param(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]+}}}:" GNU_LIFETIMEBOUND_MACRO" + ); + +MyObj &gnu_macro_param(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + +struct OnlyGNUMember { + MyObj data; + const MyObj &only_gnu_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\]\]}}" +}; + +const MyObj &OnlyGNUMember::only_gnu_this() [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute appears here on the definition}} + return data; +} + +#define CONFIGURED_LIFETIMEBOUND_MACRO [[clang::lifetimebound]] +#define LATEST_VISIBLE_LIFETIMEBOUND_MACRO [[clang::lifetimebound]] + +MyObj &configured_macro_param(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]+}}}:" LATEST_VISIBLE_LIFETIMEBOUND_MACRO" + // CHECK-CONFIG: fix-it:"{{.*}}":{[[@LINE-1]]:{{[0-9]+}}-[[@LINE-1]]:{{[0-9]+}}}:" CONFIGURED_LIFETIMEBOUND_MACRO" + ); + +MyObj &configured_macro_param(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + +struct ConfiguredMacroMember { + MyObj data; + const MyObj &configured_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]+}}}:" LATEST_VISIBLE_LIFETIMEBOUND_MACRO" + // CHECK-CONFIG: fix-it:"{{.*}}":{[[@LINE-2]]:{{[0-9]+}}-[[@LINE-2]]:{{[0-9]+}}}:" CONFIGURED_LIFETIMEBOUND_MACRO" +}; + +const MyObj &ConfiguredMacroMember::configured_this() [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute appears here on the definition}} + return data; +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
