https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/197753
>From 297fbacd610b0ec800173109bd1713e59e72697d Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Thu, 14 May 2026 19:50:21 +0300 Subject: [PATCH 1/7] add implementation --- .../LifetimeSafety/LifetimeAnnotations.h | 5 +++ .../Analyses/LifetimeSafety/LifetimeSafety.h | 10 +++++ clang/include/clang/Basic/DiagnosticGroups.td | 9 ++++- .../clang/Basic/DiagnosticSemaKinds.td | 6 +++ clang/lib/Analysis/LifetimeSafety/Checker.cpp | 23 +++++++++++ .../LifetimeSafety/LifetimeAnnotations.cpp | 13 ++++-- clang/lib/Sema/SemaLifetimeSafety.h | 27 +++++++++++++ ...ifetime-safety-misplaced-lifetimebound.cpp | 40 +++++++++++++++++++ 8 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h index f418f8a5132ec..ec05e67b05853 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h @@ -41,6 +41,11 @@ bool isNormalAssignmentOperator(const FunctionDecl *FD); /// has the lifetimebound attribute. bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD); +/// Returns the lifetimebound attribute for the implicit this parameter, if it +/// exists on the current type. +const LifetimeBoundAttr * +getDirectImplicitObjectLifetimeBoundAttr(const FunctionDecl *FD); + /// Returns the lifetimebound attribute for the implicit this parameter, if it /// exists on any redeclaration. const LifetimeBoundAttr * diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 3631b990eb542..132d20e9ed5f7 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -130,6 +130,16 @@ class LifetimeSafetySemaHelper { virtual void reportLifetimeboundViolation(const CXXMethodDecl *MDWithLifetimebound) {} + // Reports a member function definition that has [[clang::lifetimebound]] on + // the implicit this parameter when the previous declaration does not. + virtual void reportMisplacedLifetimebound(const FunctionDecl *FDef, + const FunctionDecl *FDecl) {} + + // Reports a function definition parameter that has [[clang::lifetimebound]] + // when the corresponding parameter in the previous declaration does not. + virtual void reportMisplacedLifetimebound(const ParmVarDecl *PVDDef, + const ParmVarDecl *PVDDecl) {} + // Suggests lifetime bound annotations for implicit this. virtual void suggestLifetimeboundToImplicitThis(SuggestionScope Scope, const CXXMethodDecl *MD, diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 03d423db9d21a..bf8973242cb3f 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -631,6 +631,12 @@ This warning may produce false-positives diagnostics when it cannot fully model }]; } +def LifetimeSafetyMisplacedLifetimebound : DiagGroup<"lifetime-safety-misplaced-lifetimebound"> { + code Documentation = [{ +Detects function definitions whose parameters or implicit this argument are marked as [[clang::lifetimebound]] when the corresponding declaration is not. +}]; +} + def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive", [LifetimeSafetyUseAfterScope, LifetimeSafetyReturnStackAddr, @@ -674,7 +680,8 @@ Detects misuse of [[clang::noescape]] annotation where the parameter escapes (fo def LifetimeSafetyValidations : DiagGroup<"lifetime-safety-validations", [LifetimeSafetyNoescape, - LifetimeSafetyLifetimeboundViolation]> { + LifetimeSafetyLifetimeboundViolation, + LifetimeSafetyMisplacedLifetimebound]> { code Documentation = [{ Verify function implementations adhere to the annotated lifetime contracts through lifetime safety like verifying [[clang::noescape]] and [[clang::lifetimebound]]. diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index d7dd20d6a45e4..5d41f751cb08a 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11018,6 +11018,11 @@ def warn_lifetime_safety_lifetimebound_violation InGroup<LifetimeSafetyLifetimeboundViolation>, DefaultIgnore; +def warn_lifetime_safety_misplaced_lifetimebound + : Warning<"'lifetimebound' attribute on the definition is not visible from this declaration; add it to the declaration instead">, + InGroup<LifetimeSafetyMisplacedLifetimebound>, + DefaultIgnore; + def note_lifetime_safety_used_here : Note<"later used here">; def note_lifetime_safety_invalidated_here : Note<"invalidated here">; def note_lifetime_safety_destroyed_here : Note<"destroyed here">; @@ -11030,6 +11035,7 @@ def note_lifetime_safety_dangling_static_here: Note<"this static storage dangles def note_lifetime_safety_escapes_to_field_here: Note<"escapes to this field">; def note_lifetime_safety_escapes_to_global_here: Note<"escapes to this global storage">; def note_lifetime_safety_escapes_to_static_storage_here: Note<"escapes to this static storage">; +def note_lifetime_safety_definition_lifetimebound_attribute_here: Note<"'lifetimebound' attribute written on the definition is here">; def warn_lifetime_safety_intra_tu_param_suggestion : Warning<"parameter in intra-TU function should be marked " diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index ad928c3754fea..2de413b4baa14 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -103,6 +103,7 @@ class LifetimeChecker { suggestAnnotations(); reportNoescapeViolations(); reportLifetimeboundViolations(); + reportMisplacedLifetimebound(); // Annotation inference is currently guarded by a frontend flag. In the // future, this might be replaced by a design that differentiates between // explicit and inferred findings with separate warning groups. @@ -415,6 +416,28 @@ class LifetimeChecker { } } + void reportMisplacedLifetimebound() { + const FunctionDecl *FDef = dyn_cast<FunctionDecl>(FD); + if (!FDef) + return; + + const FunctionDecl *FDecl = FDef->getPreviousDecl(); + if (!FDecl) + return; + + if (isa<CXXMethodDecl>(FDef) && + getDirectImplicitObjectLifetimeBoundAttr(FDef) && + !getDirectImplicitObjectLifetimeBoundAttr(FDecl)) + SemaHelper->reportMisplacedLifetimebound(FDef, FDecl); + + for (auto [PVDDef, PVDDecl] : + llvm::zip_equal(FDef->parameters(), FDecl->parameters())) { + if (PVDDef->hasAttr<LifetimeBoundAttr>() && + !PVDDecl->hasAttr<LifetimeBoundAttr>()) + SemaHelper->reportMisplacedLifetimebound(PVDDef, PVDDecl); + } + } + void inferAnnotations() { for (auto [Target, EscapeTarget] : AnnotationWarningsMap) { if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) { diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp index 393e558fd39c3..8f2b392fff514 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp @@ -72,6 +72,14 @@ getLifetimeBoundAttrFromFunctionType(const TypeSourceInfo &TSI) { return nullptr; } +const LifetimeBoundAttr * +getDirectImplicitObjectLifetimeBoundAttr(const FunctionDecl *FD) { + if (const TypeSourceInfo *TSI = FD->getTypeSourceInfo()) + if (const auto *Attr = getLifetimeBoundAttrFromFunctionType(*TSI)) + return Attr; + return nullptr; +} + const LifetimeBoundAttr * getImplicitObjectParamLifetimeBoundAttr(const FunctionDecl *FD) { FD = getDeclWithMergedLifetimeBoundAttrs(FD); @@ -79,9 +87,8 @@ getImplicitObjectParamLifetimeBoundAttr(const FunctionDecl *FD) { // 'this' param). We need to check all redeclarations. auto CheckRedecls = [](const FunctionDecl *F) -> const LifetimeBoundAttr * { for (const FunctionDecl *Redecl : F->redecls()) - if (const TypeSourceInfo *TSI = Redecl->getTypeSourceInfo()) - if (const auto *Attr = getLifetimeBoundAttrFromFunctionType(*TSI)) - return Attr; + if (const auto *Attr = getDirectImplicitObjectLifetimeBoundAttr(Redecl)) + return Attr; return nullptr; }; diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 1b5907a09e291..72d709e18d365 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -275,6 +275,33 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { << 2 << "" << Attr->getRange(); } + void reportMisplacedLifetimebound(const FunctionDecl *FDef, + const FunctionDecl *FDecl) override { + const auto *Attr = getImplicitObjectParamLifetimeBoundAttr(FDef); + assert(Attr && "Expected lifetimebound attribute"); + S.Diag(Lexer::getLocForEndOfToken(FDecl->getEndLoc(), 0, + S.getSourceManager(), S.getLangOpts()), + diag::warn_lifetime_safety_misplaced_lifetimebound); + + S.Diag(Attr->getLocation(), + diag::note_lifetime_safety_definition_lifetimebound_attribute_here) + << Attr->getRange(); + } + + void reportMisplacedLifetimebound(const ParmVarDecl *PVDDef, + const ParmVarDecl *PVDDecl) override { + + const auto *Attr = PVDDef->getAttr<LifetimeBoundAttr>(); + assert(Attr && "Expected lifetimebound attribute"); + S.Diag(PVDDecl->getBeginLoc(), + diag::warn_lifetime_safety_misplaced_lifetimebound) + << PVDDecl->getSourceRange(); + + S.Diag(Attr->getLocation(), + diag::note_lifetime_safety_definition_lifetimebound_attribute_here) + << Attr->getRange(); + } + void suggestLifetimeboundToImplicitThis(SuggestionScope Scope, const CXXMethodDecl *MD, const Expr *EscapeExpr) override { diff --git a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp new file mode 100644 index 0000000000000..30f5637604866 --- /dev/null +++ b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-validations -Wno-dangling -verify %s + +struct MyObj { + ~MyObj() {} +}; + +struct S { + MyObj data; + const MyObj &implicit_this_only(); // expected-warning {{'lifetimebound' attribute on the definition is not visible from this declaration; add it to the declaration instead}} + const MyObj ¶m_only(const MyObj &obj); // expected-warning {{'lifetimebound' attribute on the definition is not visible from this declaration; add it to the declaration instead}} + const MyObj &both(const MyObj &obj, bool); // expected-warning 2 {{'lifetimebound' attribute on the definition is not visible from this declaration; add it to the declaration instead}} +}; + +const MyObj &S::implicit_this_only() [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute written on the definition is here}} + return data; +} + +const MyObj &S::param_only( + const MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute written on the definition is here}} + return obj; +} + +const MyObj &S::both( + const MyObj &obj [[clang::lifetimebound]], bool use_obj) // expected-note {{'lifetimebound' attribute written on the definition is here}} + [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute written on the definition is here}} + return use_obj ? obj : data; +} + +template <class T> +struct MixedSpecializations { + T data; + T &both(T &arg, bool); // expected-warning 2 {{'lifetimebound' attribute on the definition is not visible from this declaration; add it to the declaration instead}} +}; + +template <> +MyObj &MixedSpecializations<MyObj>::both( + MyObj &arg [[clang::lifetimebound]], bool use_arg) // expected-note {{'lifetimebound' attribute written on the definition is here}} + [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute written on the definition is here}} + return use_arg ? arg : data; +} >From 6b3939822bce8f8b713c6690e4f30e95a3e8e35b Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Thu, 14 May 2026 19:57:49 +0300 Subject: [PATCH 2/7] change diagnostics a bit --- clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 +- .../Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 5d41f751cb08a..6615c44f411bc 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11019,7 +11019,7 @@ def warn_lifetime_safety_lifetimebound_violation DefaultIgnore; def warn_lifetime_safety_misplaced_lifetimebound - : Warning<"'lifetimebound' attribute on the definition is not visible from this declaration; add it to the declaration instead">, + : Warning<"'lifetimebound' attribute on the definition is not visible to callers; add it to this declaration instead">, InGroup<LifetimeSafetyMisplacedLifetimebound>, DefaultIgnore; diff --git a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp index 30f5637604866..6c0c1949931c9 100644 --- a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp +++ b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp @@ -6,9 +6,9 @@ struct MyObj { struct S { MyObj data; - const MyObj &implicit_this_only(); // expected-warning {{'lifetimebound' attribute on the definition is not visible from this declaration; add it to the declaration instead}} - const MyObj ¶m_only(const MyObj &obj); // expected-warning {{'lifetimebound' attribute on the definition is not visible from this declaration; add it to the declaration instead}} - const MyObj &both(const MyObj &obj, bool); // expected-warning 2 {{'lifetimebound' attribute on the definition is not visible from this declaration; add it to the declaration instead}} + const MyObj &implicit_this_only(); // expected-warning {{'lifetimebound' attribute on the definition is not visible to callers; add it to this declaration instead}} + const MyObj ¶m_only(const MyObj &obj); // expected-warning {{'lifetimebound' attribute on the definition is not visible to callers; add it to this declaration instead}} + const MyObj &both(const MyObj &obj, bool); // expected-warning 2 {{'lifetimebound' attribute on the definition is not visible to callers; add it to this declaration instead}} }; const MyObj &S::implicit_this_only() [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute written on the definition is here}} @@ -29,7 +29,7 @@ const MyObj &S::both( template <class T> struct MixedSpecializations { T data; - T &both(T &arg, bool); // expected-warning 2 {{'lifetimebound' attribute on the definition is not visible from this declaration; add it to the declaration instead}} + T &both(T &arg, bool); // expected-warning 2 {{'lifetimebound' attribute on the definition is not visible to callers; add it to this declaration instead}} }; template <> >From 3808e5511660d05cc2956c8c5e9197a76dd18571 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Fri, 15 May 2026 20:51:56 +0300 Subject: [PATCH 3/7] add cross-tu/intra-tu warnings --- .../LifetimeSafety/LifetimeAnnotations.h | 4 + .../Analyses/LifetimeSafety/LifetimeSafety.h | 18 ++-- clang/include/clang/Basic/DiagnosticGroups.td | 9 +- .../clang/Basic/DiagnosticSemaKinds.td | 13 ++- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 42 ++++---- .../LifetimeSafety/LifetimeAnnotations.cpp | 37 +++++++ clang/lib/Sema/SemaLifetimeSafety.h | 39 ++++---- ...ifetime-safety-misplaced-lifetimebound.cpp | 96 ++++++++++++++++--- 8 files changed, 197 insertions(+), 61 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h index ec05e67b05853..72ab6d1ffbf4c 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h @@ -12,6 +12,7 @@ #include "clang/AST/Attr.h" #include "clang/AST/DeclCXX.h" +#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h" namespace clang ::lifetimes { @@ -46,6 +47,9 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD); const LifetimeBoundAttr * getDirectImplicitObjectLifetimeBoundAttr(const FunctionDecl *FD); +const std::pair<const Decl *, WarningScope> +getUnannotatedDeclBestMatch(const FunctionDecl *FD, + const ParmVarDecl *PVD = nullptr); /// Returns the lifetimebound attribute for the implicit this parameter, if it /// exists on any redeclaration. const LifetimeBoundAttr * diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 132d20e9ed5f7..42c7eb304968c 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -41,9 +41,9 @@ struct LifetimeSafetyOpts { }; /// Enum to track functions visible across or within TU. -enum class SuggestionScope { - CrossTU, // For suggestions on declarations visible across Translation Units. - IntraTU // For suggestions on definitions local to a Translation Unit. +enum class WarningScope { + CrossTU, // For warnings on declarations visible across Translation Units. + IntraTU // For warnings on functions local to a Translation Unit. }; /// Abstract interface for operations requiring Sema access. @@ -105,7 +105,7 @@ class LifetimeSafetySemaHelper { llvm::PointerUnion<const Expr *, const FieldDecl *, const VarDecl *>; // Suggests lifetime bound annotations for function parameters. - virtual void suggestLifetimeboundToParmVar(SuggestionScope Scope, + virtual void suggestLifetimeboundToParmVar(WarningScope Scope, const ParmVarDecl *ParmToAnnotate, EscapingTarget Target) {} @@ -132,16 +132,18 @@ class LifetimeSafetySemaHelper { // Reports a member function definition that has [[clang::lifetimebound]] on // the implicit this parameter when the previous declaration does not. - virtual void reportMisplacedLifetimebound(const FunctionDecl *FDef, - const FunctionDecl *FDecl) {} + virtual void reportMisplacedLifetimebound(WarningScope Scope, + const CXXMethodDecl *FDef, + const CXXMethodDecl *FDecl) {} // Reports a function definition parameter that has [[clang::lifetimebound]] // when the corresponding parameter in the previous declaration does not. - virtual void reportMisplacedLifetimebound(const ParmVarDecl *PVDDef, + virtual void reportMisplacedLifetimebound(WarningScope Scope, + const ParmVarDecl *PVDDef, const ParmVarDecl *PVDDecl) {} // Suggests lifetime bound annotations for implicit this. - virtual void suggestLifetimeboundToImplicitThis(SuggestionScope Scope, + virtual void suggestLifetimeboundToImplicitThis(WarningScope Scope, const CXXMethodDecl *MD, const Expr *EscapeExpr) {} diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index bf8973242cb3f..1851d7b0b44a3 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -631,7 +631,14 @@ This warning may produce false-positives diagnostics when it cannot fully model }]; } -def LifetimeSafetyMisplacedLifetimebound : DiagGroup<"lifetime-safety-misplaced-lifetimebound"> { +def LifetimeSafetyCrossTUMisplacedLifetimebound + : DiagGroup<"lifetime-safety-cross-tu-misplaced-lifetimebound">; +def LifetimeSafetyIntraTUMisplacedLifetimebound + : DiagGroup<"lifetime-safety-intra-tu-misplaced-lifetimebound">; +def LifetimeSafetyMisplacedLifetimebound + : DiagGroup<"lifetime-safety-misplaced-lifetimebound", + [LifetimeSafetyCrossTUMisplacedLifetimebound, + LifetimeSafetyIntraTUMisplacedLifetimebound]> { code Documentation = [{ Detects function definitions whose parameters or implicit this argument are marked as [[clang::lifetimebound]] when the corresponding declaration is not. }]; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 6615c44f411bc..a701a73f46553 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11018,9 +11018,14 @@ def warn_lifetime_safety_lifetimebound_violation InGroup<LifetimeSafetyLifetimeboundViolation>, DefaultIgnore; -def warn_lifetime_safety_misplaced_lifetimebound - : Warning<"'lifetimebound' attribute on the definition is not visible to callers; add it to this declaration instead">, - InGroup<LifetimeSafetyMisplacedLifetimebound>, +def warn_lifetime_safety_intra_tu_misplaced_lifetimebound + : Warning<"'lifetimebound' attribute on an intra-TU definition is not visible to callers; add it to the declaration instead">, + InGroup<LifetimeSafetyIntraTUMisplacedLifetimebound>, + DefaultIgnore; + +def warn_lifetime_safety_cross_tu_misplaced_lifetimebound + : Warning<"'lifetimebound' attribute on a cross-TU definition is not visible to callers; add it to the declaration instead">, + InGroup<LifetimeSafetyCrossTUMisplacedLifetimebound>, DefaultIgnore; def note_lifetime_safety_used_here : Note<"later used here">; @@ -11035,7 +11040,7 @@ def note_lifetime_safety_dangling_static_here: Note<"this static storage dangles def note_lifetime_safety_escapes_to_field_here: Note<"escapes to this field">; def note_lifetime_safety_escapes_to_global_here: Note<"escapes to this global storage">; def note_lifetime_safety_escapes_to_static_storage_here: Note<"escapes to this static storage">; -def note_lifetime_safety_definition_lifetimebound_attribute_here: Note<"'lifetimebound' attribute written on the definition is here">; +def note_lifetime_safety_lifetimebound_here: Note<"'lifetimebound' attribute appears here on the definition">; def warn_lifetime_safety_intra_tu_param_suggestion : Warning<"parameter in intra-TU function should be marked " diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 2de413b4baa14..5078def06e9c1 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -61,6 +61,7 @@ class LifetimeChecker { llvm::DenseMap<AnnotationTarget, EscapingTarget> AnnotationWarningsMap; llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap; llvm::DenseSet<const Decl *> VerifiedLiftimeboundEscapes; + llvm::DenseMap<const Decl *, WarningScope> DeclarationsToAnnotate; const LoanPropagationAnalysis &LoanPropagation; const MovedLoansAnalysis &MovedLoans; const LiveOriginsAnalysis &LiveOrigins; @@ -135,6 +136,10 @@ class LifetimeChecker { if (PVD->hasAttr<LifetimeBoundAttr>()) { // Track that this lifetimebound parameter correctly escapes. VerifiedLiftimeboundEscapes.insert(PVD); + if (auto [UnannotatedFDecl, Scope] = + getUnannotatedDeclBestMatch(cast<FunctionDecl>(FD), PVD); + UnannotatedFDecl) + DeclarationsToAnnotate.try_emplace(UnannotatedFDecl, Scope); } else { // Otherwise, suggest lifetimebound for parameter escaping through // return or a field in constructor. @@ -152,6 +157,10 @@ class LifetimeChecker { VerifiedLiftimeboundEscapes.insert(MD); else if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) AnnotationWarningsMap.try_emplace(MD, ReturnEsc->getReturnExpr()); + if (getDirectImplicitObjectLifetimeBoundAttr(MD)) + if (auto [UnannotatedFDecl, Scope] = getUnannotatedDeclBestMatch(MD); + UnannotatedFDecl) + DeclarationsToAnnotate.try_emplace(UnannotatedFDecl, Scope); }; auto MovedAtEscape = MovedLoans.getMovedLoans(OEF); for (LoanID LID : EscapedLoans) { @@ -346,11 +355,11 @@ class LifetimeChecker { if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM)) SemaHelper->suggestLifetimeboundToParmVar( - SuggestionScope::CrossTU, + WarningScope::CrossTU, CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), EscapeTarget); else - SemaHelper->suggestLifetimeboundToParmVar(SuggestionScope::IntraTU, PVD, + SemaHelper->suggestLifetimeboundToParmVar(WarningScope::IntraTU, PVD, EscapeTarget); } @@ -360,11 +369,10 @@ class LifetimeChecker { const Expr *EscapeExpr) { if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*MD, SM)) SemaHelper->suggestLifetimeboundToImplicitThis( - SuggestionScope::CrossTU, cast<CXXMethodDecl>(CrossTUDecl), - EscapeExpr); + WarningScope::CrossTU, cast<CXXMethodDecl>(CrossTUDecl), EscapeExpr); else - SemaHelper->suggestLifetimeboundToImplicitThis(SuggestionScope::IntraTU, - MD, EscapeExpr); + SemaHelper->suggestLifetimeboundToImplicitThis(WarningScope::IntraTU, MD, + EscapeExpr); } void suggestAnnotations() { @@ -420,21 +428,13 @@ class LifetimeChecker { const FunctionDecl *FDef = dyn_cast<FunctionDecl>(FD); if (!FDef) return; - - const FunctionDecl *FDecl = FDef->getPreviousDecl(); - if (!FDecl) - return; - - if (isa<CXXMethodDecl>(FDef) && - getDirectImplicitObjectLifetimeBoundAttr(FDef) && - !getDirectImplicitObjectLifetimeBoundAttr(FDecl)) - SemaHelper->reportMisplacedLifetimebound(FDef, FDecl); - - for (auto [PVDDef, PVDDecl] : - llvm::zip_equal(FDef->parameters(), FDecl->parameters())) { - if (PVDDef->hasAttr<LifetimeBoundAttr>() && - !PVDDecl->hasAttr<LifetimeBoundAttr>()) - SemaHelper->reportMisplacedLifetimebound(PVDDef, PVDDecl); + for (auto [Decl, Scope] : DeclarationsToAnnotate) { + if (const auto *MD = dyn_cast<CXXMethodDecl>(Decl)) + SemaHelper->reportMisplacedLifetimebound(Scope, + cast<CXXMethodDecl>(FDef), MD); + else if (const auto *PVD = dyn_cast<ParmVarDecl>(Decl)) + SemaHelper->reportMisplacedLifetimebound( + Scope, FDef->getParamDecl(PVD->getFunctionScopeIndex()), PVD); } } diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp index 8f2b392fff514..88639e2c60eb5 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp @@ -14,6 +14,7 @@ #include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/OperatorKinds.h" +#include "clang/Basic/SourceManager.h" #include "llvm/ADT/StringSet.h" namespace clang::lifetimes { @@ -105,6 +106,42 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) { return isNormalAssignmentOperator(FD); } +const std::pair<const Decl *, WarningScope> +getUnannotatedDeclBestMatch(const FunctionDecl *FD, const ParmVarDecl *PVD) { + if (!FD || !FD->isExternallyVisible()) + return {nullptr, WarningScope::IntraTU}; + + auto HasAttr = [PVD](const FunctionDecl *D) -> bool { + if (PVD) + return D->getParamDecl(PVD->getFunctionScopeIndex()) + ->hasAttr<LifetimeBoundAttr>(); + return getDirectImplicitObjectLifetimeBoundAttr(D); + }; + + auto GetAnnotatedDecl = [PVD](const FunctionDecl *D) -> const Decl * { + if (!D) + return nullptr; + if (PVD) + return D->getParamDecl(PVD->getFunctionScopeIndex()); + return D; + }; + + const auto &SM = FD->getASTContext().getSourceManager(); + const FileID DefFile = SM.getFileID(SM.getExpansionLoc(FD->getLocation())); + const FunctionDecl *Fallback = nullptr; + + for (const FunctionDecl *D = FD->getPreviousDecl(); D; + D = D->getPreviousDecl()) { + if (D->isThisDeclarationADefinition() || HasAttr(D)) + continue; + if (!Fallback) + Fallback = D; + if (SM.getFileID(SM.getExpansionLoc(D->getLocation())) != DefFile) + return {GetAnnotatedDecl(D), WarningScope::CrossTU}; + } + return {GetAnnotatedDecl(Fallback), WarningScope::IntraTU}; +} + bool isInStlNamespace(const Decl *D) { const DeclContext *DC = D->getDeclContext(); if (!DC) diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 72d709e18d365..36b0aa2f9dee9 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -40,7 +40,8 @@ inline bool IsLifetimeSafetyEnabled(Sema &S, const Decl *D) { diag::warn_lifetime_safety_dangling_global_moved, diag::warn_lifetime_safety_noescape_escapes, diag::warn_lifetime_safety_lifetimebound_violation, - }; + diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound, + diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound}; for (unsigned DiagID : DiagIDs) if (!Diags.isIgnored(DiagID, D->getBeginLoc())) return true; @@ -225,11 +226,11 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { << DanglingGlobal->getEndLoc(); } - void suggestLifetimeboundToParmVar(SuggestionScope Scope, + void suggestLifetimeboundToParmVar(WarningScope Scope, const ParmVarDecl *ParmToAnnotate, EscapingTarget Target) override { unsigned DiagID = - (Scope == SuggestionScope::CrossTU) + (Scope == WarningScope::CrossTU) ? diag::warn_lifetime_safety_cross_tu_param_suggestion : diag::warn_lifetime_safety_intra_tu_param_suggestion; SourceLocation InsertionPoint = Lexer::getLocForEndOfToken( @@ -275,37 +276,43 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { << 2 << "" << Attr->getRange(); } - void reportMisplacedLifetimebound(const FunctionDecl *FDef, - const FunctionDecl *FDecl) override { + void reportMisplacedLifetimebound(WarningScope Scope, + const CXXMethodDecl *FDef, + const CXXMethodDecl *FDecl) override { const auto *Attr = getImplicitObjectParamLifetimeBoundAttr(FDef); assert(Attr && "Expected lifetimebound attribute"); + unsigned DiagID = + 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()), - diag::warn_lifetime_safety_misplaced_lifetimebound); + DiagID); - S.Diag(Attr->getLocation(), - diag::note_lifetime_safety_definition_lifetimebound_attribute_here) + S.Diag(Attr->getLocation(), diag::note_lifetime_safety_lifetimebound_here) << Attr->getRange(); } - void reportMisplacedLifetimebound(const ParmVarDecl *PVDDef, + void reportMisplacedLifetimebound(WarningScope Scope, + const ParmVarDecl *PVDDef, const ParmVarDecl *PVDDecl) override { const auto *Attr = PVDDef->getAttr<LifetimeBoundAttr>(); assert(Attr && "Expected lifetimebound attribute"); - S.Diag(PVDDecl->getBeginLoc(), - diag::warn_lifetime_safety_misplaced_lifetimebound) - << PVDDecl->getSourceRange(); + unsigned DiagID = + 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(); - S.Diag(Attr->getLocation(), - diag::note_lifetime_safety_definition_lifetimebound_attribute_here) + S.Diag(Attr->getLocation(), diag::note_lifetime_safety_lifetimebound_here) << Attr->getRange(); } - void suggestLifetimeboundToImplicitThis(SuggestionScope Scope, + void suggestLifetimeboundToImplicitThis(WarningScope Scope, const CXXMethodDecl *MD, const Expr *EscapeExpr) override { - unsigned DiagID = (Scope == SuggestionScope::CrossTU) + 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(); diff --git a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp index 6c0c1949931c9..d759368f7d4e1 100644 --- a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp +++ b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp @@ -1,40 +1,114 @@ -// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-validations -Wno-dangling -verify %s +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-intra-tu-misplaced-lifetimebound -Wno-dangling -verify=intra %t/test.cpp +// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-cross-tu-misplaced-lifetimebound -Wno-dangling -I%t -verify=cross %t/cross.cpp +//--- test.cpp struct MyObj { ~MyObj() {} }; +MyObj &free_param(MyObj &obj); // intra-warning {{'lifetimebound' attribute on an intra-TU definition is not visible to callers; add it to the declaration instead}} +MyObj &free_param(MyObj &obj [[clang::lifetimebound]]) { // intra-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + struct S { MyObj data; - const MyObj &implicit_this_only(); // expected-warning {{'lifetimebound' attribute on the definition is not visible to callers; add it to this declaration instead}} - const MyObj ¶m_only(const MyObj &obj); // expected-warning {{'lifetimebound' attribute on the definition is not visible to callers; add it to this declaration instead}} - const MyObj &both(const MyObj &obj, bool); // expected-warning 2 {{'lifetimebound' attribute on the definition is not visible to callers; add it to this declaration instead}} + const MyObj &implicit_this_only(); // intra-warning {{'lifetimebound' attribute on an intra-TU definition is not visible to callers; add it to the declaration instead}} + const MyObj ¶m_only(const MyObj &obj); // intra-warning {{'lifetimebound' attribute on an intra-TU definition is not visible to callers; add it to the declaration instead}} + const MyObj &both(const MyObj &obj, bool); // intra-warning 2 {{'lifetimebound' attribute on an intra-TU definition is not visible to callers; add it to the declaration instead}} }; -const MyObj &S::implicit_this_only() [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute written on the definition is here}} +const MyObj &S::implicit_this_only() [[clang::lifetimebound]] { // intra-note {{'lifetimebound' attribute appears here on the definition}} return data; } const MyObj &S::param_only( - const MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute written on the definition is here}} + const MyObj &obj [[clang::lifetimebound]]) { // intra-note {{'lifetimebound' attribute appears here on the definition}} return obj; } const MyObj &S::both( - const MyObj &obj [[clang::lifetimebound]], bool use_obj) // expected-note {{'lifetimebound' attribute written on the definition is here}} - [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute written on the definition is here}} + const MyObj &obj [[clang::lifetimebound]], bool use_obj) // intra-note {{'lifetimebound' attribute appears here on the definition}} + [[clang::lifetimebound]] { // intra-note {{'lifetimebound' attribute appears here on the definition}} return use_obj ? obj : data; } template <class T> struct MixedSpecializations { T data; - T &both(T &arg, bool); // expected-warning 2 {{'lifetimebound' attribute on the definition is not visible to callers; add it to this declaration instead}} + T &both(T &arg, bool); // intra-warning 2 {{'lifetimebound' attribute on an intra-TU definition is not visible to callers; add it to the declaration instead}} }; template <> MyObj &MixedSpecializations<MyObj>::both( - MyObj &arg [[clang::lifetimebound]], bool use_arg) // expected-note {{'lifetimebound' attribute written on the definition is here}} - [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute written on the definition is here}} + MyObj &arg [[clang::lifetimebound]], bool use_arg) // intra-note {{'lifetimebound' attribute appears here on the definition}} + [[clang::lifetimebound]] { // intra-note {{'lifetimebound' attribute appears here on the definition}} return use_arg ? arg : data; } + +struct InternalObj { + ~InternalObj() {} +}; + +namespace { +InternalObj &anon_param(InternalObj &obj); +InternalObj &anon_param(InternalObj &obj [[clang::lifetimebound]]) { + return obj; +} + +struct AnonS { + InternalObj data; + InternalObj &anon_this(); +}; + +InternalObj &AnonS::anon_this() [[clang::lifetimebound]] { + return data; +} +} // namespace + +static InternalObj &static_param(InternalObj &obj); +static InternalObj &static_param(InternalObj &obj [[clang::lifetimebound]]) { + return obj; +} + +struct IntraSuppressedObj { + ~IntraSuppressedObj() {} +}; + +IntraSuppressedObj &intra_suppressed(IntraSuppressedObj &obj); // intra-warning {{'lifetimebound' attribute on an intra-TU definition is not visible to callers; add it to the declaration instead}} +IntraSuppressedObj &intra_suppressed( + IntraSuppressedObj &obj [[clang::lifetimebound]]) { // intra-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + +//--- cross.h +struct HeaderObj { + ~HeaderObj() {} +}; + +HeaderObj &header_param(HeaderObj &obj); // cross-warning {{'lifetimebound' attribute on a cross-TU definition is not visible to callers; add it to the declaration instead}} + +HeaderObj &header_then_source_redecl(HeaderObj &obj); // cross-warning {{'lifetimebound' attribute on a cross-TU definition is not visible to callers; add it to the declaration instead}} + +struct HeaderS { + HeaderObj data; + HeaderObj &header_this(); // cross-warning {{'lifetimebound' attribute on a cross-TU definition is not visible to callers; add it to the declaration instead}} +}; + +//--- cross.cpp +#include "cross.h" + +HeaderObj &header_param(HeaderObj &obj [[clang::lifetimebound]]) { // cross-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + +HeaderObj &header_then_source_redecl(HeaderObj &obj); +HeaderObj &header_then_source_redecl(HeaderObj &obj [[clang::lifetimebound]]) { // cross-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + +HeaderObj &HeaderS::header_this() [[clang::lifetimebound]] { // cross-note {{'lifetimebound' attribute appears here on the definition}} + return data; +} >From a6af555a4c98522db7a0fea28c1d903ede887ffd Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Fri, 15 May 2026 22:13:30 +0300 Subject: [PATCH 4/7] resort includes... --- .../Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h | 3 ++- clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h index 72ab6d1ffbf4c..bf5a960c1c67d 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h @@ -12,10 +12,11 @@ #include "clang/AST/Attr.h" #include "clang/AST/DeclCXX.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h" namespace clang ::lifetimes { +enum class WarningScope; + // This function is needed because Decl::isInStdNamespace will return false for // iterators in some STL implementations due to them being defined in a // namespace outside of the std namespace. diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp index 88639e2c60eb5..f989477642dc4 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp @@ -13,6 +13,7 @@ #include "clang/AST/DeclTemplate.h" #include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" +#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h" #include "clang/Basic/OperatorKinds.h" #include "clang/Basic/SourceManager.h" #include "llvm/ADT/StringSet.h" >From 4297a2beb3a886f73bcea67dbabd6ff3d3570a71 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Mon, 18 May 2026 17:58:03 +0300 Subject: [PATCH 5/7] address PR comments --- .../LifetimeSafety/LifetimeAnnotations.h | 5 -- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 64 ++++++++++++++----- .../LifetimeSafety/LifetimeAnnotations.cpp | 38 ----------- ...afety-misplaced-lifetimebound-cross-tu.cpp | 26 ++++++++ ...fety-misplaced-lifetimebound-intra-tu.cpp} | 36 +---------- 5 files changed, 75 insertions(+), 94 deletions(-) create mode 100644 clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-cross-tu.cpp rename clang/test/Sema/{warn-lifetime-safety-misplaced-lifetimebound.cpp => warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp} (68%) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h index bf5a960c1c67d..ec05e67b05853 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h @@ -15,8 +15,6 @@ namespace clang ::lifetimes { -enum class WarningScope; - // This function is needed because Decl::isInStdNamespace will return false for // iterators in some STL implementations due to them being defined in a // namespace outside of the std namespace. @@ -48,9 +46,6 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD); const LifetimeBoundAttr * getDirectImplicitObjectLifetimeBoundAttr(const FunctionDecl *FD); -const std::pair<const Decl *, WarningScope> -getUnannotatedDeclBestMatch(const FunctionDecl *FD, - const ParmVarDecl *PVD = nullptr); /// Returns the lifetimebound attribute for the implicit this parameter, if it /// exists on any redeclaration. const LifetimeBoundAttr * diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 5078def06e9c1..a9ec4694501f6 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -61,7 +61,6 @@ class LifetimeChecker { llvm::DenseMap<AnnotationTarget, EscapingTarget> AnnotationWarningsMap; llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap; llvm::DenseSet<const Decl *> VerifiedLiftimeboundEscapes; - llvm::DenseMap<const Decl *, WarningScope> DeclarationsToAnnotate; const LoanPropagationAnalysis &LoanPropagation; const MovedLoansAnalysis &MovedLoans; const LiveOriginsAnalysis &LiveOrigins; @@ -136,10 +135,6 @@ class LifetimeChecker { if (PVD->hasAttr<LifetimeBoundAttr>()) { // Track that this lifetimebound parameter correctly escapes. VerifiedLiftimeboundEscapes.insert(PVD); - if (auto [UnannotatedFDecl, Scope] = - getUnannotatedDeclBestMatch(cast<FunctionDecl>(FD), PVD); - UnannotatedFDecl) - DeclarationsToAnnotate.try_emplace(UnannotatedFDecl, Scope); } else { // Otherwise, suggest lifetimebound for parameter escaping through // return or a field in constructor. @@ -157,10 +152,6 @@ class LifetimeChecker { VerifiedLiftimeboundEscapes.insert(MD); else if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) AnnotationWarningsMap.try_emplace(MD, ReturnEsc->getReturnExpr()); - if (getDirectImplicitObjectLifetimeBoundAttr(MD)) - if (auto [UnannotatedFDecl, Scope] = getUnannotatedDeclBestMatch(MD); - UnannotatedFDecl) - DeclarationsToAnnotate.try_emplace(UnannotatedFDecl, Scope); }; auto MovedAtEscape = MovedLoans.getMovedLoans(OEF); for (LoanID LID : EscapedLoans) { @@ -325,6 +316,42 @@ class LifetimeChecker { } } + const std::pair<const FunctionDecl *, WarningScope> + getCanonicalFunctionDeclForAttr(const FunctionDecl *FD) { + if (!FD || !FD->isExternallyVisible()) + return {nullptr, WarningScope::IntraTU}; + + const auto &SM = FD->getASTContext().getSourceManager(); + const FileID DefFile = SM.getFileID(SM.getExpansionLoc(FD->getLocation())); + const FunctionDecl *FirstDecl = nullptr; + WarningScope Scope = WarningScope::IntraTU; + + for (const FunctionDecl *D = FD->getPreviousDecl(); D; + D = D->getPreviousDecl()) { + if (D->isThisDeclarationADefinition()) + continue; + FirstDecl = D; + Scope = SM.getFileID(SM.getExpansionLoc(D->getLocation())) != DefFile + ? WarningScope::CrossTU + : WarningScope::IntraTU; + } + return {FirstDecl, Scope}; + } + + const std::pair<const CXXMethodDecl *, WarningScope> + getCanonicalDeclForAttr(const CXXMethodDecl *MD) { + auto [CanonicalFD, Scope] = getCanonicalFunctionDeclForAttr(MD); + return {cast_or_null<CXXMethodDecl>(CanonicalFD), Scope}; + } + + const std::pair<const ParmVarDecl *, WarningScope> + getCanonicalDeclForAttr(const FunctionDecl *FD, const ParmVarDecl *PVD) { + auto [CanonicalFD, Scope] = getCanonicalFunctionDeclForAttr(FD); + if (!CanonicalFD) + return {nullptr, Scope}; + return {CanonicalFD->getParamDecl(PVD->getFunctionScopeIndex()), Scope}; + } + /// Returns the declaration of a function that is visible across translation /// units, if such a declaration exists and is different from the definition. static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD, @@ -428,13 +455,18 @@ class LifetimeChecker { const FunctionDecl *FDef = dyn_cast<FunctionDecl>(FD); if (!FDef) return; - for (auto [Decl, Scope] : DeclarationsToAnnotate) { - if (const auto *MD = dyn_cast<CXXMethodDecl>(Decl)) - SemaHelper->reportMisplacedLifetimebound(Scope, - cast<CXXMethodDecl>(FDef), MD); - else if (const auto *PVD = dyn_cast<ParmVarDecl>(Decl)) - SemaHelper->reportMisplacedLifetimebound( - Scope, FDef->getParamDecl(PVD->getFunctionScopeIndex()), PVD); + if (const auto *MDef = dyn_cast<CXXMethodDecl>(FDef); + MDef && getDirectImplicitObjectLifetimeBoundAttr(MDef)) + if (auto [MDecl, Scope] = getCanonicalDeclForAttr(MDef); + MDecl && !getDirectImplicitObjectLifetimeBoundAttr(MDecl)) + SemaHelper->reportMisplacedLifetimebound(Scope, MDef, MDecl); + for (const auto *PDef : FDef->parameters()) { + const auto *Attr = PDef->getAttr<LifetimeBoundAttr>(); + if (!Attr || Attr->isImplicit()) + continue; + if (auto [PDecl, Scope] = getCanonicalDeclForAttr(FDef, PDef); + PDecl && !PDecl->hasAttr<LifetimeBoundAttr>()) + SemaHelper->reportMisplacedLifetimebound(Scope, PDef, PDecl); } } diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp index f989477642dc4..8f2b392fff514 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp @@ -13,9 +13,7 @@ #include "clang/AST/DeclTemplate.h" #include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h" #include "clang/Basic/OperatorKinds.h" -#include "clang/Basic/SourceManager.h" #include "llvm/ADT/StringSet.h" namespace clang::lifetimes { @@ -107,42 +105,6 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) { return isNormalAssignmentOperator(FD); } -const std::pair<const Decl *, WarningScope> -getUnannotatedDeclBestMatch(const FunctionDecl *FD, const ParmVarDecl *PVD) { - if (!FD || !FD->isExternallyVisible()) - return {nullptr, WarningScope::IntraTU}; - - auto HasAttr = [PVD](const FunctionDecl *D) -> bool { - if (PVD) - return D->getParamDecl(PVD->getFunctionScopeIndex()) - ->hasAttr<LifetimeBoundAttr>(); - return getDirectImplicitObjectLifetimeBoundAttr(D); - }; - - auto GetAnnotatedDecl = [PVD](const FunctionDecl *D) -> const Decl * { - if (!D) - return nullptr; - if (PVD) - return D->getParamDecl(PVD->getFunctionScopeIndex()); - return D; - }; - - const auto &SM = FD->getASTContext().getSourceManager(); - const FileID DefFile = SM.getFileID(SM.getExpansionLoc(FD->getLocation())); - const FunctionDecl *Fallback = nullptr; - - for (const FunctionDecl *D = FD->getPreviousDecl(); D; - D = D->getPreviousDecl()) { - if (D->isThisDeclarationADefinition() || HasAttr(D)) - continue; - if (!Fallback) - Fallback = D; - if (SM.getFileID(SM.getExpansionLoc(D->getLocation())) != DefFile) - return {GetAnnotatedDecl(D), WarningScope::CrossTU}; - } - return {GetAnnotatedDecl(Fallback), WarningScope::IntraTU}; -} - bool isInStlNamespace(const Decl *D) { const DeclContext *DC = D->getDeclContext(); if (!DC) 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 new file mode 100644 index 0000000000000..701f4f8bcbaa4 --- /dev/null +++ b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-cross-tu.cpp @@ -0,0 +1,26 @@ +// 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=cross %t/cross.cpp + +//--- cross.h +struct HeaderObj { + ~HeaderObj() {} +}; + +HeaderObj &header_param(HeaderObj &obj); // cross-warning {{'lifetimebound' attribute on a cross-TU definition is not visible to callers; add it to the declaration instead}} + +struct HeaderS { + HeaderObj data; + HeaderObj &header_this(); // cross-warning {{'lifetimebound' attribute on a cross-TU definition is not visible to callers; add it to the declaration instead}} +}; + +//--- cross.cpp +#include "cross.h" + +HeaderObj &header_param(HeaderObj &obj [[clang::lifetimebound]]) { // cross-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + +HeaderObj &HeaderS::header_this() [[clang::lifetimebound]] { // cross-note {{'lifetimebound' attribute appears here on the definition}} + return data; +} diff --git a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp similarity index 68% rename from clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp rename to clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp index d759368f7d4e1..fc44dc5f59658 100644 --- a/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound.cpp +++ b/clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp @@ -1,9 +1,5 @@ -// RUN: rm -rf %t -// RUN: split-file %s %t -// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-intra-tu-misplaced-lifetimebound -Wno-dangling -verify=intra %t/test.cpp -// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-cross-tu-misplaced-lifetimebound -Wno-dangling -I%t -verify=cross %t/cross.cpp +// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-intra-tu-misplaced-lifetimebound -Wno-dangling -verify=intra %s -//--- test.cpp struct MyObj { ~MyObj() {} }; @@ -82,33 +78,3 @@ IntraSuppressedObj &intra_suppressed( IntraSuppressedObj &obj [[clang::lifetimebound]]) { // intra-note {{'lifetimebound' attribute appears here on the definition}} return obj; } - -//--- cross.h -struct HeaderObj { - ~HeaderObj() {} -}; - -HeaderObj &header_param(HeaderObj &obj); // cross-warning {{'lifetimebound' attribute on a cross-TU definition is not visible to callers; add it to the declaration instead}} - -HeaderObj &header_then_source_redecl(HeaderObj &obj); // cross-warning {{'lifetimebound' attribute on a cross-TU definition is not visible to callers; add it to the declaration instead}} - -struct HeaderS { - HeaderObj data; - HeaderObj &header_this(); // cross-warning {{'lifetimebound' attribute on a cross-TU definition is not visible to callers; add it to the declaration instead}} -}; - -//--- cross.cpp -#include "cross.h" - -HeaderObj &header_param(HeaderObj &obj [[clang::lifetimebound]]) { // cross-note {{'lifetimebound' attribute appears here on the definition}} - return obj; -} - -HeaderObj &header_then_source_redecl(HeaderObj &obj); -HeaderObj &header_then_source_redecl(HeaderObj &obj [[clang::lifetimebound]]) { // cross-note {{'lifetimebound' attribute appears here on the definition}} - return obj; -} - -HeaderObj &HeaderS::header_this() [[clang::lifetimebound]] { // cross-note {{'lifetimebound' attribute appears here on the definition}} - return data; -} >From aa84793248a636ce629e99fb68598a0eee6ebd7d Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Mon, 18 May 2026 18:13:36 +0300 Subject: [PATCH 6/7] add a new test --- ...fetime-safety-misplaced-lifetimebound-intra-tu.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 fc44dc5f59658..f308630115eb6 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 @@ -78,3 +78,14 @@ IntraSuppressedObj &intra_suppressed( IntraSuppressedObj &obj [[clang::lifetimebound]]) { // intra-note {{'lifetimebound' attribute appears here on the definition}} return obj; } + +struct View { + friend View friend_redecl(MyObj &obj); // intra-warning {{'lifetimebound' attribute on an intra-TU definition is not visible to callers; add it to the declaration instead}} +}; + +// FIXME: Fix warning location, add a note pointing to this declaration saying "attribute inherited from this declaration" +View friend_redecl(MyObj &obj [[clang::lifetimebound]]); // intra-note {{'lifetimebound' attribute appears here on the definition}} + +View friend_redecl(MyObj &obj) { + return View{}; +} >From efd6d1248fd6adafc54b6648a25d5ac06f1157ef Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Mon, 18 May 2026 18:22:32 +0300 Subject: [PATCH 7/7] change comment --- .../warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f308630115eb6..d0f9f158f78e9 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 @@ -83,7 +83,7 @@ struct View { friend View friend_redecl(MyObj &obj); // intra-warning {{'lifetimebound' attribute on an intra-TU definition is not visible to callers; add it to the declaration instead}} }; -// FIXME: Fix warning location, add a note pointing to this declaration saying "attribute inherited from this declaration" +// 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]]); // intra-note {{'lifetimebound' attribute appears here on the definition}} View friend_redecl(MyObj &obj) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
