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