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