InefficientAlgorithmCheck.cpp 5.9 KB

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