This is an archive of the discontinued LLVM Phabricator instance.

[clang-tidy] performance-unnecessary-copy-initialization: Always allow std::function to be copied.
ClosedPublic

Authored by flx on Oct 13 2020, 10:39 AM.

Details

Summary

Since its call operator is const but can modify the state of its underlying
functor we cannot tell whether the copy is necessary or not.

This avoids false positives.

Diff Detail

Event Timeline

flx created this revision.Oct 13 2020, 10:39 AM
Herald added a project: Restricted Project. · View Herald TranscriptOct 13 2020, 10:39 AM
flx requested review of this revision.Oct 13 2020, 10:39 AM
flx retitled this revision from Always allow std::function to be copied. to [clang-tidy] performance-unnecessary-copy-initialization: Always allow std::function to be copied..Oct 13 2020, 10:40 AM
lebedev.ri added inline comments.
clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp
41

Just put it here?

flx added inline comments.Oct 13 2020, 11:09 AM
clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp
41

I tried this first, but this list is substring matched against only on the non-qualified type name, so std::function would not match anything and if we added "function" here it would match many other types that contain the word function.

gribozavr2 accepted this revision.Oct 15 2020, 9:39 PM
gribozavr2 added inline comments.
clang-tools-extra/test/clang-tidy/checkers/performance-unnecessary-copy-initialization.cpp
409

Could you add a nested inline namespace to better imitate what declarations look like in libc++?

This revision is now accepted and ready to land.Oct 15 2020, 9:39 PM
lebedev.ri added inline comments.Oct 15 2020, 11:19 PM
clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp
41

I would personally say it's a bug, especially because i personally don't like hardcoded "choices" that are impossible to change.

flx updated this revision to Diff 298745.Oct 16 2020, 1:46 PM

Add more complete fake version of std::function.

flx added inline comments.Oct 16 2020, 1:47 PM
clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp
41

Are you referring to how the matching of "AllowedTypes" works or the fact that std::function causes false positives?

The latter is currently a bug and by excluding it now its severity is downgraded from bug to missing feature. Is your concern that you would like to have std::function be matched even if it is a false positive?

Regarding "AllowedTypes" matching I'm also surprised to find it matches substrings without namespaces as the name doesn't communicate this and makes it an imprecise net to cast. Changing it could break existing users that have type substrings configured though.

clang-tools-extra/test/clang-tidy/checkers/performance-unnecessary-copy-initialization.cpp
409

I'm not sure I follow. I looked through the other tests that declare a std function and copied the declaration from modernize-avoid-bind.cpp.

How does this type alias and typedef, In theory that should also be handled.

using Functor = std::function<...>;
Functor Copy = Orig; // No warning.
clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp
35–40

It's better to use node.isInStdNamespace() instead of checking the qualified name as its designed for that very purpose. Is should probably take a CXXRecordDecl instead of a NamedDecl aswell.

gribozavr2 added inline comments.Oct 16 2020, 11:59 PM
clang-tools-extra/test/clang-tidy/checkers/performance-unnecessary-copy-initialization.cpp
409

libc++ declarations look like this:

namespace std {
inline namespace __1 {
template<...> struct function...
} // __1
} // std

The inline namespace in the middle often trips up declaration matching in checkers. And yes, many other tests don't imitate this pattern, and are often broken with libc++. Those tests should be improved.

njames93 added inline comments.Oct 17 2020, 12:11 AM
clang-tools-extra/test/clang-tidy/checkers/performance-unnecessary-copy-initialization.cpp
409

@flx Thats the reason why its advisable to use Decl::isInStdNamespace as it will handle inline namespaces.

Come to think of it, this is a pretty illogical way to solve this problem, just append ::std::function to the AllowedTypes vector in registerMatchers and be do with it. Will require dropping the const Qualifier on AllowedTypes, but aside from that it is a much simpler fix.
The has(Any)Name matcher has logic for skipping inline namespaces.

Come to think of it, this is a pretty illogical way to solve this problem, just append ::std::function to the AllowedTypes vector in registerMatchers and be do with it. Will require dropping the const Qualifier on AllowedTypes, but aside from that it is a much simpler fix.
The has(Any)Name matcher has logic for skipping inline namespaces.

This also seems like a reasonable alternative, to me.

clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp
35

There's no need for this matcher -- hasName("::std::function") is the correct way to test for this.

flx updated this revision to Diff 299471.Oct 20 2020, 2:21 PM

Use hasName matcher on the declaration of the canonical type.

flx marked 2 inline comments as done.Oct 20 2020, 2:29 PM

Thank you all for the input!

How does this type alias and typedef, In theory that should also be handled.

using Functor = std::function<...>;
Functor Copy = Orig; // No warning.

Good point. I added test cases that cover this and motivated the use of the hasName() matcher on the canonical type.

Come to think of it, this is a pretty illogical way to solve this problem, just append ::std::function to the AllowedTypes vector in registerMatchers and be do with it. Will require dropping the const Qualifier on AllowedTypes, but aside from that it is a much simpler fix.
The has(Any)Name matcher has logic for skipping inline namespaces.

AllowedTypes is currently matched using a regular expression on only the not fully qualified name of the NamedDecl:

https://github.com/llvm/llvm-project/blob/343410d1cc154db99b7858e0a9c3ffd86ad94e45/clang-tools-extra/clang-tidy/utils/Matchers.h#L49

This is why this approach didn't work.

clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp
35–40

hasName() on the canonical type worked and handled type aliases as well.

clang-tools-extra/test/clang-tidy/checkers/performance-unnecessary-copy-initialization.cpp
409

Thanks for the extra explanation and sample code. This is done now and works.

flx updated this revision to Diff 299684.Oct 21 2020, 7:39 AM

Fix compile errors.

aaron.ballman accepted this revision.Oct 21 2020, 11:37 AM

LGTM with an extra testing request (that should hopefully just work for you).

clang-tools-extra/test/clang-tidy/checkers/performance-unnecessary-copy-initialization.cpp
445

Can you add one more test case to be sure we really only care about The std::function and not some pathological code like:

namespace clever {
namespace std {
template <class R, class... Args>
class function {}; // clever::std::function is not ::std::function
}
}
}
flx updated this revision to Diff 299794.Oct 21 2020, 1:28 PM

Add fake std function to ensure it is still matched.

flx marked an inline comment as done.Oct 21 2020, 1:37 PM

Added a fake std function that still triggers the check.

This revision was automatically updated to reflect the committed changes.