-
Notifications
You must be signed in to change notification settings - Fork 16.2k
[clang-tidy] Add performance-redundant-lookup check #125420
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
Closed
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
78390b2
[clang-tidy] Add performance-redundant-lookup check
steakhal ae03a45
[clang-tidy][NFC] Mock set, map, memory, utility and functional headers
steakhal 09b6c50
Fix doc syntax
steakhal 67cbabf
Swap the Limitations and Options sections
steakhal 691da4a
Apply suggestions from code review
steakhal 7aadfd9
Apply suggestions from code review
steakhal d9d3984
NFC Sync check desc with ReleaseNotes
steakhal 93dd6c7
Use SmallPtrSet<2> for representing a lookup group
steakhal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
clang-tools-extra/clang-tidy/performance/RedundantLookupCheck.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| //===--- RedundantLookupCheck.cpp - clang-tidy ----------------------------===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "RedundantLookupCheck.h" | ||
| #include "../utils/OptionsUtils.h" | ||
| #include "clang/AST/ASTContext.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
| #include "clang/ASTMatchers/ASTMatchers.h" | ||
| #include "llvm/ADT/STLExtras.h" | ||
| #include "llvm/ADT/SmallVector.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include "llvm/Support/Regex.h" | ||
|
|
||
| using namespace clang::ast_matchers; | ||
|
|
||
| namespace clang::tidy::performance { | ||
|
|
||
| static constexpr auto DefaultContainerNameRegex = "set|map"; | ||
|
|
||
| static const llvm::StringRef DefaultLookupMethodNames = | ||
| llvm::StringLiteral( // | ||
| "at;" | ||
| "contains;" | ||
| "count;" | ||
| "find_as;" | ||
| "find;" | ||
| // These are tricky, as they take the "key" at different places. | ||
| // They sometimes bundle up the key and the value together in a pair. | ||
| // "emplace;" | ||
| // "insert_or_assign;" | ||
| // "insert;" | ||
| // "try_emplace;" | ||
| ) | ||
| .drop_back(); // Drops the last semicolon. | ||
|
|
||
| RedundantLookupCheck::RedundantLookupCheck(StringRef Name, | ||
| ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context), | ||
| ContainerNameRegex( | ||
| Options.get("ContainerNameRegex", DefaultContainerNameRegex)), | ||
| LookupMethodNames(utils::options::parseStringList( | ||
| Options.get("LookupMethodNames", DefaultLookupMethodNames))) {} | ||
|
|
||
| void RedundantLookupCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | ||
| Options.store(Opts, "ContainerNameRegex", ContainerNameRegex); | ||
| Options.store(Opts, "LookupMethodNames", | ||
| utils::options::serializeStringList(LookupMethodNames)); | ||
| } | ||
|
|
||
| namespace { | ||
| /// Checks if any of the ends of the source range is in a macro expansion. | ||
| AST_MATCHER(Expr, hasMacroSourceRange) { | ||
| SourceRange R = Node.getSourceRange(); | ||
| return R.getBegin().isMacroID() || R.getEnd().isMacroID(); | ||
| } | ||
| } // namespace | ||
|
|
||
| static constexpr const char *ObjKey = "obj"; | ||
| static constexpr const char *LookupKey = "key"; | ||
| static constexpr const char *LookupCallKey = "lookup"; | ||
| static constexpr const char *EnclosingFnKey = "fn"; | ||
|
|
||
| void RedundantLookupCheck::registerMatchers(MatchFinder *Finder) { | ||
| auto MatchesContainerNameRegex = | ||
| matchesName(ContainerNameRegex, llvm::Regex::IgnoreCase); | ||
|
|
||
| // Match that the expression is a record type with a name that contains "map" | ||
| // or "set". | ||
| auto RecordCalledMapOrSet = | ||
| expr(ignoringImpCasts(hasType(hasUnqualifiedDesugaredType(recordType( | ||
| hasDeclaration(namedDecl(MatchesContainerNameRegex))))))) | ||
| .bind(ObjKey); | ||
|
|
||
| auto SubscriptCall = | ||
| cxxOperatorCallExpr(hasOverloadedOperatorName("[]"), argumentCountIs(2), | ||
| hasArgument(0, RecordCalledMapOrSet), | ||
| hasArgument(1, expr().bind(LookupKey))); | ||
|
|
||
| auto LookupMethodCalls = | ||
| cxxMemberCallExpr(on(RecordCalledMapOrSet), argumentCountIs(1), | ||
| hasArgument(0, expr().bind(LookupKey)), | ||
| callee(cxxMethodDecl(hasAnyName(LookupMethodNames)))); | ||
|
|
||
| // Match any lookup or subscript calls that are not in a macro expansion. | ||
| auto AnyLookup = callExpr(unless(hasMacroSourceRange()), | ||
| anyOf(SubscriptCall, LookupMethodCalls)) | ||
| .bind(LookupCallKey); | ||
|
|
||
| // We need to collect all lookups in a function to be able to report them in | ||
| // batches. | ||
| Finder->addMatcher( | ||
| functionDecl(hasBody(compoundStmt(forEachDescendant(AnyLookup)))) | ||
| .bind(EnclosingFnKey), | ||
| this); | ||
| } | ||
|
|
||
| /// Hash the container object expr along with the key used for lookup and the | ||
| /// enclosing function together. | ||
| static unsigned hashLookupEvent(const ASTContext &Ctx, | ||
| const FunctionDecl *EnclosingFn, | ||
| const Expr *LookupKey, | ||
| const Expr *ContainerObject) { | ||
| llvm::FoldingSetNodeID ID; | ||
| ID.AddPointer(EnclosingFn); | ||
|
|
||
| LookupKey->Profile(ID, Ctx, /*Canonical=*/true, | ||
| /*ProfileLambdaExpr=*/true); | ||
| ContainerObject->Profile(ID, Ctx, /*Canonical=*/true, | ||
| /*ProfileLambdaExpr=*/true); | ||
| return ID.ComputeHash(); | ||
| } | ||
|
|
||
| void RedundantLookupCheck::check(const MatchFinder::MatchResult &Result) { | ||
| SM = Result.SourceManager; | ||
|
|
||
| const auto *EnclosingFn = | ||
| Result.Nodes.getNodeAs<FunctionDecl>(EnclosingFnKey); | ||
| const auto *LookupCall = Result.Nodes.getNodeAs<CallExpr>(LookupCallKey); | ||
| const auto *Key = Result.Nodes.getNodeAs<Expr>(LookupKey); | ||
| const auto *ContainerObject = Result.Nodes.getNodeAs<Expr>(ObjKey); | ||
|
|
||
| const unsigned LookupHash = | ||
| hashLookupEvent(*Result.Context, EnclosingFn, ContainerObject, Key); | ||
| RegisteredLookups.try_emplace(LookupHash).first->second.insert(LookupCall); | ||
| } | ||
|
|
||
| void RedundantLookupCheck::onEndOfTranslationUnit() { | ||
| auto ByBeginLoc = [this](const CallExpr *Lookup1, const CallExpr *Lookup2) { | ||
| return SM->isBeforeInTranslationUnit(Lookup1->getBeginLoc(), | ||
| Lookup2->getBeginLoc()); | ||
| }; | ||
|
|
||
| // Process the found lookups of each function. | ||
| for (const auto &LookupGroup : llvm::make_second_range(RegisteredLookups)) { | ||
| if (LookupGroup.size() < 2) | ||
| continue; | ||
|
|
||
| llvm::SmallVector<const CallExpr *> SortedGroup; | ||
| SortedGroup.reserve(LookupGroup.size()); | ||
| llvm::append_range(SortedGroup, LookupGroup); | ||
| llvm::sort(SortedGroup, ByBeginLoc); | ||
|
|
||
| const CallExpr *FirstLookupCall = SortedGroup.front(); | ||
| diag(FirstLookupCall->getBeginLoc(), "possibly redundant container lookups") | ||
| << FirstLookupCall->getSourceRange(); | ||
|
|
||
| for (const CallExpr *LookupCall : llvm::drop_begin(SortedGroup)) { | ||
| diag(LookupCall->getBeginLoc(), "next lookup here", DiagnosticIDs::Note) | ||
| << LookupCall->getSourceRange(); | ||
| } | ||
| } | ||
|
|
||
| RegisteredLookups.clear(); | ||
| } | ||
|
|
||
| } // namespace clang::tidy::performance | ||
47 changes: 47 additions & 0 deletions
47
clang-tools-extra/clang-tidy/performance/RedundantLookupCheck.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| //===--- RedundantLookupCheck.h - clang-tidy --------------------*- C++ -*-===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_REDUNDANTLOOKUPCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_REDUNDANTLOOKUPCHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
| #include "clang/AST/Expr.h" | ||
| #include "llvm/ADT/SmallPtrSet.h" | ||
|
|
||
| namespace clang { | ||
| class SourceManager; | ||
| } // namespace clang | ||
|
|
||
| namespace clang::tidy::performance { | ||
|
|
||
| /// Detects redundant container lookups. | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/performance/redundant-lookup.html | ||
| class RedundantLookupCheck : public ClangTidyCheck { | ||
| public: | ||
| RedundantLookupCheck(StringRef Name, ClangTidyContext *Context); | ||
| void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
| void onEndOfTranslationUnit() override; | ||
| void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
| bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { | ||
| return LangOpts.CPlusPlus; | ||
| } | ||
|
|
||
| private: | ||
| llvm::DenseMap<unsigned, llvm::SmallPtrSet<const CallExpr *, 2>> | ||
| RegisteredLookups; | ||
| const StringRef ContainerNameRegex; | ||
| const std::vector<StringRef> LookupMethodNames; | ||
| const SourceManager *SM = nullptr; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::performance | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_REDUNDANTLOOKUPCHECK_H |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -91,6 +91,12 @@ Improvements to clang-tidy | |
| New checks | ||
| ^^^^^^^^^^ | ||
|
|
||
| - New :doc:`performance-redundant-lookup | ||
| <clang-tidy/checks/performance/redundant-lookup>` check. | ||
|
|
||
| This check warns about potential redundant container lookup operations within | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dropped in c6a681c |
||
| a function. | ||
|
|
||
| New check aliases | ||
| ^^^^^^^^^^^^^^^^^ | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better would be to use ExprSequence like bugprone-use-after-move check does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know of this
ExprSequenceutility. I had a quick look at this and it looks really nice.In my case, I use this predicate to sort the findings, to always pick a deterministic error location, and to construct meaningful notes in order. That said, this is not used for driving a matching decision.
In the checker, I intentionally let the design open for matching cases like this:
Because similar patterns could be frequently refactored into:
map[key] = coin() ? foo() : bar();While requiring a sequence relationship wouldn't allow matching this.
But its a good point that likely such a case would warrant a specific diagnostic message for delivering that it's more likely a FP.
However, I admit that it probably needs some refinement, because if the lookup group spans across many many lines, then the report becomes less actionable. Not to mention if some loop between some lookups actually modify the map.
I think I'd need more field experience to make a justified choice here.