Skip to content

Commit 69a5cf5

Browse files
authored
[LifetimeSafety] Extend suggestions for lifetimebound to also warn on canonical declarations (llvm#198784)
With this patch, we suggest adding the `clang::lifetimebound` attribute on the canonical declaration and on the earliest redeclaration in each other file, preserving diagnostics for declarations visible from other translation units while avoiding duplicate suggestions within the same file. Fixes llvm#198624 Fixes llvm#198628
1 parent 074b6be commit 69a5cf5

5 files changed

Lines changed: 152 additions & 92 deletions

clang/lib/Analysis/LifetimeSafety/Checker.cpp

Lines changed: 61 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -316,103 +316,83 @@ class LifetimeChecker {
316316
}
317317
}
318318

319-
std::pair<const FunctionDecl *, WarningScope>
320-
getCanonicalFunctionDeclForAttr(const FunctionDecl *FDef) {
319+
// Returns declarations that should be annotated with lifetime attributes
320+
// in order to annotate FDef: the canonical declaration and the earliest
321+
// redeclarations in each other file. This defines the placement policy for
322+
// lifetime annotations. Each target is paired with its corresponding warning
323+
// scope.
324+
llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2>
325+
getTargetDeclsForAttr(const FunctionDecl *FDef) {
321326
if (!FDef)
322-
return {nullptr, WarningScope::IntraTU};
327+
return {};
323328

324329
assert(FDef->isThisDeclarationADefinition() &&
325330
"Expected FunctionDecl to be a definition");
326331

327332
const auto &SM = FDef->getASTContext().getSourceManager();
328-
const FileID DefFile =
329-
SM.getFileID(SM.getExpansionLoc(FDef->getLocation()));
330-
const FunctionDecl *CanonicalDecl = FDef->getCanonicalDecl();
331-
WarningScope Scope = WarningScope::IntraTU;
332-
333-
Scope = SM.getFileID(SM.getExpansionLoc(CanonicalDecl->getLocation())) !=
334-
DefFile
335-
? WarningScope::CrossTU
336-
: WarningScope::IntraTU;
337-
338-
return {CanonicalDecl, Scope};
339-
}
340333

341-
std::pair<const CXXMethodDecl *, WarningScope>
342-
getCanonicalDeclForAttr(const CXXMethodDecl *MDef) {
343-
auto [CanonicalFDecl, Scope] = getCanonicalFunctionDeclForAttr(MDef);
344-
return {cast_or_null<CXXMethodDecl>(CanonicalFDecl), Scope};
345-
}
334+
auto GetFile = [&SM](const FunctionDecl *FD) {
335+
return SM.getFileID(SM.getExpansionLoc(FD->getLocation()));
336+
};
346337

347-
std::pair<const ParmVarDecl *, WarningScope>
348-
getCanonicalDeclForAttr(const FunctionDecl *FDef, const ParmVarDecl *PVDDef) {
349-
auto [CanonicalFDecl, Scope] = getCanonicalFunctionDeclForAttr(FDef);
350-
if (!CanonicalFDecl)
351-
return {nullptr, Scope};
352-
return {CanonicalFDecl->getParamDecl(PVDDef->getFunctionScopeIndex()),
353-
Scope};
354-
}
338+
const FileID DefFile = GetFile(FDef);
339+
const FunctionDecl *CanonicalDecl = FDef->getCanonicalDecl();
340+
llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2> Targets{
341+
{CanonicalDecl, GetFile(CanonicalDecl) == DefFile
342+
? WarningScope::IntraTU
343+
: WarningScope::CrossTU}};
344+
345+
// Find the earliest redeclaration in each file other than the definition
346+
// file.
347+
auto AddCrossTUDecl = [&](const FunctionDecl *FD) {
348+
FileID File = GetFile(FD);
349+
if (File == DefFile)
350+
return;
351+
for (auto [SeenFD, _] : Targets)
352+
if (GetFile(SeenFD) == File)
353+
return;
354+
Targets.push_back({FD, WarningScope::CrossTU});
355+
};
355356

356-
/// Returns the declaration of a function that is visible across translation
357-
/// units, if such a declaration exists and is different from the definition.
358-
static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
359-
SourceManager &SM) {
360-
if (!FD.isExternallyVisible())
361-
return nullptr;
362-
const FileID DefinitionFile = SM.getFileID(FD.getLocation());
363-
for (const FunctionDecl *Redecl : FD.redecls())
364-
if (SM.getFileID(Redecl->getLocation()) != DefinitionFile)
365-
return Redecl;
366-
367-
return nullptr;
368-
}
357+
// We iterate in reverse order (from most recent to oldest) to find
358+
// the first declaration in each file.
359+
for (const FunctionDecl *Redecl :
360+
llvm::reverse(llvm::to_vector(FDef->redecls())))
361+
AddCrossTUDecl(Redecl);
369362

