blob: 2f311da09cdb9334157dd4e9954f52e54560dfc9 [file] [log] [blame]
Haojian Wuc5cc0332017-04-18 07:46:39 +00001//===--- 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
10#include "InefficientVectorOperationCheck.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Lex/Lexer.h"
14#include "../utils/DeclRefExprUtils.h"
Haojian Wu489f3632017-04-26 18:13:05 +000015#include "../utils/OptionsUtils.h"
Haojian Wuc5cc0332017-04-18 07:46:39 +000016
17using namespace clang::ast_matchers;
18
19namespace clang {
20namespace tidy {
21namespace performance {
22
23namespace {
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:
Haojian Wu489f3632017-04-26 18:13:05 +000037// - LoopCounterName: The entire for loop (as ForStmt).
Haojian Wuc5cc0332017-04-18 07:46:39 +000038// - 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// - PushBackCallName: 'v.push_back(i)' (as cxxMemberCallExpr).
43// - LoopInitVarName: 'i' (as VarDecl).
44// - LoopEndExpr: '10+1' (as Expr).
45static const char LoopCounterName[] = "for_loop_counter";
46static const char LoopParentName[] = "loop_parent";
47static const char VectorVarDeclName[] = "vector_var_decl";
48static const char VectorVarDeclStmtName[] = "vector_var_decl_stmt";
49static const char PushBackCallName[] = "push_back_call";
Haojian Wuc5cc0332017-04-18 07:46:39 +000050static const char LoopInitVarName[] = "loop_init_var";
51static const char LoopEndExprName[] = "loop_end_expr";
52
Haojian Wu489f3632017-04-26 18:13:05 +000053static const char RangeLoopName[] = "for_range_loop";
54
55ast_matchers::internal::Matcher<Expr> supportedContainerTypesMatcher() {
56 return hasType(cxxRecordDecl(hasAnyName(
57 "::std::vector", "::std::set", "::std::unordered_set", "::std::map",
Haojian Wuce4b17f2017-05-15 18:18:28 +000058 "::std::unordered_map", "::std::array", "::std::deque")));
Haojian Wu489f3632017-04-26 18:13:05 +000059}
60
Haojian Wuc5cc0332017-04-18 07:46:39 +000061} // namespace
62
Haojian Wu489f3632017-04-26 18:13:05 +000063InefficientVectorOperationCheck::InefficientVectorOperationCheck(
64 StringRef Name, ClangTidyContext *Context)
65 : ClangTidyCheck(Name, Context),
66 VectorLikeClasses(utils::options::parseStringList(
67 Options.get("VectorLikeClasses", "::std::vector"))) {}
68
69void InefficientVectorOperationCheck::storeOptions(
70 ClangTidyOptions::OptionMap &Opts) {
71 Options.store(Opts, "VectorLikeClasses",
72 utils::options::serializeStringList(VectorLikeClasses));
73}
74
Haojian Wuc5cc0332017-04-18 07:46:39 +000075void InefficientVectorOperationCheck::registerMatchers(MatchFinder *Finder) {
Haojian Wu489f3632017-04-26 18:13:05 +000076 const auto VectorDecl = cxxRecordDecl(hasAnyName(SmallVector<StringRef, 5>(
77 VectorLikeClasses.begin(), VectorLikeClasses.end())));
Haojian Wuc5cc0332017-04-18 07:46:39 +000078 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 PushBackCallExpr =
85 cxxMemberCallExpr(
86 callee(cxxMethodDecl(hasName("push_back"))), on(hasType(VectorDecl)),
87 onImplicitObjectArgument(declRefExpr(to(VectorVarDecl))))
88 .bind(PushBackCallName);
89 const auto PushBackCall =
90 expr(anyOf(PushBackCallExpr, exprWithCleanups(has(PushBackCallExpr))));
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
Haojian Wu489f3632017-04-26 18:13:05 +0000101 // Matchers for the loop whose body has only 1 push_back calling statement.
102 const auto HasInterestingLoopBody = hasBody(anyOf(
103 compoundStmt(statementCountIs(1), has(PushBackCall)), PushBackCall));
104 const auto InInterestingCompoundStmt =
105 hasParent(compoundStmt(has(VectorVarDefStmt)).bind(LoopParentName));
106
Haojian Wuc5cc0332017-04-18 07:46:39 +0000107 // Match counter-based for loops:
108 // for (int i = 0; i < n; ++i) { v.push_back(...); }
109 //
110 // FIXME: Support more types of counter-based loops like decrement loops.
111 Finder->addMatcher(
112 forStmt(
113 hasLoopInit(LoopVarInit),
114 hasCondition(binaryOperator(
115 hasOperatorName("<"), hasLHS(RefersToLoopVar),
116 hasRHS(expr(unless(hasDescendant(expr(RefersToLoopVar))))
117 .bind(LoopEndExprName)))),
118 hasIncrement(unaryOperator(hasOperatorName("++"),
119 hasUnaryOperand(RefersToLoopVar))),
Haojian Wu489f3632017-04-26 18:13:05 +0000120 HasInterestingLoopBody, InInterestingCompoundStmt)
Haojian Wuc5cc0332017-04-18 07:46:39 +0000121 .bind(LoopCounterName),
122 this);
Haojian Wu489f3632017-04-26 18:13:05 +0000123
124 // Match for-range loops:
125 // for (const auto& E : data) { v.push_back(...); }
126 //
127 // FIXME: Support more complex range-expressions.
128 Finder->addMatcher(
129 cxxForRangeStmt(
130 hasRangeInit(declRefExpr(supportedContainerTypesMatcher())),
131 HasInterestingLoopBody, InInterestingCompoundStmt)
132 .bind(RangeLoopName),
133 this);
Haojian Wuc5cc0332017-04-18 07:46:39 +0000134}
135
136void InefficientVectorOperationCheck::check(
137 const MatchFinder::MatchResult &Result) {
Haojian Wuc2c22832017-04-18 20:47:34 +0000138 auto* Context = Result.Context;
139 if (Context->getDiagnostics().hasUncompilableErrorOccurred())
Haojian Wuc5cc0332017-04-18 07:46:39 +0000140 return;
141
142 const SourceManager &SM = *Result.SourceManager;
Haojian Wu489f3632017-04-26 18:13:05 +0000143 const auto *VectorVarDecl =
144 Result.Nodes.getNodeAs<VarDecl>(VectorVarDeclName);
Haojian Wuc5cc0332017-04-18 07:46:39 +0000145 const auto *ForLoop = Result.Nodes.getNodeAs<ForStmt>(LoopCounterName);
Haojian Wu489f3632017-04-26 18:13:05 +0000146 const auto *RangeLoop =
147 Result.Nodes.getNodeAs<CXXForRangeStmt>(RangeLoopName);
Haojian Wuc5cc0332017-04-18 07:46:39 +0000148 const auto *PushBackCall =
149 Result.Nodes.getNodeAs<CXXMemberCallExpr>(PushBackCallName);
150 const auto *LoopEndExpr = Result.Nodes.getNodeAs<Expr>(LoopEndExprName);
151 const auto *LoopParent = Result.Nodes.getNodeAs<CompoundStmt>(LoopParentName);
Haojian Wu489f3632017-04-26 18:13:05 +0000152
153 const Stmt *LoopStmt = ForLoop;
154 if (!LoopStmt)
155 LoopStmt = RangeLoop;
Haojian Wuc5cc0332017-04-18 07:46:39 +0000156
157 llvm::SmallPtrSet<const DeclRefExpr *, 16> AllVectorVarRefs =
158 utils::decl_ref_expr::allDeclRefExprs(*VectorVarDecl, *LoopParent,
Haojian Wuc2c22832017-04-18 20:47:34 +0000159 *Context);
Haojian Wuc5cc0332017-04-18 07:46:39 +0000160 for (const auto *Ref : AllVectorVarRefs) {
161 // Skip cases where there are usages (defined as DeclRefExpr that refers to
162 // "v") of vector variable `v` before the for loop. We consider these usages
163 // are operations causing memory preallocation (e.g. "v.resize(n)",
164 // "v.reserve(n)").
165 //
166 // FIXME: make it more intelligent to identify the pre-allocating operations
167 // before the for loop.
168 if (SM.isBeforeInTranslationUnit(Ref->getLocation(),
Haojian Wu489f3632017-04-26 18:13:05 +0000169 LoopStmt->getLocStart())) {
Haojian Wuc5cc0332017-04-18 07:46:39 +0000170 return;
171 }
172 }
173
Haojian Wuc2c22832017-04-18 20:47:34 +0000174 llvm::StringRef VectorVarName = Lexer::getSourceText(
Haojian Wuc5cc0332017-04-18 07:46:39 +0000175 CharSourceRange::getTokenRange(
176 PushBackCall->getImplicitObjectArgument()->getSourceRange()),
Haojian Wuc2c22832017-04-18 20:47:34 +0000177 SM, Context->getLangOpts());
Haojian Wuc5cc0332017-04-18 07:46:39 +0000178
Haojian Wu489f3632017-04-26 18:13:05 +0000179 std::string ReserveStmt;
180 // Handle for-range loop cases.
181 if (RangeLoop) {
182 // Get the range-expression in a for-range statement represented as
183 // `for (range-declarator: range-expression)`.
184 StringRef RangeInitExpName = Lexer::getSourceText(
185 CharSourceRange::getTokenRange(
186 RangeLoop->getRangeInit()->getSourceRange()),
187 SM, Context->getLangOpts());
188
189 ReserveStmt =
190 (VectorVarName + ".reserve(" + RangeInitExpName + ".size()" + ");\n")
191 .str();
192 } else if (ForLoop) {
193 // Handle counter-based loop cases.
194 StringRef LoopEndSource = Lexer::getSourceText(
195 CharSourceRange::getTokenRange(LoopEndExpr->getSourceRange()), SM,
196 Context->getLangOpts());
197 ReserveStmt = (VectorVarName + ".reserve(" + LoopEndSource + ");\n").str();
198 }
199
200 auto Diag = diag(PushBackCall->getLocStart(),
Haojian Wuc5cc0332017-04-18 07:46:39 +0000201 "'push_back' is called inside a loop; "
Haojian Wu489f3632017-04-26 18:13:05 +0000202 "consider pre-allocating the vector capacity before the loop");
203
204 if (!ReserveStmt.empty())
205 Diag << FixItHint::CreateInsertion(LoopStmt->getLocStart(), ReserveStmt);
Haojian Wuc5cc0332017-04-18 07:46:39 +0000206}
207
208} // namespace performance
209} // namespace tidy
210} // namespace clang