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