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