12#include "clang/AST/ASTContext.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14#include "clang/Lex/Lexer.h"
51static const char LoopCounterName[] =
"for_loop_counter";
52static const char LoopParentName[] =
"loop_parent";
53static const char VectorVarDeclName[] =
"vector_var_decl";
54static const char VectorVarDeclStmtName[] =
"vector_var_decl_stmt";
55static const char PushBackOrEmplaceBackCallName[] =
"append_call";
56static const char ProtoVarDeclName[] =
"proto_var_decl";
57static const char ProtoVarDeclStmtName[] =
"proto_var_decl_stmt";
58static const char ProtoAddFieldCallName[] =
"proto_add_field";
59static const char LoopInitVarName[] =
"loop_init_var";
60static const char LoopEndExprName[] =
"loop_end_expr";
61static const char RangeLoopName[] =
"for_range_loop";
63ast_matchers::internal::Matcher<Expr> supportedContainerTypesMatcher() {
64 return hasType(cxxRecordDecl(hasAnyName(
65 "::std::vector",
"::std::set",
"::std::unordered_set",
"::std::map",
66 "::std::unordered_map",
"::std::array",
"::std::deque")));
70 return Node.HasSideEffects(Finder->getASTContext());
78 VectorLikeClasses(
utils::options::parseStringList(
79 Options.get(
"VectorLikeClasses",
"::std::vector"))),
80 EnableProto(Options.get(
"EnableProto", false)) {}
84 Options.store(Opts,
"VectorLikeClasses",
86 Options.store(Opts,
"EnableProto", EnableProto);
89void InefficientVectorOperationCheck::addMatcher(
90 const DeclarationMatcher &TargetRecordDecl, StringRef VarDeclName,
91 StringRef VarDeclStmtName,
const DeclarationMatcher &AppendMethodDecl,
92 StringRef AppendCallName, MatchFinder *Finder) {
93 const auto DefaultConstructorCall = cxxConstructExpr(
94 hasType(TargetRecordDecl),
95 hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
96 const auto TargetVarDecl =
97 varDecl(hasInitializer(DefaultConstructorCall)).bind(VarDeclName);
98 const auto TargetVarDefStmt =
99 declStmt(hasSingleDecl(equalsBoundNode(std::string(VarDeclName))))
100 .bind(VarDeclStmtName);
102 const auto AppendCallExpr =
104 callee(AppendMethodDecl), on(hasType(TargetRecordDecl)),
105 onImplicitObjectArgument(declRefExpr(to(TargetVarDecl))))
106 .bind(AppendCallName);
107 const auto AppendCall = expr(ignoringImplicit(AppendCallExpr));
108 const auto LoopVarInit = declStmt(hasSingleDecl(
109 varDecl(hasInitializer(ignoringParenImpCasts(integerLiteral(equals(0)))))
110 .bind(LoopInitVarName)));
111 const auto RefersToLoopVar = ignoringParenImpCasts(
112 declRefExpr(to(varDecl(equalsBoundNode(LoopInitVarName)))));
116 const auto HasInterestingLoopBody = hasBody(
117 anyOf(compoundStmt(statementCountIs(1), has(AppendCall)), AppendCall));
118 const auto InInterestingCompoundStmt =
119 hasParent(compoundStmt(has(TargetVarDefStmt)).bind(LoopParentName));
129 forStmt(hasLoopInit(LoopVarInit),
130 hasCondition(binaryOperator(
131 hasOperatorName(
"<"), hasLHS(RefersToLoopVar),
132 hasRHS(expr(unless(hasDescendant(expr(RefersToLoopVar))))
133 .bind(LoopEndExprName)))),
134 hasIncrement(unaryOperator(hasOperatorName(
"++"),
135 hasUnaryOperand(RefersToLoopVar))),
136 HasInterestingLoopBody, InInterestingCompoundStmt)
137 .bind(LoopCounterName),
150 anyOf(declRefExpr(supportedContainerTypesMatcher()),
151 memberExpr(hasObjectExpression(unless(hasSideEffects())),
152 supportedContainerTypesMatcher()))),
153 HasInterestingLoopBody, InInterestingCompoundStmt)
154 .bind(RangeLoopName),
159 const auto VectorDecl = cxxRecordDecl(hasAnyName(VectorLikeClasses));
160 const auto AppendMethodDecl =
161 cxxMethodDecl(hasAnyName(
"push_back",
"emplace_back"));
162 addMatcher(VectorDecl, VectorVarDeclName, VectorVarDeclStmtName,
163 AppendMethodDecl, PushBackOrEmplaceBackCallName, Finder);
166 const auto ProtoDecl =
167 cxxRecordDecl(isDerivedFrom(
"::proto2::MessageLite"));
172 const auto AddFieldMethodDecl =
173 cxxMethodDecl(matchesName(
"::add_"), unless(isConst()));
174 addMatcher(ProtoDecl, ProtoVarDeclName, ProtoVarDeclStmtName,
175 AddFieldMethodDecl, ProtoAddFieldCallName, Finder);
180 const MatchFinder::MatchResult &Result) {
181 auto *Context = Result.Context;
182 if (Context->getDiagnostics().hasUncompilableErrorOccurred())
185 const SourceManager &SM = *Result.SourceManager;
186 const auto *VectorVarDecl =
187 Result.Nodes.getNodeAs<VarDecl>(VectorVarDeclName);
188 const auto *ForLoop = Result.Nodes.getNodeAs<ForStmt>(LoopCounterName);
189 const auto *RangeLoop =
190 Result.Nodes.getNodeAs<CXXForRangeStmt>(RangeLoopName);
191 const auto *VectorAppendCall =
192 Result.Nodes.getNodeAs<CXXMemberCallExpr>(PushBackOrEmplaceBackCallName);
193 const auto *ProtoVarDecl = Result.Nodes.getNodeAs<VarDecl>(ProtoVarDeclName);
194 const auto *ProtoAddFieldCall =
195 Result.Nodes.getNodeAs<CXXMemberCallExpr>(ProtoAddFieldCallName);
196 const auto *LoopEndExpr = Result.Nodes.getNodeAs<Expr>(LoopEndExprName);
197 const auto *LoopParent = Result.Nodes.getNodeAs<CompoundStmt>(LoopParentName);
199 const CXXMemberCallExpr *AppendCall =
200 VectorAppendCall ? VectorAppendCall : ProtoAddFieldCall;
201 assert(AppendCall &&
"no append call expression");
203 const Stmt *LoopStmt = ForLoop;
205 LoopStmt = RangeLoop;
207 const auto *TargetVarDecl = VectorVarDecl;
209 TargetVarDecl = ProtoVarDecl;
211 llvm::SmallPtrSet<const DeclRefExpr *, 16> AllVarRefs =
214 for (
const auto *Ref : AllVarRefs) {
222 if (SM.isBeforeInTranslationUnit(Ref->getLocation(),
223 LoopStmt->getBeginLoc())) {
228 std::string PartialReserveStmt;
229 if (VectorAppendCall !=
nullptr) {
230 PartialReserveStmt =
".reserve";
232 llvm::StringRef FieldName = ProtoAddFieldCall->getMethodDecl()->getName();
233 FieldName.consume_front(
"add_");
234 std::string MutableFieldName = (
"mutable_" + FieldName).str();
235 PartialReserveStmt =
"." + MutableFieldName +
239 llvm::StringRef VarName = Lexer::getSourceText(
240 CharSourceRange::getTokenRange(
241 AppendCall->getImplicitObjectArgument()->getSourceRange()),
242 SM, Context->getLangOpts());
244 std::string ReserveSize;
249 StringRef RangeInitExpName =
250 Lexer::getSourceText(CharSourceRange::getTokenRange(
251 RangeLoop->getRangeInit()->getSourceRange()),
252 SM, Context->getLangOpts());
253 ReserveSize = (RangeInitExpName +
".size()").str();
254 }
else if (ForLoop) {
256 StringRef LoopEndSource = Lexer::getSourceText(
257 CharSourceRange::getTokenRange(LoopEndExpr->getSourceRange()), SM,
258 Context->getLangOpts());
259 ReserveSize = std::string(LoopEndSource);
262 auto Diag = diag(AppendCall->getBeginLoc(),
263 "%0 is called inside a loop; consider pre-allocating the "
264 "container capacity before the loop")
265 << AppendCall->getMethodDecl()->getDeclName();
266 if (!ReserveSize.empty()) {
267 std::string ReserveStmt =
268 (VarName + PartialReserveStmt +
"(" + ReserveSize +
");\n").str();
269 Diag << FixItHint::CreateInsertion(LoopStmt->getBeginLoc(), ReserveStmt);
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
AST_MATCHER(BinaryOperator, isRelationalOperator)
SmallPtrSet< const DeclRefExpr *, 16 > allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context)
Returns set of all DeclRefExprs to VarDecl within Stmt.
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap