xref: /llvm-project/clang-tools-extra/clang-tidy/performance/InefficientAlgorithmCheck.cpp (revision 0f1721c480369bad1c8d3f9a664f8db6853f35fc)
16e39e689SAlexander Kornienko //===--- InefficientAlgorithmCheck.cpp - clang-tidy------------------------===//
26e39e689SAlexander Kornienko //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
66e39e689SAlexander Kornienko //
76e39e689SAlexander Kornienko //===----------------------------------------------------------------------===//
86e39e689SAlexander Kornienko 
96e39e689SAlexander Kornienko #include "InefficientAlgorithmCheck.h"
106e39e689SAlexander Kornienko #include "clang/AST/ASTContext.h"
116e39e689SAlexander Kornienko #include "clang/ASTMatchers/ASTMatchFinder.h"
126e39e689SAlexander Kornienko #include "clang/Lex/Lexer.h"
136e39e689SAlexander Kornienko 
146e39e689SAlexander Kornienko using namespace clang::ast_matchers;
156e39e689SAlexander Kornienko 
167d2ea6c4SCarlos Galvez namespace clang::tidy::performance {
176e39e689SAlexander Kornienko 
areTypesCompatible(QualType Left,QualType Right)186e39e689SAlexander Kornienko static bool areTypesCompatible(QualType Left, QualType Right) {
196e39e689SAlexander Kornienko   if (const auto *LeftRefType = Left->getAs<ReferenceType>())
206e39e689SAlexander Kornienko     Left = LeftRefType->getPointeeType();
216e39e689SAlexander Kornienko   if (const auto *RightRefType = Right->getAs<ReferenceType>())
226e39e689SAlexander Kornienko     Right = RightRefType->getPointeeType();
236e39e689SAlexander Kornienko   return Left->getCanonicalTypeUnqualified() ==
246e39e689SAlexander Kornienko          Right->getCanonicalTypeUnqualified();
256e39e689SAlexander Kornienko }
266e39e689SAlexander Kornienko 
registerMatchers(MatchFinder * Finder)276e39e689SAlexander Kornienko void InefficientAlgorithmCheck::registerMatchers(MatchFinder *Finder) {
286e39e689SAlexander Kornienko   const auto Algorithms =
296e39e689SAlexander Kornienko       hasAnyName("::std::find", "::std::count", "::std::equal_range",
306e39e689SAlexander Kornienko                  "::std::lower_bound", "::std::upper_bound");
316e39e689SAlexander Kornienko   const auto ContainerMatcher = classTemplateSpecializationDecl(hasAnyName(
326e39e689SAlexander Kornienko       "::std::set", "::std::map", "::std::multiset", "::std::multimap",
336e39e689SAlexander Kornienko       "::std::unordered_set", "::std::unordered_map",
346e39e689SAlexander Kornienko       "::std::unordered_multiset", "::std::unordered_multimap"));
356e39e689SAlexander Kornienko 
366c2eca96SStephen Kelly   const auto Matcher =
376e39e689SAlexander Kornienko       callExpr(
386e39e689SAlexander Kornienko           callee(functionDecl(Algorithms)),
396e39e689SAlexander Kornienko           hasArgument(
406c2eca96SStephen Kelly               0, cxxMemberCallExpr(
416e39e689SAlexander Kornienko                      callee(cxxMethodDecl(hasName("begin"))),
426e39e689SAlexander Kornienko                      on(declRefExpr(
436e39e689SAlexander Kornienko                             hasDeclaration(decl().bind("IneffContObj")),
446e39e689SAlexander Kornienko                             anyOf(hasType(ContainerMatcher.bind("IneffCont")),
456e39e689SAlexander Kornienko                                   hasType(pointsTo(
466e39e689SAlexander Kornienko                                       ContainerMatcher.bind("IneffContPtr")))))
476c2eca96SStephen Kelly                             .bind("IneffContExpr")))),
486e39e689SAlexander Kornienko           hasArgument(
496c2eca96SStephen Kelly               1, cxxMemberCallExpr(callee(cxxMethodDecl(hasName("end"))),
506c2eca96SStephen Kelly                                    on(declRefExpr(hasDeclaration(
516c2eca96SStephen Kelly                                        equalsBoundNode("IneffContObj")))))),
526c2eca96SStephen Kelly           hasArgument(2, expr().bind("AlgParam")))
536c2eca96SStephen Kelly           .bind("IneffAlg");
546e39e689SAlexander Kornienko 
556e39e689SAlexander Kornienko   Finder->addMatcher(Matcher, this);
566e39e689SAlexander Kornienko }
576e39e689SAlexander Kornienko 
check(const MatchFinder::MatchResult & Result)586e39e689SAlexander Kornienko void InefficientAlgorithmCheck::check(const MatchFinder::MatchResult &Result) {
596e39e689SAlexander Kornienko   const auto *AlgCall = Result.Nodes.getNodeAs<CallExpr>("IneffAlg");
606e39e689SAlexander Kornienko   const auto *IneffCont =
616e39e689SAlexander Kornienko       Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("IneffCont");
626e39e689SAlexander Kornienko   bool PtrToContainer = false;
636e39e689SAlexander Kornienko   if (!IneffCont) {
646e39e689SAlexander Kornienko     IneffCont =
656e39e689SAlexander Kornienko         Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("IneffContPtr");
666e39e689SAlexander Kornienko     PtrToContainer = true;
676e39e689SAlexander Kornienko   }
686e39e689SAlexander Kornienko   const llvm::StringRef IneffContName = IneffCont->getName();
6920f0f15aSKazu Hirata   const bool Unordered = IneffContName.contains("unordered");
7020f0f15aSKazu Hirata   const bool Maplike = IneffContName.contains("map");
716e39e689SAlexander Kornienko 
726e39e689SAlexander Kornienko   // Store if the key type of the container is compatible with the value
736e39e689SAlexander Kornienko   // that is searched for.
746e39e689SAlexander Kornienko   QualType ValueType = AlgCall->getArg(2)->getType();
756e39e689SAlexander Kornienko   QualType KeyType =
766e39e689SAlexander Kornienko       IneffCont->getTemplateArgs()[0].getAsType().getCanonicalType();
776e39e689SAlexander Kornienko   const bool CompatibleTypes = areTypesCompatible(KeyType, ValueType);
786e39e689SAlexander Kornienko 
796e39e689SAlexander Kornienko   // Check if the comparison type for the algorithm and the container matches.
806e39e689SAlexander Kornienko   if (AlgCall->getNumArgs() == 4 && !Unordered) {
816e39e689SAlexander Kornienko     const Expr *Arg = AlgCall->getArg(3);
826e39e689SAlexander Kornienko     const QualType AlgCmp =
836e39e689SAlexander Kornienko         Arg->getType().getUnqualifiedType().getCanonicalType();
8420f0f15aSKazu Hirata     const unsigned CmpPosition = IneffContName.contains("map") ? 2 : 1;
856e39e689SAlexander Kornienko     const QualType ContainerCmp = IneffCont->getTemplateArgs()[CmpPosition]
866e39e689SAlexander Kornienko                                       .getAsType()
876e39e689SAlexander Kornienko                                       .getUnqualifiedType()
886e39e689SAlexander Kornienko                                       .getCanonicalType();
896e39e689SAlexander Kornienko     if (AlgCmp != ContainerCmp) {
9043465bf3SStephen Kelly       diag(Arg->getBeginLoc(),
916e39e689SAlexander Kornienko            "different comparers used in the algorithm and the container");
926e39e689SAlexander Kornienko       return;
936e39e689SAlexander Kornienko     }
946e39e689SAlexander Kornienko   }
956e39e689SAlexander Kornienko 
966e39e689SAlexander Kornienko   const auto *AlgDecl = AlgCall->getDirectCallee();
976e39e689SAlexander Kornienko   if (!AlgDecl)
986e39e689SAlexander Kornienko     return;
996e39e689SAlexander Kornienko 
100*0f1721c4SKazu Hirata   if (Unordered && AlgDecl->getName().contains("bound"))
1016e39e689SAlexander Kornienko     return;
1026e39e689SAlexander Kornienko 
1036e39e689SAlexander Kornienko   const auto *AlgParam = Result.Nodes.getNodeAs<Expr>("AlgParam");
1046e39e689SAlexander Kornienko   const auto *IneffContExpr = Result.Nodes.getNodeAs<Expr>("IneffContExpr");
1056e39e689SAlexander Kornienko   FixItHint Hint;
1066e39e689SAlexander Kornienko 
1076e39e689SAlexander Kornienko   SourceManager &SM = *Result.SourceManager;
1086e39e689SAlexander Kornienko   LangOptions LangOpts = getLangOpts();
1096e39e689SAlexander Kornienko 
1106e39e689SAlexander Kornienko   CharSourceRange CallRange =
1116e39e689SAlexander Kornienko       CharSourceRange::getTokenRange(AlgCall->getSourceRange());
1126e39e689SAlexander Kornienko 
1136e39e689SAlexander Kornienko   // FIXME: Create a common utility to extract a file range that the given token
1146e39e689SAlexander Kornienko   // sequence is exactly spelled at (without macro argument expansions etc.).
1156e39e689SAlexander Kornienko   // We can't use Lexer::makeFileCharRange here, because for
1166e39e689SAlexander Kornienko   //
1176e39e689SAlexander Kornienko   //   #define F(x) x
1186e39e689SAlexander Kornienko   //   x(a b c);
1196e39e689SAlexander Kornienko   //
1206e39e689SAlexander Kornienko   // it will return "x(a b c)", when given the range "a"-"c". It makes sense for
1216e39e689SAlexander Kornienko   // removals, but not for replacements.
1226e39e689SAlexander Kornienko   //
1236e39e689SAlexander Kornienko   // This code is over-simplified, but works for many real cases.
1246e39e689SAlexander Kornienko   if (SM.isMacroArgExpansion(CallRange.getBegin()) &&
1256e39e689SAlexander Kornienko       SM.isMacroArgExpansion(CallRange.getEnd())) {
1266e39e689SAlexander Kornienko     CallRange.setBegin(SM.getSpellingLoc(CallRange.getBegin()));
1276e39e689SAlexander Kornienko     CallRange.setEnd(SM.getSpellingLoc(CallRange.getEnd()));
1286e39e689SAlexander Kornienko   }
1296e39e689SAlexander Kornienko 
1306e39e689SAlexander Kornienko   if (!CallRange.getBegin().isMacroID() && !Maplike && CompatibleTypes) {
1316e39e689SAlexander Kornienko     StringRef ContainerText = Lexer::getSourceText(
1326e39e689SAlexander Kornienko         CharSourceRange::getTokenRange(IneffContExpr->getSourceRange()), SM,
1336e39e689SAlexander Kornienko         LangOpts);
1346e39e689SAlexander Kornienko     StringRef ParamText = Lexer::getSourceText(
1356e39e689SAlexander Kornienko         CharSourceRange::getTokenRange(AlgParam->getSourceRange()), SM,
1366e39e689SAlexander Kornienko         LangOpts);
1376e39e689SAlexander Kornienko     std::string ReplacementText =
1386e39e689SAlexander Kornienko         (llvm::Twine(ContainerText) + (PtrToContainer ? "->" : ".") +
1396e39e689SAlexander Kornienko          AlgDecl->getName() + "(" + ParamText + ")")
1406e39e689SAlexander Kornienko             .str();
1416e39e689SAlexander Kornienko     Hint = FixItHint::CreateReplacement(CallRange, ReplacementText);
1426e39e689SAlexander Kornienko   }
1436e39e689SAlexander Kornienko 
14443465bf3SStephen Kelly   diag(AlgCall->getBeginLoc(),
1456e39e689SAlexander Kornienko        "this STL algorithm call should be replaced with a container method")
1466e39e689SAlexander Kornienko       << Hint;
1476e39e689SAlexander Kornienko }
1486e39e689SAlexander Kornienko 
1497d2ea6c4SCarlos Galvez } // namespace clang::tidy::performance
150