370-
static const FunctionDecl *getCrossTUDecl(const ParmVarDecl &PVD,
371-
SourceManager &SM) {
372-
if (const auto *FD = dyn_cast<FunctionDecl>(PVD.getDeclContext()))
373-
return getCrossTUDecl(*FD, SM);
374-
return nullptr;
363+
return Targets;
375364
}
376365

377-
static void suggestWithScopeForParmVar(LifetimeSafetySemaHelper *SemaHelper,
378-
const ParmVarDecl *PVD,
379-
SourceManager &SM,
380-
EscapingTarget EscapeTarget) {
366+
void suggestWithScopeForParmVar(const ParmVarDecl *PVD,
367+
EscapingTarget EscapeTarget) {
381368
if (llvm::isa<const VarDecl *>(EscapeTarget))
382369
return;
383370

384-
if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM))
385-
SemaHelper->suggestLifetimeboundToParmVar(
386-
WarningScope::CrossTU,
387-
CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()),
388-
EscapeTarget);
389-
else
390-
SemaHelper->suggestLifetimeboundToParmVar(WarningScope::IntraTU, PVD,
371+
for (auto [Decl, Scope] : getTargetDeclsForAttr(cast<FunctionDecl>(FD))) {
372+
const auto *ParmToAnnotate =
373+
Decl->getParamDecl(PVD->getFunctionScopeIndex());
374+
SemaHelper->suggestLifetimeboundToParmVar(Scope, ParmToAnnotate,
391375
EscapeTarget);
376+
}
392377
}
393378

394-
static void
395-
suggestWithScopeForImplicitThis(LifetimeSafetySemaHelper *SemaHelper,
396-
const CXXMethodDecl *MD, SourceManager &SM,
397-
const Expr *EscapeExpr) {
398-
if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*MD, SM))
379+
void suggestWithScopeForImplicitThis(const CXXMethodDecl *MD,
380+
const Expr *EscapeExpr) {
381+
for (auto [Decl, Scope] : getTargetDeclsForAttr(MD)) {
399382
SemaHelper->suggestLifetimeboundToImplicitThis(
400-
WarningScope::CrossTU, cast<CXXMethodDecl>(CrossTUDecl), EscapeExpr);
401-
else
402-
SemaHelper->suggestLifetimeboundToImplicitThis(WarningScope::IntraTU, MD,
403-
EscapeExpr);
383+
Scope, cast<CXXMethodDecl>(Decl), EscapeExpr);
384+
}
404385
}
405386

406387
void suggestAnnotations() {
407388
if (!SemaHelper)
408389
return;
409-
SourceManager &SM = AST.getSourceManager();
410390
for (auto [Target, EscapeTarget] : AnnotationWarningsMap) {
411391
if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>())
412-
suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeTarget);
392+
suggestWithScopeForParmVar(PVD, EscapeTarget);
413393
else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {
414394
if (const auto *EscapeExpr = EscapeTarget.dyn_cast<const Expr *>())
415-
suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr);
395+
suggestWithScopeForImplicitThis(MD, EscapeExpr);
416396
else
417397
llvm_unreachable("Implicit this can only escape via Expr (return)");
418398
}
@@ -458,23 +438,29 @@ class LifetimeChecker {
458438
const FunctionDecl *FDef = dyn_cast<FunctionDecl>(FD);
459439
if (!FDef)
460440
return;
441+
442+
auto TargetDecls = getTargetDeclsForAttr(FDef);
461443
// Check if implicit 'this' has lifetimebound on definition but not on
462444
// declaration.
463445
if (const auto *MDef = dyn_cast<CXXMethodDecl>(FDef);
464446
MDef && getDirectImplicitObjectLifetimeBoundAttr(MDef))
465-
if (auto [MDecl, Scope] = getCanonicalDeclForAttr(MDef);
466-
MDecl && !getDirectImplicitObjectLifetimeBoundAttr(MDecl))
467-
SemaHelper->reportMisplacedLifetimebound(Scope, MDef, MDecl);
447+
for (auto [Decl, Scope] : TargetDecls) {
448+
const auto *MDecl = cast<CXXMethodDecl>(Decl);
449+
if (!getDirectImplicitObjectLifetimeBoundAttr(MDecl))
450+
SemaHelper->reportMisplacedLifetimebound(Scope, MDef, MDecl);
451+
}
468452

469453
// Check each parameter for explicit lifetimebound on definition but not on
470454
// declaration.
471455
for (const auto *PDef : FDef->parameters()) {
472456
const auto *Attr = PDef->getAttr<LifetimeBoundAttr>();
473457
if (!Attr || Attr->isImplicit())
474458
continue;
475-
if (auto [PDecl, Scope] = getCanonicalDeclForAttr(FDef, PDef);
476-
PDecl && !PDecl->hasAttr<LifetimeBoundAttr>())
477-
SemaHelper->reportMisplacedLifetimebound(Scope, PDef, PDecl);
459+
for (auto [Decl, Scope] : TargetDecls) {
460+
const auto *PDecl = Decl->getParamDecl(PDef->getFunctionScopeIndex());
461+
if (!PDecl->hasAttr<LifetimeBoundAttr>())
462+
SemaHelper->reportMisplacedLifetimebound(Scope, PDef, PDecl);
463+
}
478464
}
479465
}
480466

