https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/198784
>From f95dcf6ef9be88c85f4a94735e93077046ecfe68 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Wed, 20 May 2026 16:30:14 +0300 Subject: [PATCH 1/6] remove function --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 57 ++++--------------- .../test/Sema/warn-lifetime-safety-fixits.cpp | 9 ++- .../Sema/warn-lifetime-safety-suggestions.cpp | 24 ++++---- 3 files changed, 27 insertions(+), 63 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index d6a15139aa4ea..4b9d195411179 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -353,66 +353,31 @@ class LifetimeChecker { 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, - SourceManager &SM) { - if (!FD.isExternallyVisible()) - return nullptr; - const FileID DefinitionFile = SM.getFileID(FD.getLocation()); - for (const FunctionDecl *Redecl : FD.redecls()) - if (SM.getFileID(Redecl->getLocation()) != DefinitionFile) - return Redecl; - - return nullptr; - } - - static const FunctionDecl *getCrossTUDecl(const ParmVarDecl &PVD, - SourceManager &SM) { - if (const auto *FD = dyn_cast<FunctionDecl>(PVD.getDeclContext())) - return getCrossTUDecl(*FD, SM); - return nullptr; - } - - static void suggestWithScopeForParmVar(LifetimeSafetySemaHelper *SemaHelper, - const ParmVarDecl *PVD, - SourceManager &SM, - EscapingTarget EscapeTarget) { + void suggestWithScopeForParmVar(const ParmVarDecl *PVD, + EscapingTarget EscapeTarget) { if (llvm::isa<const VarDecl *>(EscapeTarget)) return; - if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM)) - SemaHelper->suggestLifetimeboundToParmVar( - WarningScope::CrossTU, - CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), - EscapeTarget); - else - SemaHelper->suggestLifetimeboundToParmVar(WarningScope::IntraTU, PVD, - EscapeTarget); + auto [Parm, Scope] = getCanonicalDeclForAttr(cast<FunctionDecl>(FD), PVD); + SemaHelper->suggestLifetimeboundToParmVar(Scope, Parm, EscapeTarget); } - static void - suggestWithScopeForImplicitThis(LifetimeSafetySemaHelper *SemaHelper, - const CXXMethodDecl *MD, SourceManager &SM, - const Expr *EscapeExpr) { - if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*MD, SM)) - SemaHelper->suggestLifetimeboundToImplicitThis( - WarningScope::CrossTU, cast<CXXMethodDecl>(CrossTUDecl), EscapeExpr); - else - SemaHelper->suggestLifetimeboundToImplicitThis(WarningScope::IntraTU, MD, - EscapeExpr); + void suggestWithScopeForImplicitThis(const CXXMethodDecl *MD, + const Expr *EscapeExpr) { + auto [MethodDecl, Scope] = getCanonicalDeclForAttr(MD); + SemaHelper->suggestLifetimeboundToImplicitThis(Scope, MethodDecl, + EscapeExpr); } void suggestAnnotations() { if (!SemaHelper) return; - SourceManager &SM = AST.getSourceManager(); for (auto [Target, EscapeTarget] : AnnotationWarningsMap) { if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>()) - suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeTarget); + suggestWithScopeForParmVar(PVD, EscapeTarget); else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) { if (const auto *EscapeExpr = EscapeTarget.dyn_cast<const Expr *>()) - suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr); + suggestWithScopeForImplicitThis(MD, EscapeExpr); else llvm_unreachable("Implicit this can only escape via Expr (return)"); } diff --git a/clang/test/Sema/warn-lifetime-safety-fixits.cpp b/clang/test/Sema/warn-lifetime-safety-fixits.cpp index 88d8bd379de8b..d9c7e8d3f0519 100644 --- a/clang/test/Sema/warn-lifetime-safety-fixits.cpp +++ b/clang/test/Sema/warn-lifetime-safety-fixits.cpp @@ -69,12 +69,11 @@ int *arr_default(int a[2] = nullptr) { return a; } -// FIXME: Iterate over redecls and add [[clang::lifetimebound]] View multi_decl(View a); +// CHECK: :[[@LINE-1]]:17: warning: parameter in intra-TU function should be marked +// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:23-[[@LINE-2]]:23}:" {{\[\[}}clang::lifetimebound]]" View multi_decl(View a); View multi_decl(View a) { - // CHECK: :[[@LINE-1]]:17: warning: parameter in intra-TU function should be marked - // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:23-[[@LINE-2]]:23}:" {{\[\[}}clang::lifetimebound]]" return a; } @@ -145,10 +144,10 @@ struct OutOfLine { OutOfLine() {} ~OutOfLine() {} const OutOfLine &get() const; + // CHECK: :[[@LINE-1]]:31: warning: implicit this in intra-TU function should be marked + // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:31-[[@LINE-2]]:31}:" {{\[\[}}clang::lifetimebound]]" }; const OutOfLine &OutOfLine::get() const { - // CHECK: :[[@LINE-1]]:40: warning: implicit this in intra-TU function should be marked - // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:40-[[@LINE-2]]:40}:" {{\[\[}}clang::lifetimebound]]" return *this; } diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp index 973c610eb58ab..5122e634e594f 100644 --- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp +++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp @@ -46,8 +46,8 @@ inline View inline_header_return_view(View a) { // expected-warning {{parameter return a; // expected-note {{param returned here}} } -View redeclared_in_header(View a); -inline View redeclared_in_header(View a) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +View redeclared_in_header(View a); // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +inline View redeclared_in_header(View a) { return a; // expected-note {{param returned here}} } @@ -177,8 +177,8 @@ View reassigned_to_another_parameter( return a; // expected-note {{param returned here}} } -View intra_tu_func_redecl(View a); -View intra_tu_func_redecl(View a) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. +View intra_tu_func_redecl(View a); // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. +View intra_tu_func_redecl(View a) { return a; // expected-note {{param returned here}} } } @@ -282,25 +282,25 @@ MyObj* return_pointer_by_func(MyObj* a) { // expected-warning {{paramete } // namespace correct_order_inference namespace incorrect_order_inference_view { -View return_view_callee(View a); +View return_view_callee(View a); // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. View return_view_caller(View a) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. return return_view_callee(a); // expected-note {{param returned here}} } -View return_view_callee(View a) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. +View return_view_callee(View a) { return a; // expected-note {{param returned here}} } } // namespace incorrect_order_inference_view namespace incorrect_order_inference_object { -MyObj* return_object_callee(MyObj* a); +MyObj* return_object_callee(MyObj* a); // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. MyObj* return_object_caller(MyObj* a) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. return return_object_callee(a); // expected-note {{param returned here}} } -MyObj* return_object_callee(MyObj* a) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. +MyObj* return_object_callee(MyObj* a) { return a; // expected-note {{param returned here}} } } // namespace incorrect_order_inference_object @@ -322,13 +322,13 @@ View inference_top_level_return_stack_view() { } // namespace simple_annotation_inference namespace inference_in_order_with_redecls { -View inference_callee_return_identity(View a); -View inference_callee_return_identity(View a) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. +View inference_callee_return_identity(View a); // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. +View inference_callee_return_identity(View a) { return a; // expected-note {{param returned here}} } -View inference_caller_forwards_callee(View a); -View inference_caller_forwards_callee(View a) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. +View inference_caller_forwards_callee(View a); // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}. +View inference_caller_forwards_callee(View a) { return inference_callee_return_identity(a); // expected-note {{param returned here}} } >From 8c31e0f4a28d0c2dd91d91e23fd97131f45d2808 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Wed, 20 May 2026 17:55:45 +0300 Subject: [PATCH 2/6] revert the change and warn on both crosstu declarations and canonical declarations --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 43 +++++++++++++++++-- .../Sema/warn-lifetime-safety-suggestions.cpp | 9 ++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 4b9d195411179..f6b116b2d03a6 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -353,20 +353,55 @@ class LifetimeChecker { 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, + SourceManager &SM) { + if (!FD.isExternallyVisible()) + return nullptr; + const FileID DefinitionFile = SM.getFileID(FD.getLocation()); + for (const FunctionDecl *Redecl : FD.redecls()) + if (SM.getFileID(Redecl->getLocation()) != DefinitionFile) + return Redecl; + + return nullptr; + } + + static const FunctionDecl *getCrossTUDecl(const ParmVarDecl &PVD, + SourceManager &SM) { + if (const auto *FD = dyn_cast<FunctionDecl>(PVD.getDeclContext())) + return getCrossTUDecl(*FD, SM); + return nullptr; + } + void suggestWithScopeForParmVar(const ParmVarDecl *PVD, EscapingTarget EscapeTarget) { if (llvm::isa<const VarDecl *>(EscapeTarget)) return; - auto [Parm, Scope] = getCanonicalDeclForAttr(cast<FunctionDecl>(FD), PVD); - SemaHelper->suggestLifetimeboundToParmVar(Scope, Parm, EscapeTarget); + auto [CanonicalPVD, Scope] = + getCanonicalDeclForAttr(cast<FunctionDecl>(FD), PVD); + const auto *CrossTUFD = getCrossTUDecl(*PVD, AST.getSourceManager()); + const auto *CrossTUParm = + CrossTUFD ? CrossTUFD->getParamDecl(PVD->getFunctionScopeIndex()) + : nullptr; + + SemaHelper->suggestLifetimeboundToParmVar(Scope, CanonicalPVD, + EscapeTarget); + if (CrossTUParm && CrossTUParm != CanonicalPVD) + SemaHelper->suggestLifetimeboundToParmVar(WarningScope::CrossTU, + CrossTUParm, EscapeTarget); } void suggestWithScopeForImplicitThis(const CXXMethodDecl *MD, const Expr *EscapeExpr) { - auto [MethodDecl, Scope] = getCanonicalDeclForAttr(MD); - SemaHelper->suggestLifetimeboundToImplicitThis(Scope, MethodDecl, + auto [CanonicalDecl, Scope] = getCanonicalDeclForAttr(MD); + const auto *CrossTUDecl = getCrossTUDecl(*MD, AST.getSourceManager()); + SemaHelper->suggestLifetimeboundToImplicitThis(Scope, CanonicalDecl, EscapeExpr); + if (CrossTUDecl && CrossTUDecl != CanonicalDecl) + SemaHelper->suggestLifetimeboundToImplicitThis( + WarningScope::CrossTU, cast<CXXMethodDecl>(CrossTUDecl), EscapeExpr); } void suggestAnnotations() { diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp index 5122e634e594f..30d1896450236 100644 --- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp +++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp @@ -31,6 +31,8 @@ struct [[gsl::Pointer()]] View { View definition_before_header(View a); // expected-warning {{parameter in cross-TU function should be marked [[clang::lifetimebound]]}} +View redeclared_before_header_include(View a); // expected-warning {{parameter in cross-TU function should be marked [[clang::lifetimebound]]}} + View return_view_directly(View a); // expected-warning {{parameter in cross-TU function should be marked [[clang::lifetimebound]]}} View conditional_return_view( @@ -67,6 +69,9 @@ struct ReturnThisPointer { //--- test_source.cpp +struct View; +View redeclared_before_header_include(View a); // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} + #include "test_header.h" #include "Inputs/lifetime-analysis.h" @@ -74,6 +79,10 @@ View definition_before_header(View a) { return a; // expected-note {{param returned here}} } +View redeclared_before_header_include(View a) { + return a; // expected-note 2 {{param returned here}} +} + View return_view_directly(View a) { return a; // expected-note {{param returned here}} } >From 73adbd7bc23c7d7407e977fcd489282237913e0e Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Thu, 21 May 2026 21:36:36 +0300 Subject: [PATCH 3/6] change impl and update tests --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 128 ++++++++---------- ...afety-misplaced-lifetimebound-cross-tu.cpp | 14 ++ ...afety-misplaced-lifetimebound-intra-tu.cpp | 6 + .../Sema/warn-lifetime-safety-suggestions.cpp | 46 +++++++ 4 files changed, 126 insertions(+), 68 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index f6b116b2d03a6..38ad62b37df60 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -316,62 +316,57 @@ class LifetimeChecker { } } - std::pair<const FunctionDecl *, WarningScope> - getCanonicalFunctionDeclForAttr(const FunctionDecl *FDef) { + // Returns diagnostic targets for annotations on FDef: the canonical + // declaration and the earliest redeclaration in each other file. Each + // target is paired with the warning scope appropriate for its file. + llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2> + getTargetDeclsForAttr(const FunctionDecl *FDef) { if (!FDef) - return {nullptr, WarningScope::IntraTU}; + return {}; assert(FDef->isThisDeclarationADefinition() && "Expected FunctionDecl to be a definition"); const auto &SM = FDef->getASTContext().getSourceManager(); - const FileID DefFile = - SM.getFileID(SM.getExpansionLoc(FDef->getLocation())); - const FunctionDecl *CanonicalDecl = FDef->getCanonicalDecl(); - WarningScope Scope = WarningScope::IntraTU; - Scope = SM.getFileID(SM.getExpansionLoc(CanonicalDecl->getLocation())) != - DefFile - ? WarningScope::CrossTU - : WarningScope::IntraTU; + auto GetLoc = [&SM](const FunctionDecl *FD) { + return SM.getExpansionLoc(FD->getLocation()); + }; + auto GetFile = [&SM, &GetLoc](const FunctionDecl *FD) { + return SM.getFileID(GetLoc(FD)); + }; - return {CanonicalDecl, Scope}; - } + const FileID DefFile = GetFile(FDef); + llvm::SmallVector<std::pair<FileID, const FunctionDecl *>, 2> + EarliestDeclForFile{ + {GetFile(FDef->getCanonicalDecl()), FDef->getCanonicalDecl()}}; - std::pair<const CXXMethodDecl *, WarningScope> - getCanonicalDeclForAttr(const CXXMethodDecl *MDef) { - auto [CanonicalFDecl, Scope] = getCanonicalFunctionDeclForAttr(MDef); - return {cast_or_null<CXXMethodDecl>(CanonicalFDecl), Scope}; - } + auto AddCrossTUDecl = [&](const FunctionDecl *FD) { + FileID File = GetFile(FD); + if (File == DefFile) + return; + for (auto &[SeenFile, SeenFD] : EarliestDeclForFile) { + if (SeenFile != File) + continue; + if (SM.isBeforeInTranslationUnit(GetLoc(FD), GetLoc(SeenFD))) + SeenFD = FD; + return; + } + EarliestDeclForFile.push_back({File, FD}); + }; - std::pair<const ParmVarDecl *, WarningScope> - getCanonicalDeclForAttr(const FunctionDecl *FDef, const ParmVarDecl *PVDDef) { - auto [CanonicalFDecl, Scope] = getCanonicalFunctionDeclForAttr(FDef); - if (!CanonicalFDecl) - return {nullptr, Scope}; - return {CanonicalFDecl->getParamDecl(PVDDef->getFunctionScopeIndex()), - Scope}; - } + for (const FunctionDecl *Redecl : FDef->redecls()) { + AddCrossTUDecl(Redecl); + } - /// 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, - SourceManager &SM) { - if (!FD.isExternallyVisible()) - return nullptr; - const FileID DefinitionFile = SM.getFileID(FD.getLocation()); - for (const FunctionDecl *Redecl : FD.redecls()) - if (SM.getFileID(Redecl->getLocation()) != DefinitionFile) - return Redecl; - - return nullptr; - } + llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2> Targets; + for (auto [File, FD] : EarliestDeclForFile) { + WarningScope Scope = + File == DefFile ? WarningScope::IntraTU : WarningScope::CrossTU; + Targets.push_back({FD, Scope}); + } - static const FunctionDecl *getCrossTUDecl(const ParmVarDecl &PVD, - SourceManager &SM) { - if (const auto *FD = dyn_cast<FunctionDecl>(PVD.getDeclContext())) - return getCrossTUDecl(*FD, SM); - return nullptr; + return Targets; } void suggestWithScopeForParmVar(const ParmVarDecl *PVD, @@ -379,29 +374,20 @@ class LifetimeChecker { if (llvm::isa<const VarDecl *>(EscapeTarget)) return; - auto [CanonicalPVD, Scope] = - getCanonicalDeclForAttr(cast<FunctionDecl>(FD), PVD); - const auto *CrossTUFD = getCrossTUDecl(*PVD, AST.getSourceManager()); - const auto *CrossTUParm = - CrossTUFD ? CrossTUFD->getParamDecl(PVD->getFunctionScopeIndex()) - : nullptr; - - SemaHelper->suggestLifetimeboundToParmVar(Scope, CanonicalPVD, - EscapeTarget); - if (CrossTUParm && CrossTUParm != CanonicalPVD) - SemaHelper->suggestLifetimeboundToParmVar(WarningScope::CrossTU, - CrossTUParm, EscapeTarget); + for (auto [Decl, Scope] : getTargetDeclsForAttr(cast<FunctionDecl>(FD))) { + const auto *ParmToAnnotate = + Decl->getParamDecl(PVD->getFunctionScopeIndex()); + SemaHelper->suggestLifetimeboundToParmVar(Scope, ParmToAnnotate, + EscapeTarget); + } } void suggestWithScopeForImplicitThis(const CXXMethodDecl *MD, const Expr *EscapeExpr) { - auto [CanonicalDecl, Scope] = getCanonicalDeclForAttr(MD); - const auto *CrossTUDecl = getCrossTUDecl(*MD, AST.getSourceManager()); - SemaHelper->suggestLifetimeboundToImplicitThis(Scope, CanonicalDecl, - EscapeExpr); - if (CrossTUDecl && CrossTUDecl != CanonicalDecl) + for (auto [Decl, Scope] : getTargetDeclsForAttr(MD)) { SemaHelper->suggestLifetimeboundToImplicitThis( - WarningScope::CrossTU, cast<CXXMethodDecl>(CrossTUDecl), EscapeExpr); + Scope, cast<CXXMethodDecl>(Decl), EscapeExpr); + } } void suggestAnnotations() { @@ -458,13 +444,17 @@ class LifetimeChecker { const FunctionDecl *FDef = dyn_cast<FunctionDecl>(FD); if (!FDef) return; + + auto TargetDecls = getTargetDeclsForAttr(FDef); // Check if implicit 'this' has lifetimebound on definition but not on // declaration. 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 (auto [Decl, Scope] : TargetDecls) { + const auto *MDecl = cast<CXXMethodDecl>(Decl); + if (!getDirectImplicitObjectLifetimeBoundAttr(MDecl)) + SemaHelper->reportMisplacedLifetimebound(Scope, MDef, MDecl); + } // Check each parameter for explicit lifetimebound on definition but not on // declaration. @@ -472,9 +462,11 @@ class LifetimeChecker { 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); + for (auto [Decl, Scope] : TargetDecls) { + const auto *PDecl = Decl->getParamDecl(PDef->getFunctionScopeIndex()); + if (!PDecl->hasAttr<LifetimeBoundAttr>()) + SemaHelper->reportMisplacedLifetimebound(Scope, PDef, PDecl); + } } } 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..583fba6988573 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 @@ -14,8 +14,18 @@ struct HeaderS { 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}} }; +//--- cross_1.h +struct HeaderObj; +HeaderObj &multi_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}} + +//--- cross_2.h +struct HeaderObj; +HeaderObj &multi_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}} + //--- cross.cpp #include "cross.h" +#include "cross_1.h" +#include "cross_2.h" HeaderObj &header_param(HeaderObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}} return obj; @@ -24,3 +34,7 @@ HeaderObj &header_param(HeaderObj &obj [[clang::lifetimebound]]) { // expected-n HeaderObj &HeaderS::header_this() [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute appears here on the definition}} return data; } + +HeaderObj &multi_header_param(HeaderObj &obj [[clang::lifetimebound]]) { // expected-note 2 {{'lifetimebound' attribute appears here on the definition}} + return obj; +} 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..998e92b467819 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 @@ -9,6 +9,12 @@ MyObj &free_param(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lif return obj; } +MyObj &earliest_redecl_param(MyObj &obj); // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}} +MyObj &earliest_redecl_param(MyObj &obj); +MyObj &earliest_redecl_param(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}} + return obj; +} + 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}} diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp index 30d1896450236..d9aa308986913 100644 --- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp +++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp @@ -67,14 +67,40 @@ struct ReturnThisPointer { #endif // TEST_HEADER_H +//--- test_redecls_header.h + +View earliest_decl_in_header(View a); // expected-warning {{parameter in cross-TU function should be marked [[clang::lifetimebound]]}} +View earliest_decl_in_header(View a); + +View multi_redecl_one_file(View a); // expected-warning {{parameter in cross-TU function should be marked [[clang::lifetimebound]]}} +View multi_redecl_one_file(View a); +View multi_redecl_one_file(View a); + +View source_and_header(View a); // expected-warning {{parameter in cross-TU function should be marked [[clang::lifetimebound]]}} + +//--- test_redecls_header_1.h + +View in_two_headers(View a); // expected-warning {{parameter in cross-TU function should be marked [[clang::lifetimebound]]}} + +//--- test_redecls_header_2.h + +View in_two_headers(View a); // expected-warning {{parameter in cross-TU function should be marked [[clang::lifetimebound]]}} + //--- test_source.cpp struct View; View redeclared_before_header_include(View a); // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +View source_and_header(View a); // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} #include "test_header.h" +#include "test_redecls_header.h" +#include "test_redecls_header_1.h" +#include "test_redecls_header_2.h" #include "Inputs/lifetime-analysis.h" +View earliest_decl_in_source(View a); // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +View earliest_decl_in_source(View a); + View definition_before_header(View a) { return a; // expected-note {{param returned here}} } @@ -112,6 +138,26 @@ MyObj& return_unnamed_ref(MyObj& a, bool c) { return a; // expected-note {{param returned here}} } +View earliest_decl_in_header(View a) { + return a; // expected-note {{param returned here}} +} + +View earliest_decl_in_source(View a) { + return a; // expected-note {{param returned here}} +} + +View multi_redecl_one_file(View a) { + return a; // expected-note {{param returned here}} +} + +View in_two_headers(View a) { + return a; // expected-note 2 {{param returned here}} +} + +View source_and_header(View a) { + return a; // expected-note 2 {{param returned here}} +} + MyObj& return_reference(MyObj& a, // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} MyObj& b, // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} bool c) { >From 614c4efd151add2afaa01e2b05deac31a48e78c0 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Fri, 22 May 2026 11:48:55 +0300 Subject: [PATCH 4/6] cleanup logic a bit and a few nits --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 38ad62b37df60..388f9c116c645 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -316,9 +316,11 @@ class LifetimeChecker { } } - // Returns diagnostic targets for annotations on FDef: the canonical - // declaration and the earliest redeclaration in each other file. Each - // target is paired with the warning scope appropriate for its file. + // Returns declarations that should be annotated with lifetime attributes + // in order to annotate FDef: the canonical declaration and the earliest + // redeclarations in each other file. This defines the placement policy for + // lifetime annotations. Each target is paired with its corresponding warning + // scope. llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2> getTargetDeclsForAttr(const FunctionDecl *FDef) { if (!FDef) @@ -345,19 +347,15 @@ class LifetimeChecker { FileID File = GetFile(FD); if (File == DefFile) return; - for (auto &[SeenFile, SeenFD] : EarliestDeclForFile) { - if (SeenFile != File) - continue; - if (SM.isBeforeInTranslationUnit(GetLoc(FD), GetLoc(SeenFD))) - SeenFD = FD; - return; - } + for (auto [SeenFile, SeenFD] : EarliestDeclForFile) + if (SeenFile == File) + return; EarliestDeclForFile.push_back({File, FD}); }; - for (const FunctionDecl *Redecl : FDef->redecls()) { + llvm::SmallVector<const FunctionDecl *, 4> Redecls(FDef->redecls()); + for (const FunctionDecl *Redecl : llvm::reverse(Redecls)) AddCrossTUDecl(Redecl); - } llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2> Targets; for (auto [File, FD] : EarliestDeclForFile) { >From 65974ebc5fbbc0af718c0ead8b2d16af64305bf7 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Fri, 22 May 2026 12:49:15 +0300 Subject: [PATCH 5/6] simplify code --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 388f9c116c645..adb104fa978c1 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -339,31 +339,26 @@ class LifetimeChecker { }; const FileID DefFile = GetFile(FDef); - llvm::SmallVector<std::pair<FileID, const FunctionDecl *>, 2> - EarliestDeclForFile{ - {GetFile(FDef->getCanonicalDecl()), FDef->getCanonicalDecl()}}; + const FunctionDecl *CanonicalDecl = FDef->getCanonicalDecl(); + llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2> Targets{ + {CanonicalDecl, GetFile(CanonicalDecl) == DefFile + ? WarningScope::IntraTU + : WarningScope::CrossTU}}; auto AddCrossTUDecl = [&](const FunctionDecl *FD) { FileID File = GetFile(FD); if (File == DefFile) return; - for (auto [SeenFile, SeenFD] : EarliestDeclForFile) - if (SeenFile == File) + for (auto [SeenFD, _] : Targets) + if (GetFile(SeenFD) == File) return; - EarliestDeclForFile.push_back({File, FD}); + Targets.push_back({FD, WarningScope::CrossTU}); }; llvm::SmallVector<const FunctionDecl *, 4> Redecls(FDef->redecls()); for (const FunctionDecl *Redecl : llvm::reverse(Redecls)) AddCrossTUDecl(Redecl); - llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2> Targets; - for (auto [File, FD] : EarliestDeclForFile) { - WarningScope Scope = - File == DefFile ? WarningScope::IntraTU : WarningScope::CrossTU; - Targets.push_back({FD, Scope}); - } - return Targets; } >From bff670379d131b5f00bc27807f47261120835fab Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Fri, 22 May 2026 14:49:31 +0300 Subject: [PATCH 6/6] add comments and remove lambda --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index adb104fa978c1..53899251b9643 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -331,11 +331,8 @@ class LifetimeChecker { const auto &SM = FDef->getASTContext().getSourceManager(); - auto GetLoc = [&SM](const FunctionDecl *FD) { - return SM.getExpansionLoc(FD->getLocation()); - }; - auto GetFile = [&SM, &GetLoc](const FunctionDecl *FD) { - return SM.getFileID(GetLoc(FD)); + auto GetFile = [&SM](const FunctionDecl *FD) { + return SM.getFileID(SM.getExpansionLoc(FD->getLocation())); }; const FileID DefFile = GetFile(FDef); @@ -345,6 +342,8 @@ class LifetimeChecker { ? WarningScope::IntraTU : WarningScope::CrossTU}}; + // Find the earliest redeclaration in each file other than the definition + // file. auto AddCrossTUDecl = [&](const FunctionDecl *FD) { FileID File = GetFile(FD); if (File == DefFile) @@ -355,8 +354,10 @@ class LifetimeChecker { Targets.push_back({FD, WarningScope::CrossTU}); }; - llvm::SmallVector<const FunctionDecl *, 4> Redecls(FDef->redecls()); - for (const FunctionDecl *Redecl : llvm::reverse(Redecls)) + // We iterate in reverse order (from most recent to oldest) to find + // the first declaration in each file. + for (const FunctionDecl *Redecl : + llvm::reverse(llvm::to_vector(FDef->redecls()))) AddCrossTUDecl(Redecl); return Targets; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
