| 1 | //===--- InefficientAlgorithmCheck.cpp - clang-tidy------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "InefficientAlgorithmCheck.h" |
| 10 | #include "clang/AST/ASTContext.h" |
| 11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 12 | #include "clang/Lex/Lexer.h" |
| 13 | |
| 14 | using namespace clang::ast_matchers; |
| 15 | |
| 16 | namespace clang::tidy::performance { |
| 17 | |
| 18 | static bool areTypesCompatible(QualType Left, QualType Right) { |
| 19 | if (const auto *LeftRefType = Left->getAs<ReferenceType>()) |
| 20 | Left = LeftRefType->getPointeeType(); |
| 21 | if (const auto *RightRefType = Right->getAs<ReferenceType>()) |
| 22 | Right = RightRefType->getPointeeType(); |
| 23 | return Left->getCanonicalTypeUnqualified() == |
| 24 | Right->getCanonicalTypeUnqualified(); |
| 25 | } |
| 26 | |
| 27 | void InefficientAlgorithmCheck::registerMatchers(MatchFinder *Finder) { |
| 28 | const auto Algorithms = |
| 29 | hasAnyName("::std::find" , "::std::count" , "::std::equal_range" , |
| 30 | "::std::lower_bound" , "::std::upper_bound" ); |
| 31 | const auto ContainerMatcher = classTemplateSpecializationDecl(hasAnyName( |
| 32 | "::std::set" , "::std::map" , "::std::multiset" , "::std::multimap" , |
| 33 | "::std::unordered_set" , "::std::unordered_map" , |
| 34 | "::std::unordered_multiset" , "::std::unordered_multimap" )); |
| 35 | |
| 36 | const auto Matcher = |
| 37 | callExpr( |
| 38 | callee(InnerMatcher: functionDecl(Algorithms)), |
| 39 | hasArgument( |
| 40 | N: 0, InnerMatcher: cxxMemberCallExpr( |
| 41 | callee(InnerMatcher: cxxMethodDecl(hasName(Name: "begin" ))), |
| 42 | on(InnerMatcher: declRefExpr( |
| 43 | hasDeclaration(InnerMatcher: decl().bind(ID: "IneffContObj" )), |
| 44 | anyOf(hasType(InnerMatcher: ContainerMatcher.bind(ID: "IneffCont" )), |
| 45 | hasType(InnerMatcher: pointsTo( |
| 46 | InnerMatcher: ContainerMatcher.bind(ID: "IneffContPtr" ))))) |
| 47 | .bind(ID: "IneffContExpr" )))), |
| 48 | hasArgument( |
| 49 | N: 1, InnerMatcher: cxxMemberCallExpr(callee(InnerMatcher: cxxMethodDecl(hasName(Name: "end" ))), |
| 50 | on(InnerMatcher: declRefExpr(hasDeclaration( |
| 51 | InnerMatcher: equalsBoundNode(ID: "IneffContObj" )))))), |
| 52 | hasArgument(N: 2, InnerMatcher: expr().bind(ID: "AlgParam" ))) |
| 53 | .bind(ID: "IneffAlg" ); |
| 54 | |
| 55 | Finder->addMatcher(NodeMatch: Matcher, Action: this); |
| 56 | } |
| 57 | |
| 58 | void InefficientAlgorithmCheck::check(const MatchFinder::MatchResult &Result) { |
| 59 | const auto *AlgCall = Result.Nodes.getNodeAs<CallExpr>(ID: "IneffAlg" ); |
| 60 | const auto *IneffCont = |
| 61 | Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>(ID: "IneffCont" ); |
| 62 | bool PtrToContainer = false; |
| 63 | if (!IneffCont) { |
| 64 | IneffCont = |
| 65 | Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>(ID: "IneffContPtr" ); |
| 66 | PtrToContainer = true; |
| 67 | } |
| 68 | const llvm::StringRef IneffContName = IneffCont->getName(); |
| 69 | const bool Unordered = IneffContName.contains(Other: "unordered" ); |
| 70 | const bool Maplike = IneffContName.contains(Other: "map" ); |
| 71 | |
| 72 | // Store if the key type of the container is compatible with the value |
| 73 | // that is searched for. |
| 74 | QualType ValueType = AlgCall->getArg(Arg: 2)->getType(); |
| 75 | QualType KeyType = |
| 76 | IneffCont->getTemplateArgs()[0].getAsType().getCanonicalType(); |
| 77 | const bool CompatibleTypes = areTypesCompatible(Left: KeyType, Right: ValueType); |
| 78 | |
| 79 | // Check if the comparison type for the algorithm and the container matches. |
| 80 | if (AlgCall->getNumArgs() == 4 && !Unordered) { |
| 81 | const Expr *Arg = AlgCall->getArg(Arg: 3); |
| 82 | const QualType AlgCmp = |
| 83 | Arg->getType().getUnqualifiedType().getCanonicalType(); |
| 84 | const unsigned CmpPosition = IneffContName.contains(Other: "map" ) ? 2 : 1; |
| 85 | const QualType ContainerCmp = IneffCont->getTemplateArgs()[CmpPosition] |
| 86 | .getAsType() |
| 87 | .getUnqualifiedType() |
| 88 | .getCanonicalType(); |
| 89 | if (AlgCmp != ContainerCmp) { |
| 90 | diag(Arg->getBeginLoc(), |
| 91 | "different comparers used in the algorithm and the container" ); |
| 92 | return; |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | const auto *AlgDecl = AlgCall->getDirectCallee(); |
| 97 | if (!AlgDecl) |
| 98 | return; |
| 99 | |
| 100 | if (Unordered && AlgDecl->getName().contains("bound" )) |
| 101 | return; |
| 102 | |
| 103 | const auto *AlgParam = Result.Nodes.getNodeAs<Expr>(ID: "AlgParam" ); |
| 104 | const auto *IneffContExpr = Result.Nodes.getNodeAs<Expr>(ID: "IneffContExpr" ); |
| 105 | FixItHint Hint; |
| 106 | |
| 107 | SourceManager &SM = *Result.SourceManager; |
| 108 | LangOptions LangOpts = getLangOpts(); |
| 109 | |
| 110 | CharSourceRange CallRange = |
| 111 | CharSourceRange::getTokenRange(AlgCall->getSourceRange()); |
| 112 | |
| 113 | // FIXME: Create a common utility to extract a file range that the given token |
| 114 | // sequence is exactly spelled at (without macro argument expansions etc.). |
| 115 | // We can't use Lexer::makeFileCharRange here, because for |
| 116 | // |
| 117 | // #define F(x) x |
| 118 | // x(a b c); |
| 119 | // |
| 120 | // it will return "x(a b c)", when given the range "a"-"c". It makes sense for |
| 121 | // removals, but not for replacements. |
| 122 | // |
| 123 | // This code is over-simplified, but works for many real cases. |
| 124 | if (SM.isMacroArgExpansion(Loc: CallRange.getBegin()) && |
| 125 | SM.isMacroArgExpansion(Loc: CallRange.getEnd())) { |
| 126 | CallRange.setBegin(SM.getSpellingLoc(Loc: CallRange.getBegin())); |
| 127 | CallRange.setEnd(SM.getSpellingLoc(Loc: CallRange.getEnd())); |
| 128 | } |
| 129 | |
| 130 | if (!CallRange.getBegin().isMacroID() && !Maplike && CompatibleTypes) { |
| 131 | StringRef ContainerText = Lexer::getSourceText( |
| 132 | Range: CharSourceRange::getTokenRange(IneffContExpr->getSourceRange()), SM, |
| 133 | LangOpts); |
| 134 | StringRef ParamText = Lexer::getSourceText( |
| 135 | Range: CharSourceRange::getTokenRange(AlgParam->getSourceRange()), SM, |
| 136 | LangOpts); |
| 137 | std::string ReplacementText = |
| 138 | (llvm::Twine(ContainerText) + (PtrToContainer ? "->" : "." ) + |
| 139 | AlgDecl->getName() + "(" + ParamText + ")" ) |
| 140 | .str(); |
| 141 | Hint = FixItHint::CreateReplacement(RemoveRange: CallRange, Code: ReplacementText); |
| 142 | } |
| 143 | |
| 144 | diag(Loc: AlgCall->getBeginLoc(), |
| 145 | Description: "this STL algorithm call should be replaced with a container method" ) |
| 146 | << Hint; |
| 147 | } |
| 148 | |
| 149 | } // namespace clang::tidy::performance |
| 150 | |