blob: be9b1e3190d66f2c2b082bc2f99c157febbea54e [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"
15
16using namespace clang::ast_matchers;
17
18namespace clang {
19namespace tidy {
20namespace performance {
21
22namespace {
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// - LoopName: 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// - PushBackCallName: 'v.push_back(i)' (as cxxMemberCallExpr).
42// - LoopInitVarName: 'i' (as VarDecl).
43// - LoopEndExpr: '10+1' (as Expr).
44static const char LoopCounterName[] = "for_loop_counter";
45static const char LoopParentName[] = "loop_parent";
46static const char VectorVarDeclName[] = "vector_var_decl";
47static const char VectorVarDeclStmtName[] = "vector_var_decl_stmt";
48static const char PushBackCallName[] = "push_back_call";
49
50static const char LoopInitVarName[] = "loop_init_var";
51static const char LoopEndExprName[] = "loop_end_expr";
52
53} // namespace
54
55void InefficientVectorOperationCheck::registerMatchers(MatchFinder *Finder) {
56 const auto VectorDecl = cxxRecordDecl(hasName("::std::vector"));
57 const auto VectorDefaultConstructorCall = cxxConstructExpr(
58 hasType(VectorDecl),
59 hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
60 const auto VectorVarDecl =
61 varDecl(hasInitializer(VectorDefaultConstructorCall))
62 .bind(VectorVarDeclName);
63 const auto PushBackCallExpr =
64 cxxMemberCallExpr(
65 callee(cxxMethodDecl(hasName("push_back"))), on(hasType(VectorDecl)),
66 onImplicitObjectArgument(declRefExpr(to(VectorVarDecl))))
67 .bind(PushBackCallName);
68 const auto PushBackCall =
69 expr(anyOf(PushBackCallExpr, exprWithCleanups(has(PushBackCallExpr))));
70 const auto VectorVarDefStmt =
71 declStmt(hasSingleDecl(equalsBoundNode(VectorVarDeclName)))
72 .bind(VectorVarDeclStmtName);
73
74 const auto LoopVarInit =
75 declStmt(hasSingleDecl(varDecl(hasInitializer(integerLiteral(equals(0))))
76 .bind(LoopInitVarName)));
77 const auto RefersToLoopVar = ignoringParenImpCasts(
78 declRefExpr(to(varDecl(equalsBoundNode(LoopInitVarName)))));
79
80 // Match counter-based for loops:
81 // for (int i = 0; i < n; ++i) { v.push_back(...); }
82 //
83 // FIXME: Support more types of counter-based loops like decrement loops.
84 Finder->addMatcher(
85 forStmt(
86 hasLoopInit(LoopVarInit),
87 hasCondition(binaryOperator(
88 hasOperatorName("<"), hasLHS(RefersToLoopVar),
89 hasRHS(expr(unless(hasDescendant(expr(RefersToLoopVar))))
90 .bind(LoopEndExprName)))),
91 hasIncrement(unaryOperator(hasOperatorName("++"),
92 hasUnaryOperand(RefersToLoopVar))),
93 hasBody(anyOf(compoundStmt(statementCountIs(1), has(PushBackCall)),
94 PushBackCall)),
95 hasParent(compoundStmt(has(VectorVarDefStmt)).bind(LoopParentName)))
96 .bind(LoopCounterName),
97 this);
98}
99
100void InefficientVectorOperationCheck::check(
101 const MatchFinder::MatchResult &Result) {
102 if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred())
103 return;
104
105 const SourceManager &SM = *Result.SourceManager;
106 const auto *ForLoop = Result.Nodes.getNodeAs<ForStmt>(LoopCounterName);
107 const auto *PushBackCall =
108 Result.Nodes.getNodeAs<CXXMemberCallExpr>(PushBackCallName);
109 const auto *LoopEndExpr = Result.Nodes.getNodeAs<Expr>(LoopEndExprName);
110 const auto *LoopParent = Result.Nodes.getNodeAs<CompoundStmt>(LoopParentName);
111 const auto *VectorVarDecl =
112 Result.Nodes.getNodeAs<VarDecl>(VectorVarDeclName);
113
114 llvm::SmallPtrSet<const DeclRefExpr *, 16> AllVectorVarRefs =
115 utils::decl_ref_expr::allDeclRefExprs(*VectorVarDecl, *LoopParent,
116 *Result.Context);
117 for (const auto *Ref : AllVectorVarRefs) {
118 // Skip cases where there are usages (defined as DeclRefExpr that refers to
119 // "v") of vector variable `v` before the for loop. We consider these usages
120 // are operations causing memory preallocation (e.g. "v.resize(n)",
121 // "v.reserve(n)").
122 //
123 // FIXME: make it more intelligent to identify the pre-allocating operations
124 // before the for loop.
125 if (SM.isBeforeInTranslationUnit(Ref->getLocation(),
126 ForLoop->getLocStart())) {
127 return;
128 }
129 }
130
131 llvm::StringRef LoopEndSource = clang::Lexer::getSourceText(
132 CharSourceRange::getTokenRange(LoopEndExpr->getSourceRange()), SM,
133 clang::LangOptions());
134 llvm::StringRef VectorVarName = clang::Lexer::getSourceText(
135 CharSourceRange::getTokenRange(
136 PushBackCall->getImplicitObjectArgument()->getSourceRange()),
137 SM, clang::LangOptions());
138 std::string ReserveStmt =
139 (VectorVarName + ".reserve(" + LoopEndSource + ");\n").str();
140
141 diag(PushBackCall->getLocStart(),
142 "'push_back' is called inside a loop; "
143 "consider pre-allocating the vector capacity before the loop.")
144 << FixItHint::CreateInsertion(ForLoop->getLocStart(), ReserveStmt);
145}
146
147} // namespace performance
148} // namespace tidy
149} // namespace clang