[LifetimeSafety] Extend suggestions for lifetimebound to also warn on canonical declarations #198784
Conversation
|
@usx95 Label was not added to this for some reason :( edit: nvm, the bot just took longer this time. |
|
@llvm/pr-subscribers-clang-temporal-safety @llvm/pr-subscribers-clang Author: NeKon69 ChangesWith this patch, we suggest adding the Fixes #198624 Full diff: https://github.com/llvm/llvm-project/pull/198784.diff 3 Files Affected:
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}}
}
|
|
Maybe I was missing something, but I am not sure if this is the right approach. The canonical declaration is the first declaration in the redecl chain. But this does not necessarily mean that the first declaration is the one that is visible to other TUs. Consider: Here, the canonical decl would be the one before the include, but the cross-tu decl would be the one in the header file. |
|
Yes, but I think we will change that with #198628. I can instead close that issue first, then work on this. |
|
Oh, as per the comment on the issue, we probably want users to both annotate the canonical decl and the decl that is visible from other TUs. In most cases these two are the same, but not always. |
|
Yeah, but that is a separate change from this one. If you would prefer, I can close the other issue first. |
|
If we still want to enforce that the declaration in the header needs the annotation, we still need |
|
I am going to instead report both locations (if different), would that be better? |
|
Yeah, I think I would be happy with that! |
getCrossTUDecl with getCanonicalDeclForAttrlifetimebound to also warn on canonical declarations
|
@Xazax-hun could you review this, please? |
There was a problem hiding this comment.
Functional/Policy changes:
getCrossTUDecl currently seems to give only the single last decl which is in a separate file.
- We should prefer the earliest decl in the file.
- It is possible that these are available across multiple such files.
So the final target list would be the canonical decl + all first decls in each different file. These would have repeated warnings but should be fine ?
Code structure.
getCanonicalFunctionDeclForAttr and getCrossTUDecl are also quite similar. I would prefer to have a single place dictating the annotation placement policy.
I would suggest to merge these functions to abstract this into a function getTargetDeclsForAttr which returns a "list of target decls" to be annotated given an input decl. The result could have other attributes like xTU/intra-TU, definition/decl, etc.
WDYT @Xazax-hun
This sounds great to me! |
That's what I also had in mind originally for solving #198628, but I guess we could merge these issues together in this PR? |
|
I have implemented a single function that returns a vector of function decls to annotate. |
|
I updated the code. Unfortunately, you cannot directly iterate over reversed redeclarations because they are not bidirectional, so I had to store the redeclarations in a local vector first. I do not think this should be too expensive, though. |
| ? WarningScope::CrossTU | ||
| : WarningScope::IntraTU; | ||
| auto GetLoc = [&SM](const FunctionDecl *FD) { | ||
| return SM.getExpansionLoc(FD->getLocation()); |
There was a problem hiding this comment.
Hmm. I think this is technically correct, expansion loc determines where is this declaration actually visible. But it is also a bit hard for the users to understand what is going on. Not for this PR, but whenever we use an expansion loc, it would be nice to have both the expansion and the spelling loc in the diagnostic via a note (when they differ). But this is absolutely a small thing for the future, should not block anything here.
There was a problem hiding this comment.
I just realized, do we not already attach a note when the expression was expended from a macro?
Example:
https://godbolt.org/z/r8W86M4Tj
usx95
left a comment
There was a problem hiding this comment.
Looks great. Thanks.
Last few nits.
|
I will land this as there are approvals with no blocking comments. |
|
Some of the buildbots are failing after his commit (e.g., Arm MSan: https://lab.llvm.org/staging/#/builders/7/builds/1733; Arm ASan: https://lab.llvm.org/staging/#/builders/42/builds/4748). Could you please take a look? e.g., |
|
I've drafted a small fix: #199455 |
With this patch, we suggest adding the
clang::lifetimeboundattribute 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 #198624
Fixes #198628