clang/test/Sema/warn-lifetime-safety-fixits.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,11 @@ int *arr_default(int a[2] = nullptr) {
6969
return a;
7070
}
7171

72-
// FIXME: Iterate over redecls and add [[clang::lifetimebound]]
7372
View multi_decl(View a);
73+
// CHECK: :[[@LINE-1]]:17: warning: parameter in intra-TU function should be marked
74+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:23-[[@LINE-2]]:23}:" {{\[\[}}clang::lifetimebound]]"
7475
View multi_decl(View a);
7576
View multi_decl(View a) {
76-
// CHECK: :[[@LINE-1]]:17: warning: parameter in intra-TU function should be marked
77-
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:23-[[@LINE-2]]:23}:" {{\[\[}}clang::lifetimebound]]"
7877
return a;
7978
}
8079

@@ -145,10 +144,10 @@ struct OutOfLine {
145144
OutOfLine() {}
146145
~OutOfLine() {}
147146
const OutOfLine &get() const;
147+
// CHECK: :[[@LINE-1]]:31: warning: implicit this in intra-TU function should be marked
148+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:31-[[@LINE-2]]:31}:" {{\[\[}}clang::lifetimebound]]"
148149
};
149150
const OutOfLine &OutOfLine::get() const {
150-
// CHECK: :[[@LINE-1]]:40: warning: implicit this in intra-TU function should be marked
151-
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:40-[[@LINE-2]]:40}:" {{\[\[}}clang::lifetimebound]]"
152151
return *this;
153152
}
154153

clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-cross-tu.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,18 @@ struct HeaderS {
1414
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}}
1515
};
1616

17+
//--- cross_1.h
18+
struct HeaderObj;
19+
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}}
20+
21+
//--- cross_2.h
22+
struct HeaderObj;
23+
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}}
24+
1725
//--- cross.cpp
1826
#include "cross.h"
27+
#include "cross_1.h"
28+
#include "cross_2.h"
1929

2030
HeaderObj &header_param(HeaderObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}}
2131
return obj;
@@ -24,3 +34,7 @@ HeaderObj &header_param(HeaderObj &obj [[clang::lifetimebound]]) { // expected-n
2434
HeaderObj &HeaderS::header_this() [[clang::lifetimebound]] { // expected-note {{'lifetimebound' attribute appears here on the definition}}
2535
return data;
2636
}
37+
38+
HeaderObj &multi_header_param(HeaderObj &obj [[clang::lifetimebound]]) { // expected-note 2 {{'lifetimebound' attribute appears here on the definition}}
39+
return obj;
40+
}

clang/test/Sema/warn-lifetime-safety-misplaced-lifetimebound-intra-tu.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ MyObj &free_param(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lif
99
return obj;
1010
}
1111

12+
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}}
13+
MyObj &earliest_redecl_param(MyObj &obj);
14+
MyObj &earliest_redecl_param(MyObj &obj [[clang::lifetimebound]]) { // expected-note {{'lifetimebound' attribute appears here on the definition}}
15+
return obj;
16+
}
17+
1218
struct S {
1319
MyObj data;
1420
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}}

0 commit comments

Comments
 (0)