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