InefficientVectorOperationCheck.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. //===--- InefficientVectorOperationCheck.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 "InefficientVectorOperationCheck.h"
  9. #include "../utils/DeclRefExprUtils.h"
  10. #include "../utils/OptionsUtils.h"
  11. #include "clang/AST/ASTContext.h"
  12. #include "clang/ASTMatchers/ASTMatchFinder.h"
  13. #include "clang/Lex/Lexer.h"
  14. using namespace clang::ast_matchers;
  15. namespace clang::tidy::performance {
  16. namespace {
  17. // Matcher names. Given the code:
  18. //
  19. // \code
  20. // void f() {
  21. // vector<T> v;
  22. // for (int i = 0; i < 10 + 1; ++i) {
  23. // v.push_back(i);
  24. // }
  25. //
  26. // SomeProto p;
  27. // for (int i = 0; i < 10 + 1; ++i) {
  28. // p.add_xxx(i);
  29. // }
  30. // }
  31. // \endcode
  32. //
  33. // The matcher names are bound to following parts of the AST:
  34. // - LoopCounterName: The entire for loop (as ForStmt).
  35. // - LoopParentName: The body of function f (as CompoundStmt).
  36. // - VectorVarDeclName: 'v' (as VarDecl).
  37. // - VectorVarDeclStmatName: The entire 'std::vector<T> v;' statement (as
  38. // DeclStmt).
  39. // - PushBackOrEmplaceBackCallName: 'v.push_back(i)' (as cxxMemberCallExpr).
  40. // - LoopInitVarName: 'i' (as VarDecl).
  41. // - LoopEndExpr: '10+1' (as Expr).
  42. // If EnableProto, the proto related names are bound to the following parts:
  43. // - ProtoVarDeclName: 'p' (as VarDecl).
  44. // - ProtoVarDeclStmtName: The entire 'SomeProto p;' statement (as DeclStmt).
  45. // - ProtoAddFieldCallName: 'p.add_xxx(i)' (as cxxMemberCallExpr).
  46. static const char LoopCounterName[] = "for_loop_counter";
  47. static const char LoopParentName[] = "loop_parent";
  48. static const char VectorVarDeclName[] = "vector_var_decl";
  49. static const char VectorVarDeclStmtName[] = "vector_var_decl_stmt";
  50. static const char PushBackOrEmplaceBackCallName[] = "append_call";
  51. static const char ProtoVarDeclName[] = "proto_var_decl";
  52. static const char ProtoVarDeclStmtName[] = "proto_var_decl_stmt";
  53. static const char ProtoAddFieldCallName[] = "proto_add_field";
  54. static const char LoopInitVarName[] = "loop_init_var";
  55. static const char LoopEndExprName[] = "loop_end_expr";
  56. static const char RangeLoopName[] = "for_range_loop";
  57. ast_matchers::internal::Matcher<Expr> supportedContainerTypesMatcher() {
  58. return hasType(cxxRecordDecl(hasAnyName(
  59. "::std::vector", "::std::set", "::std::unordered_set", "::std::map",
  60. "::std::unordered_map", "::std::array", "::std::deque")));
  61. }
  62. AST_MATCHER(Expr, hasSideEffects) {
  63. return Node.HasSideEffects(Finder->getASTContext());
  64. }
  65. } // namespace
  66. InefficientVectorOperationCheck::InefficientVectorOperationCheck(
  67. StringRef Name, ClangTidyContext *Context)
  68. : ClangTidyCheck(Name, Context),
  69. VectorLikeClasses(utils::options::parseStringList(
  70. Options.get("VectorLikeClasses", "::std::vector"))),
  71. EnableProto(Options.getLocalOrGlobal("EnableProto", false)) {}
  72. void InefficientVectorOperationCheck::storeOptions(
  73. ClangTidyOptions::OptionMap &Opts) {
  74. Options.store(Opts, "VectorLikeClasses",
  75. utils::options::serializeStringList(VectorLikeClasses));
  76. Options.store(Opts, "EnableProto", EnableProto);
  77. }
  78. void InefficientVectorOperationCheck::addMatcher(
  79. const DeclarationMatcher &TargetRecordDecl, StringRef VarDeclName,
  80. StringRef VarDeclStmtName, const DeclarationMatcher &AppendMethodDecl,
  81. StringRef AppendCallName, MatchFinder *Finder) {
  82. const auto DefaultConstructorCall = cxxConstructExpr(
  83. hasType(TargetRecordDecl),
  84. hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
  85. const auto TargetVarDecl =
  86. varDecl(hasInitializer(DefaultConstructorCall)).bind(VarDeclName);
  87. const auto TargetVarDefStmt =
  88. declStmt(hasSingleDecl(equalsBoundNode(std::string(VarDeclName))))
  89. .bind(VarDeclStmtName);
  90. const auto AppendCallExpr =
  91. cxxMemberCallExpr(
  92. callee(AppendMethodDecl), on(hasType(TargetRecordDecl)),
  93. onImplicitObjectArgument(declRefExpr(to(TargetVarDecl))))
  94. .bind(AppendCallName);
  95. const auto AppendCall = expr(ignoringImplicit(AppendCallExpr));
  96. const auto LoopVarInit =
  97. declStmt(hasSingleDecl(varDecl(hasInitializer(integerLiteral(equals(0))))
  98. .bind(LoopInitVarName)));
  99. const auto RefersToLoopVar = ignoringParenImpCasts(
  100. declRefExpr(to(varDecl(equalsBoundNode(LoopInitVarName)))));
  101. // Matchers for the loop whose body has only 1 push_back/emplace_back calling
  102. // statement.
  103. const auto HasInterestingLoopBody = hasBody(
  104. anyOf(compoundStmt(statementCountIs(1), has(AppendCall)), AppendCall));
  105. const auto InInterestingCompoundStmt =
  106. hasParent(compoundStmt(has(TargetVarDefStmt)).bind(LoopParentName));
  107. // Match counter-based for loops:
  108. // for (int i = 0; i < n; ++i) {
  109. // v.push_back(...);
  110. // // Or: proto.add_xxx(...);
  111. // }
  112. //
  113. // FIXME: Support more types of counter-based loops like decrement loops.
  114. Finder->addMatcher(
  115. forStmt(
  116. hasLoopInit(LoopVarInit),
  117. hasCondition(binaryOperator(
  118. hasOperatorName("<"), hasLHS(RefersToLoopVar),
  119. hasRHS(expr(unless(hasDescendant(expr(RefersToLoopVar))))
  120. .bind(LoopEndExprName)))),
  121. hasIncrement(unaryOperator(hasOperatorName("++"),
  122. hasUnaryOperand(RefersToLoopVar))),
  123. HasInterestingLoopBody, InInterestingCompoundStmt)
  124. .bind(LoopCounterName),
  125. this);
  126. // Match for-range loops:
  127. // for (const auto& E : data) {
  128. // v.push_back(...);
  129. // // Or: proto.add_xxx(...);
  130. // }
  131. //
  132. // FIXME: Support more complex range-expressions.
  133. Finder->addMatcher(
  134. cxxForRangeStmt(
  135. hasRangeInit(
  136. anyOf(declRefExpr(supportedContainerTypesMatcher()),
  137. memberExpr(hasObjectExpression(unless(hasSideEffects())),
  138. supportedContainerTypesMatcher()))),
  139. HasInterestingLoopBody, InInterestingCompoundStmt)
  140. .bind(RangeLoopName),
  141. this);
  142. }
  143. void InefficientVectorOperationCheck::registerMatchers(MatchFinder *Finder) {
  144. const auto VectorDecl = cxxRecordDecl(hasAnyName(VectorLikeClasses));
  145. const auto AppendMethodDecl =
  146. cxxMethodDecl(hasAnyName("push_back", "emplace_back"));
  147. addMatcher(VectorDecl, VectorVarDeclName, VectorVarDeclStmtName,
  148. AppendMethodDecl, PushBackOrEmplaceBackCallName, Finder);
  149. if (EnableProto) {
  150. const auto ProtoDecl =
  151. cxxRecordDecl(isDerivedFrom("::proto2::MessageLite"));
  152. // A method's name starts with "add_" might not mean it's an add field
  153. // call; it could be the getter for a proto field of which the name starts
  154. // with "add_". So we exclude const methods.
  155. const auto AddFieldMethodDecl =
  156. cxxMethodDecl(matchesName("::add_"), unless(isConst()));
  157. addMatcher(ProtoDecl, ProtoVarDeclName, ProtoVarDeclStmtName,
  158. AddFieldMethodDecl, ProtoAddFieldCallName, Finder);
  159. }
  160. }
  161. void InefficientVectorOperationCheck::check(
  162. const MatchFinder::MatchResult &Result) {
  163. auto* Context = Result.Context;
  164. if (Context->getDiagnostics().hasUncompilableErrorOccurred())
  165. return;
  166. const SourceManager &SM = *Result.SourceManager;
  167. const auto *VectorVarDecl =
  168. Result.Nodes.getNodeAs<VarDecl>(VectorVarDeclName);
  169. const auto *ForLoop = Result.Nodes.getNodeAs<ForStmt>(LoopCounterName);
  170. const auto *RangeLoop =
  171. Result.Nodes.getNodeAs<CXXForRangeStmt>(RangeLoopName);
  172. const auto *VectorAppendCall =
  173. Result.Nodes.getNodeAs<CXXMemberCallExpr>(PushBackOrEmplaceBackCallName);
  174. const auto *ProtoVarDecl = Result.Nodes.getNodeAs<VarDecl>(ProtoVarDeclName);
  175. const auto *ProtoAddFieldCall =
  176. Result.Nodes.getNodeAs<CXXMemberCallExpr>(ProtoAddFieldCallName);
  177. const auto *LoopEndExpr = Result.Nodes.getNodeAs<Expr>(LoopEndExprName);
  178. const auto *LoopParent = Result.Nodes.getNodeAs<CompoundStmt>(LoopParentName);
  179. const CXXMemberCallExpr *AppendCall =
  180. VectorAppendCall ? VectorAppendCall : ProtoAddFieldCall;
  181. assert(AppendCall && "no append call expression");
  182. const Stmt *LoopStmt = ForLoop;
  183. if (!LoopStmt)
  184. LoopStmt = RangeLoop;
  185. const auto *TargetVarDecl = VectorVarDecl;
  186. if (!TargetVarDecl)
  187. TargetVarDecl = ProtoVarDecl;
  188. llvm::SmallPtrSet<const DeclRefExpr *, 16> AllVarRefs =
  189. utils::decl_ref_expr::allDeclRefExprs(*TargetVarDecl, *LoopParent,
  190. *Context);
  191. for (const auto *Ref : AllVarRefs) {
  192. // Skip cases where there are usages (defined as DeclRefExpr that refers
  193. // to "v") of vector variable / proto variable `v` before the for loop. We
  194. // consider these usages are operations causing memory preallocation (e.g.
  195. // "v.resize(n)", "v.reserve(n)").
  196. //
  197. // FIXME: make it more intelligent to identify the pre-allocating
  198. // operations before the for loop.
  199. if (SM.isBeforeInTranslationUnit(Ref->getLocation(),
  200. LoopStmt->getBeginLoc())) {
  201. return;
  202. }
  203. }
  204. std::string PartialReserveStmt;
  205. if (VectorAppendCall != nullptr) {
  206. PartialReserveStmt = ".reserve";
  207. } else {
  208. llvm::StringRef FieldName = ProtoAddFieldCall->getMethodDecl()->getName();
  209. FieldName.consume_front("add_");
  210. std::string MutableFieldName = ("mutable_" + FieldName).str();
  211. PartialReserveStmt = "." + MutableFieldName +
  212. "()->Reserve"; // e.g., ".mutable_xxx()->Reserve"
  213. }
  214. llvm::StringRef VarName = Lexer::getSourceText(
  215. CharSourceRange::getTokenRange(
  216. AppendCall->getImplicitObjectArgument()->getSourceRange()),
  217. SM, Context->getLangOpts());
  218. std::string ReserveSize;
  219. // Handle for-range loop cases.
  220. if (RangeLoop) {
  221. // Get the range-expression in a for-range statement represented as
  222. // `for (range-declarator: range-expression)`.
  223. StringRef RangeInitExpName =
  224. Lexer::getSourceText(CharSourceRange::getTokenRange(
  225. RangeLoop->getRangeInit()->getSourceRange()),
  226. SM, Context->getLangOpts());
  227. ReserveSize = (RangeInitExpName + ".size()").str();
  228. } else if (ForLoop) {
  229. // Handle counter-based loop cases.
  230. StringRef LoopEndSource = Lexer::getSourceText(
  231. CharSourceRange::getTokenRange(LoopEndExpr->getSourceRange()), SM,
  232. Context->getLangOpts());
  233. ReserveSize = std::string(LoopEndSource);
  234. }
  235. auto Diag = diag(AppendCall->getBeginLoc(),
  236. "%0 is called inside a loop; consider pre-allocating the "
  237. "container capacity before the loop")
  238. << AppendCall->getMethodDecl()->getDeclName();
  239. if (!ReserveSize.empty()) {
  240. std::string ReserveStmt =
  241. (VarName + PartialReserveStmt + "(" + ReserveSize + ");\n").str();
  242. Diag << FixItHint::CreateInsertion(LoopStmt->getBeginLoc(), ReserveStmt);
  243. }
  244. }
  245. } // namespace clang::tidy::performance