clang-tools  9.0.0
InefficientVectorOperationCheck.cpp
Go to the documentation of this file.
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 
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Lex/Lexer.h"
13 #include "../utils/DeclRefExprUtils.h"
14 #include "../utils/OptionsUtils.h"
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang {
19 namespace tidy {
20 namespace performance {
21 
22 namespace {
23 
24 // Matcher names. Given the code:
25 //
26 // \code
27 // void f() {
28 // vector<T> v;
29 // for (int i = 0; i < 10 + 1; ++i) {
30 // v.push_back(i);
31 // }
32 // }
33 // \endcode
34 //
35 // The matcher names are bound to following parts of the AST:
36 // - LoopCounterName: The entire for loop (as ForStmt).
37 // - LoopParentName: The body of function f (as CompoundStmt).
38 // - VectorVarDeclName: 'v' in (as VarDecl).
39 // - VectorVarDeclStmatName: The entire 'std::vector<T> v;' statement (as
40 // DeclStmt).
41 // - PushBackOrEmplaceBackCallName: 'v.push_back(i)' (as cxxMemberCallExpr).
42 // - LoopInitVarName: 'i' (as VarDecl).
43 // - LoopEndExpr: '10+1' (as Expr).
44 static const char LoopCounterName[] = "for_loop_counter";
45 static const char LoopParentName[] = "loop_parent";
46 static const char VectorVarDeclName[] = "vector_var_decl";
47 static const char VectorVarDeclStmtName[] = "vector_var_decl_stmt";
48 static const char PushBackOrEmplaceBackCallName[] = "append_call";
49 static const char LoopInitVarName[] = "loop_init_var";
50 static const char LoopEndExprName[] = "loop_end_expr";
51 
52 static const char RangeLoopName[] = "for_range_loop";
53 
54 ast_matchers::internal::Matcher<Expr> supportedContainerTypesMatcher() {
55  return hasType(cxxRecordDecl(hasAnyName(
56  "::std::vector", "::std::set", "::std::unordered_set", "::std::map",
57  "::std::unordered_map", "::std::array", "::std::deque")));
58 }
59 
60 } // namespace
61 
62 InefficientVectorOperationCheck::InefficientVectorOperationCheck(
63  StringRef Name, ClangTidyContext *Context)
64  : ClangTidyCheck(Name, Context),
65  VectorLikeClasses(utils::options::parseStringList(
66  Options.get("VectorLikeClasses", "::std::vector"))) {}
67 
70  Options.store(Opts, "VectorLikeClasses",
71  utils::options::serializeStringList(VectorLikeClasses));
72 }
73 
75  const auto VectorDecl = cxxRecordDecl(hasAnyName(SmallVector<StringRef, 5>(
76  VectorLikeClasses.begin(), VectorLikeClasses.end())));
77  const auto VectorDefaultConstructorCall = cxxConstructExpr(
78  hasType(VectorDecl),
79  hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
80  const auto VectorVarDecl =
81  varDecl(hasInitializer(VectorDefaultConstructorCall))
82  .bind(VectorVarDeclName);
83  const auto VectorAppendCallExpr =
84  cxxMemberCallExpr(
85  callee(cxxMethodDecl(hasAnyName("push_back", "emplace_back"))),
86  on(hasType(VectorDecl)),
87  onImplicitObjectArgument(declRefExpr(to(VectorVarDecl))))
88  .bind(PushBackOrEmplaceBackCallName);
89  const auto VectorAppendCall = expr(ignoringImplicit(VectorAppendCallExpr));
90  const auto VectorVarDefStmt =
91  declStmt(hasSingleDecl(equalsBoundNode(VectorVarDeclName)))
92  .bind(VectorVarDeclStmtName);
93 
94  const auto LoopVarInit =
95  declStmt(hasSingleDecl(varDecl(hasInitializer(integerLiteral(equals(0))))
96  .bind(LoopInitVarName)));
97  const auto RefersToLoopVar = ignoringParenImpCasts(
98  declRefExpr(to(varDecl(equalsBoundNode(LoopInitVarName)))));
99 
100  // Matchers for the loop whose body has only 1 push_back/emplace_back calling
101  // statement.
102  const auto HasInterestingLoopBody =
103  hasBody(anyOf(compoundStmt(statementCountIs(1), has(VectorAppendCall)),
104  VectorAppendCall));
105  const auto InInterestingCompoundStmt =
106  hasParent(compoundStmt(has(VectorVarDefStmt)).bind(LoopParentName));
107 
108  // Match counter-based for loops:
109  // for (int i = 0; i < n; ++i) { v.push_back(...); }
110  //
111  // FIXME: Support more types of counter-based loops like decrement loops.
112  Finder->addMatcher(
113  forStmt(
114  hasLoopInit(LoopVarInit),
115  hasCondition(binaryOperator(
116  hasOperatorName("<"), hasLHS(RefersToLoopVar),
117  hasRHS(expr(unless(hasDescendant(expr(RefersToLoopVar))))
118  .bind(LoopEndExprName)))),
119  hasIncrement(unaryOperator(hasOperatorName("++"),
120  hasUnaryOperand(RefersToLoopVar))),
121  HasInterestingLoopBody, InInterestingCompoundStmt)
122  .bind(LoopCounterName),
123  this);
124 
125  // Match for-range loops:
126  // for (const auto& E : data) { v.push_back(...); }
127  //
128  // FIXME: Support more complex range-expressions.
129  Finder->addMatcher(
130  cxxForRangeStmt(
131  hasRangeInit(declRefExpr(supportedContainerTypesMatcher())),
132  HasInterestingLoopBody, InInterestingCompoundStmt)
133  .bind(RangeLoopName),
134  this);
135 }
136 
138  const MatchFinder::MatchResult &Result) {
139  auto* Context = Result.Context;
140  if (Context->getDiagnostics().hasUncompilableErrorOccurred())
141  return;
142 
143  const SourceManager &SM = *Result.SourceManager;
144  const auto *VectorVarDecl =
145  Result.Nodes.getNodeAs<VarDecl>(VectorVarDeclName);
146  const auto *ForLoop = Result.Nodes.getNodeAs<ForStmt>(LoopCounterName);
147  const auto *RangeLoop =
148  Result.Nodes.getNodeAs<CXXForRangeStmt>(RangeLoopName);
149  const auto *VectorAppendCall =
150  Result.Nodes.getNodeAs<CXXMemberCallExpr>(PushBackOrEmplaceBackCallName);
151  const auto *LoopEndExpr = Result.Nodes.getNodeAs<Expr>(LoopEndExprName);
152  const auto *LoopParent = Result.Nodes.getNodeAs<CompoundStmt>(LoopParentName);
153 
154  const Stmt *LoopStmt = ForLoop;
155  if (!LoopStmt)
156  LoopStmt = RangeLoop;
157 
158  llvm::SmallPtrSet<const DeclRefExpr *, 16> AllVectorVarRefs =
159  utils::decl_ref_expr::allDeclRefExprs(*VectorVarDecl, *LoopParent,
160  *Context);
161  for (const auto *Ref : AllVectorVarRefs) {
162  // Skip cases where there are usages (defined as DeclRefExpr that refers to
163  // "v") of vector variable `v` before the for loop. We consider these usages
164  // are operations causing memory preallocation (e.g. "v.resize(n)",
165  // "v.reserve(n)").
166  //
167  // FIXME: make it more intelligent to identify the pre-allocating operations
168  // before the for loop.
169  if (SM.isBeforeInTranslationUnit(Ref->getLocation(),
170  LoopStmt->getBeginLoc())) {
171  return;
172  }
173  }
174 
175  llvm::StringRef VectorVarName = Lexer::getSourceText(
177  VectorAppendCall->getImplicitObjectArgument()->getSourceRange()),
178  SM, Context->getLangOpts());
179 
180  std::string ReserveStmt;
181  // Handle for-range loop cases.
182  if (RangeLoop) {
183  // Get the range-expression in a for-range statement represented as
184  // `for (range-declarator: range-expression)`.
185  StringRef RangeInitExpName = Lexer::getSourceText(
187  RangeLoop->getRangeInit()->getSourceRange()),
188  SM, Context->getLangOpts());
189 
190  ReserveStmt =
191  (VectorVarName + ".reserve(" + RangeInitExpName + ".size()" + ");\n")
192  .str();
193  } else if (ForLoop) {
194  // Handle counter-based loop cases.
195  StringRef LoopEndSource = Lexer::getSourceText(
196  CharSourceRange::getTokenRange(LoopEndExpr->getSourceRange()), SM,
197  Context->getLangOpts());
198  ReserveStmt = (VectorVarName + ".reserve(" + LoopEndSource + ");\n").str();
199  }
200 
201  auto Diag =
202  diag(VectorAppendCall->getBeginLoc(),
203  "%0 is called inside a loop; "
204  "consider pre-allocating the vector capacity before the loop")
205  << VectorAppendCall->getMethodDecl()->getDeclName();
206 
207  if (!ReserveStmt.empty())
208  Diag << FixItHint::CreateInsertion(LoopStmt->getBeginLoc(), ReserveStmt);
209 }
210 
211 } // namespace performance
212 } // namespace tidy
213 } // namespace clang
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
std::string serializeStringList(ArrayRef< std::string > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
SmallPtrSet< const DeclRefExpr *, 16 > allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context)
Returns set of all DeclRefExprs to VarDecl within Stmt.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Base class for all clang-tidy checks.
std::vector< std::string > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
static constexpr llvm::StringLiteral Name
std::map< std::string, std::string > OptionMap
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Definition: SourceCode.cpp:203
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
Definition: Rename.cpp:36
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.