-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[clang-tidy] unnecessary-value-param: Allow moving of value arguments. #145871
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Some functions take an argument by value because it allows efficiently moving them to a function that takes by value. Do not pessimize that case.
@llvm/pr-subscribers-clang-tools-extra Author: Clement Courbet (legrosbuffle) ChangesSome functions take an argument by value because it allows efficiently moving them to a function that takes by value. Do not pessimize that case. Full diff: https://github.com/llvm/llvm-project/pull/145871.diff 3 Files Affected:
diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp
index d89c3a69fc841..d18a3c914bf93 100644
--- a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp
+++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp
@@ -41,6 +41,17 @@ bool hasLoopStmtAncestor(const DeclRefExpr &DeclRef, const Decl &Decl,
return Matches.empty();
}
+bool isArgOfStdMove(const DeclRefExpr &DeclRef, const Decl &Decl,
+ ASTContext &Context) {
+ auto Matches = match(
+ traverse(TK_AsIs, decl(forEachDescendant(callExpr(
+ callee(functionDecl(hasName("std::move"))),
+ hasArgument(0, ignoringParenImpCasts(declRefExpr(
+ equalsNode(&DeclRef)))))))),
+ Decl, Context);
+ return Matches.empty();
+}
+
} // namespace
UnnecessaryValueParamCheck::UnnecessaryValueParamCheck(
@@ -100,6 +111,11 @@ void UnnecessaryValueParamCheck::check(const MatchFinder::MatchResult &Result) {
auto CanonicalType = Param->getType().getCanonicalType();
const auto &DeclRefExpr = **AllDeclRefExprs.begin();
+ // The reference is in a call to `std::move`, do not warn.
+ if (isArgOfStdMove(DeclRefExpr, *Function, *Result.Context)) {
+ return;
+ }
+
if (!hasLoopStmtAncestor(DeclRefExpr, *Function, *Result.Context) &&
((utils::type_traits::hasNonTrivialMoveConstructor(CanonicalType) &&
utils::decl_ref_expr::isCopyConstructorArgument(
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 934d52086b3b9..2413b742dc480 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -294,6 +294,7 @@ Changes in existing checks
to avoid matching usage of functions within the current compilation unit.
Added an option `IgnoreCoroutines` with the default value `true` to
suppress this check for coroutines where passing by reference may be unsafe.
+ Fix false positive on by-value parameters that are only moved.
- Improved :doc:`readability-convert-member-functions-to-static
<clang-tidy/checks/readability/convert-member-functions-to-static>` check by
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param.cpp
index 88c491ea1eabc..a9af4f60561a4 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param.cpp
@@ -344,6 +344,10 @@ void ReferenceFunctionByCallingIt() {
PositiveMessageAndFixAsFunctionIsCalled(ExpensiveToCopyType());
}
+void NegativeMoved(ExpensiveToCopyType A) {
+ A Copy = std::move(A);
+}
+
// Virtual method overrides of dependent types cannot be recognized unless they
// are marked as override or final. Test that check is not triggered on methods
// marked with override or final.
|
@llvm/pr-subscribers-clang-tidy Author: Clement Courbet (legrosbuffle) ChangesSome functions take an argument by value because it allows efficiently moving them to a function that takes by value. Do not pessimize that case. Full diff: https://github.com/llvm/llvm-project/pull/145871.diff 3 Files Affected:
diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp
index d89c3a69fc841..d18a3c914bf93 100644
--- a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp
+++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp
@@ -41,6 +41,17 @@ bool hasLoopStmtAncestor(const DeclRefExpr &DeclRef, const Decl &Decl,
return Matches.empty();
}
+bool isArgOfStdMove(const DeclRefExpr &DeclRef, const Decl &Decl,
+ ASTContext &Context) {
+ auto Matches = match(
+ traverse(TK_AsIs, decl(forEachDescendant(callExpr(
+ callee(functionDecl(hasName("std::move"))),
+ hasArgument(0, ignoringParenImpCasts(declRefExpr(
+ equalsNode(&DeclRef)))))))),
+ Decl, Context);
+ return Matches.empty();
+}
+
} // namespace
UnnecessaryValueParamCheck::UnnecessaryValueParamCheck(
@@ -100,6 +111,11 @@ void UnnecessaryValueParamCheck::check(const MatchFinder::MatchResult &Result) {
auto CanonicalType = Param->getType().getCanonicalType();
const auto &DeclRefExpr = **AllDeclRefExprs.begin();
+ // The reference is in a call to `std::move`, do not warn.
+ if (isArgOfStdMove(DeclRefExpr, *Function, *Result.Context)) {
+ return;
+ }
+
if (!hasLoopStmtAncestor(DeclRefExpr, *Function, *Result.Context) &&
((utils::type_traits::hasNonTrivialMoveConstructor(CanonicalType) &&
utils::decl_ref_expr::isCopyConstructorArgument(
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 934d52086b3b9..2413b742dc480 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -294,6 +294,7 @@ Changes in existing checks
to avoid matching usage of functions within the current compilation unit.
Added an option `IgnoreCoroutines` with the default value `true` to
suppress this check for coroutines where passing by reference may be unsafe.
+ Fix false positive on by-value parameters that are only moved.
- Improved :doc:`readability-convert-member-functions-to-static
<clang-tidy/checks/readability/convert-member-functions-to-static>` check by
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param.cpp
index 88c491ea1eabc..a9af4f60561a4 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param.cpp
@@ -344,6 +344,10 @@ void ReferenceFunctionByCallingIt() {
PositiveMessageAndFixAsFunctionIsCalled(ExpensiveToCopyType());
}
+void NegativeMoved(ExpensiveToCopyType A) {
+ A Copy = std::move(A);
+}
+
// Virtual method overrides of dependent types cannot be recognized unless they
// are marked as override or final. Test that check is not triggered on methods
// marked with override or final.
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
Shouldn't this case be already covered by void PositiveMoveOnCopyConstruction(ExpensiveMovableType E) {
auto F = E;
// CHECK-MESSAGES: [[@LINE-1]]:12: warning: parameter 'E' is passed by value and only copied once; consider moving it to avoid unnecessary copies [performance-unnecessary-value-param]
// CHECK-FIXES: auto F = std::move(E);
} and void PositiveConstRefNotMoveConstructible(ExpensiveToCopyType T) {
// CHECK-MESSAGES: [[@LINE-1]]:63: warning: the parameter 'T' is copied
// CHECK-FIXES: void PositiveConstRefNotMoveConstructible(const ExpensiveToCopyType& T) {
auto U = T;
} We have a difference when the check would suggest |
If we want to change the behavior of the check, I'd suggest making an option that will force "always move", no matter how expensive is the object to copy |
No, this case is different: note that the With
If the caller passes an lvalue (case L), there is one copy (in the caller) and one move (in The check currently suggests:
With this code, we always have a copy in both cases L and R, because Given that move is typically much cheaper than copy, there should never be a reason to pick the second version, especially given tha the user expressed their intent to move. |
To sum up: For me it this patch adds inconsistency for the check: if we have void NegativeMoved(ExpensiveToCopyType A) {
ExpensiveToCopyType Copy = std::move(A); // no warning
} when I remove void NegativeMoved(ExpensiveToCopyType A) { // use const &
ExpensiveToCopyType Copy = A;
} I think a better way is to provide the user an option to specify that he always want to use |
Sorry for accidentally closing, my mouse slipped:( |
Note that in the test I wrote I called a ctor for simplicity, but in general I want to avoid warning on anything that looks like this:
The move ctor just happens to be a specific case of If we wanted to be fully consistent we'd have to turn:
into:
We've actually had such a check internally for a few years (suggesting to |
See #53489 and #139525 about such a check. I think moving (no pun intended) the logic for suggesting using This is also the case with other checks which combine multiple functionalities - which makes sense if you want a "standalone" check which gives you the final state of the fixed code. But it might lead to aforementioned overlap and duplicated logic, and at some point possibly even inconsistencies. IMO only having incremental changes and requiring multiple runs until you have clean code is fine. |
Some functions take an argument by value because it allows efficiently moving them to a function that takes by value. Do not pessimize that case.