clang-tools 22.0.0git
UseEmplaceCheck.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
9#include "UseEmplaceCheck.h"
11using namespace clang::ast_matchers;
12
13namespace clang::tidy::modernize {
14
15namespace {
16AST_MATCHER_P(InitListExpr, initCountLeq, unsigned, N) {
17 return Node.getNumInits() <= N;
18}
19
20// Identical to hasAnyName, except it does not take template specifiers into
21// account. This is used to match the functions names as in
22// DefaultEmplacyFunctions below without caring about the template types of the
23// containers.
24AST_MATCHER_P(NamedDecl, hasAnyNameIgnoringTemplates, std::vector<StringRef>,
25 Names) {
26 const std::string FullName = "::" + Node.getQualifiedNameAsString();
27
28 // This loop removes template specifiers by only keeping characters not within
29 // template brackets. We keep a depth count to handle nested templates. For
30 // example, it'll transform a::b<c<d>>::e<f> to simply a::b::e.
31 std::string FullNameTrimmed;
32 int Depth = 0;
33 for (const auto &Character : FullName)
34 if (Character == '<')
35 ++Depth;
36 else if (Character == '>')
37 --Depth;
38 else if (Depth == 0)
39 FullNameTrimmed.append(1, Character);
40
41 // This loop is taken from HasNameMatcher::matchesNodeFullSlow in
42 // clang/lib/ASTMatchers/ASTMatchersInternal.cpp and checks whether
43 // FullNameTrimmed matches any of the given Names.
44 const StringRef FullNameTrimmedRef = FullNameTrimmed;
45 return llvm::any_of(Names, [&](const StringRef Pattern) {
46 if (Pattern.starts_with("::"))
47 return FullNameTrimmed == Pattern;
48 return FullNameTrimmedRef.ends_with(Pattern) &&
49 FullNameTrimmedRef.drop_back(Pattern.size()).ends_with("::");
50 });
51}
52
53// Checks if the given matcher is the last argument of the given CallExpr.
54AST_MATCHER_P(CallExpr, hasLastArgument,
55 clang::ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
56 if (Node.getNumArgs() == 0)
57 return false;
58
59 return InnerMatcher.matches(*Node.getArg(Node.getNumArgs() - 1), Finder,
60 Builder);
61}
62
63// Checks if the given member call has the same number of arguments as the
64// function had parameters defined (this is useful to check if there is only one
65// variadic argument).
66AST_MATCHER(CXXMemberCallExpr, hasSameNumArgsAsDeclNumParams) {
67 if (const FunctionTemplateDecl *Primary =
68 Node.getMethodDecl()->getPrimaryTemplate())
69 return Node.getNumArgs() == Primary->getTemplatedDecl()->getNumParams();
70
71 return Node.getNumArgs() == Node.getMethodDecl()->getNumParams();
72}
73
74AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) {
75 return Node.hasExplicitTemplateArgs();
76}
77} // namespace
78
79// Helper Matcher which applies the given QualType Matcher either directly or by
80// resolving a pointer type to its pointee. Used to match v.push_back() as well
81// as p->push_back().
83 const ast_matchers::internal::Matcher<QualType> &TypeMatcher) {
84 return anyOf(hasType(TypeMatcher),
85 hasType(pointerType(pointee(TypeMatcher))));
86}
87
88// Matches if the node has canonical type matching any of the given names.
89static auto hasWantedType(llvm::ArrayRef<StringRef> TypeNames) {
90 return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasAnyName(TypeNames))));
91}
92
93// Matches member call expressions of the named method on the listed container
94// types.
95static auto
96cxxMemberCallExprOnContainer(StringRef MethodName,
97 llvm::ArrayRef<StringRef> ContainerNames) {
98 return cxxMemberCallExpr(
99 hasDeclaration(functionDecl(hasName(MethodName))),
100 on(hasTypeOrPointeeType(hasWantedType(ContainerNames))));
101}
102
104 "::std::vector; ::std::list; ::std::deque";
105static const auto DefaultContainersWithPush =
106 "::std::stack; ::std::queue; ::std::priority_queue";
108 "::std::forward_list; ::std::list; ::std::deque";
109static const auto DefaultSmartPointers =
110 "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr";
111static const auto DefaultTupleTypes = "::std::pair; ::std::tuple";
112static const auto DefaultTupleMakeFunctions =
113 "::std::make_pair; ::std::make_tuple";
114static const auto DefaultEmplacyFunctions =
115 "vector::emplace_back; vector::emplace;"
116 "deque::emplace; deque::emplace_front; deque::emplace_back;"
117 "forward_list::emplace_after; forward_list::emplace_front;"
118 "list::emplace; list::emplace_back; list::emplace_front;"
119 "set::emplace; set::emplace_hint;"
120 "map::emplace; map::emplace_hint;"
121 "multiset::emplace; multiset::emplace_hint;"
122 "multimap::emplace; multimap::emplace_hint;"
123 "unordered_set::emplace; unordered_set::emplace_hint;"
124 "unordered_map::emplace; unordered_map::emplace_hint;"
125 "unordered_multiset::emplace; unordered_multiset::emplace_hint;"
126 "unordered_multimap::emplace; unordered_multimap::emplace_hint;"
127 "stack::emplace; queue::emplace; priority_queue::emplace";
128
130 : ClangTidyCheck(Name, Context), IgnoreImplicitConstructors(Options.get(
131 "IgnoreImplicitConstructors", false)),
132 ContainersWithPushBack(utils::options::parseStringList(Options.get(
133 "ContainersWithPushBack", DefaultContainersWithPushBack))),
134 ContainersWithPush(utils::options::parseStringList(
135 Options.get("ContainersWithPush", DefaultContainersWithPush))),
136 ContainersWithPushFront(utils::options::parseStringList(Options.get(
137 "ContainersWithPushFront", DefaultContainersWithPushFront))),
138 SmartPointers(utils::options::parseStringList(
139 Options.get("SmartPointers", DefaultSmartPointers))),
140 TupleTypes(utils::options::parseStringList(
141 Options.get("TupleTypes", DefaultTupleTypes))),
142 TupleMakeFunctions(utils::options::parseStringList(
143 Options.get("TupleMakeFunctions", DefaultTupleMakeFunctions))),
144 EmplacyFunctions(utils::options::parseStringList(
145 Options.get("EmplacyFunctions", DefaultEmplacyFunctions))) {}
146
147void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
148 // FIXME: Bunch of functionality that could be easily added:
149 // + add handling of `insert` for stl associative container, but be careful
150 // because this requires special treatment (it could cause performance
151 // regression)
152 // + match for emplace calls that should be replaced with insertion
153 auto CallPushBack =
154 cxxMemberCallExprOnContainer("push_back", ContainersWithPushBack);
155 auto CallPush = cxxMemberCallExprOnContainer("push", ContainersWithPush);
156 auto CallPushFront =
157 cxxMemberCallExprOnContainer("push_front", ContainersWithPushFront);
158
159 auto CallEmplacy = cxxMemberCallExpr(
160 hasDeclaration(
161 functionDecl(hasAnyNameIgnoringTemplates(EmplacyFunctions))),
163 hasCanonicalType(hasDeclaration(has(typedefNameDecl(
164 hasName("value_type"),
165 hasType(hasCanonicalType(recordType().bind("value_type"))))))))));
166
167 // We can't replace push_backs of smart pointer because
168 // if emplacement fails (f.e. bad_alloc in vector) we will have leak of
169 // passed pointer because smart pointer won't be constructed
170 // (and destructed) as in push_back case.
171 auto IsCtorOfSmartPtr =
172 hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(SmartPointers))));
173
174 // Bitfields binds only to consts and emplace_back take it by universal ref.
175 auto BitFieldAsArgument = hasAnyArgument(
176 ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField())))));
177
178 // Initializer list can't be passed to universal reference.
179 auto InitializerListAsArgument = hasAnyArgument(
180 ignoringImplicit(allOf(cxxConstructExpr(isListInitialization()),
181 unless(cxxTemporaryObjectExpr()))));
182
183 // We could have leak of resource.
184 auto NewExprAsArgument = hasAnyArgument(ignoringImplicit(cxxNewExpr()));
185 // We would call another constructor.
186 auto ConstructingDerived =
187 hasParent(implicitCastExpr(hasCastKind(CastKind::CK_DerivedToBase)));
188
189 // emplace_back can't access private or protected constructors.
190 auto IsPrivateOrProtectedCtor =
191 hasDeclaration(cxxConstructorDecl(anyOf(isPrivate(), isProtected())));
192
193 auto HasInitList = anyOf(has(ignoringImplicit(initListExpr())),
194 has(cxxStdInitializerListExpr()));
195
196 // FIXME: Discard 0/NULL (as nullptr), static inline const data members,
197 // overloaded functions and template names.
198 auto SoughtConstructExpr =
199 cxxConstructExpr(
200 unless(anyOf(IsCtorOfSmartPtr, HasInitList, BitFieldAsArgument,
201 InitializerListAsArgument, NewExprAsArgument,
202 ConstructingDerived, IsPrivateOrProtectedCtor)))
203 .bind("ctor");
204 auto HasConstructExpr = has(ignoringImplicit(SoughtConstructExpr));
205
206 // allow for T{} to be replaced, even if no CTOR is declared
207 auto HasConstructInitListExpr = has(initListExpr(
208 initCountLeq(1), anyOf(allOf(has(SoughtConstructExpr),
209 has(cxxConstructExpr(argumentCountIs(0)))),
210 has(cxxBindTemporaryExpr(
211 has(SoughtConstructExpr),
212 has(cxxConstructExpr(argumentCountIs(0))))))));
213 auto HasBracedInitListExpr =
214 anyOf(has(cxxBindTemporaryExpr(HasConstructInitListExpr)),
215 HasConstructInitListExpr);
216
217 auto MakeTuple = ignoringImplicit(
218 callExpr(callee(expr(ignoringImplicit(declRefExpr(
219 unless(hasExplicitTemplateArgs()),
220 to(functionDecl(hasAnyName(TupleMakeFunctions))))))))
221 .bind("make"));
222
223 // make_something can return type convertible to container's element type.
224 // Allow the conversion only on containers of pairs.
225 auto MakeTupleCtor = ignoringImplicit(cxxConstructExpr(
226 has(materializeTemporaryExpr(MakeTuple)),
227 hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(TupleTypes))))));
228
229 auto SoughtParam =
230 materializeTemporaryExpr(
231 anyOf(has(MakeTuple), has(MakeTupleCtor), HasConstructExpr,
232 HasBracedInitListExpr,
233 has(cxxFunctionalCastExpr(HasConstructExpr)),
234 has(cxxFunctionalCastExpr(HasBracedInitListExpr))))
235 .bind("temporary_expr");
236
237 auto HasConstructExprWithValueTypeType =
238 has(ignoringImplicit(cxxConstructExpr(
239 SoughtConstructExpr,
240 hasType(hasCanonicalType(type(equalsBoundNode("value_type")))))));
241
242 auto HasBracedInitListWithValueTypeType = anyOf(
243 allOf(HasConstructInitListExpr,
244 has(initListExpr(hasType(
245 hasCanonicalType(type(equalsBoundNode("value_type"))))))),
246 has(cxxBindTemporaryExpr(HasConstructInitListExpr,
247 has(initListExpr(hasType(hasCanonicalType(
248 type(equalsBoundNode("value_type")))))))));
249
250 auto HasConstructExprWithValueTypeTypeAsLastArgument = hasLastArgument(
251 materializeTemporaryExpr(
252 anyOf(HasConstructExprWithValueTypeType,
253 HasBracedInitListWithValueTypeType,
254 has(cxxFunctionalCastExpr(HasConstructExprWithValueTypeType)),
255 has(cxxFunctionalCastExpr(HasBracedInitListWithValueTypeType))))
256 .bind("temporary_expr"));
257
258 Finder->addMatcher(
259 traverse(TK_AsIs, cxxMemberCallExpr(CallPushBack, has(SoughtParam),
260 unless(isInTemplateInstantiation()))
261 .bind("push_back_call")),
262 this);
263
264 Finder->addMatcher(
265 traverse(TK_AsIs, cxxMemberCallExpr(CallPush, has(SoughtParam),
266 unless(isInTemplateInstantiation()))
267 .bind("push_call")),
268 this);
269
270 Finder->addMatcher(
271 traverse(TK_AsIs, cxxMemberCallExpr(CallPushFront, has(SoughtParam),
272 unless(isInTemplateInstantiation()))
273 .bind("push_front_call")),
274 this);
275
276 Finder->addMatcher(
277 traverse(TK_AsIs,
278 cxxMemberCallExpr(
279 CallEmplacy, HasConstructExprWithValueTypeTypeAsLastArgument,
280 hasSameNumArgsAsDeclNumParams(),
281 unless(isInTemplateInstantiation()))
282 .bind("emplacy_call")),
283 this);
284
285 Finder->addMatcher(
286 traverse(TK_AsIs,
287 cxxMemberCallExpr(
288 CallEmplacy,
289 on(hasType(cxxRecordDecl(has(typedefNameDecl(
290 hasName("value_type"),
291 hasType(hasCanonicalType(recordType(hasDeclaration(
292 cxxRecordDecl(hasAnyName(SmallVector<StringRef, 2>(
293 TupleTypes.begin(), TupleTypes.end())))))))))))),
294 has(MakeTuple), hasSameNumArgsAsDeclNumParams(),
295 unless(isInTemplateInstantiation()))
296 .bind("emplacy_call")),
297 this);
298}
299
300void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
301 const auto *PushBackCall =
302 Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_back_call");
303 const auto *PushCall = Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_call");
304 const auto *PushFrontCall =
305 Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_front_call");
306 const auto *EmplacyCall =
307 Result.Nodes.getNodeAs<CXXMemberCallExpr>("emplacy_call");
308 const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");
309 const auto *MakeCall = Result.Nodes.getNodeAs<CallExpr>("make");
310 const auto *TemporaryExpr =
311 Result.Nodes.getNodeAs<MaterializeTemporaryExpr>("temporary_expr");
312
313 const CXXMemberCallExpr *Call = [&]() {
314 if (PushBackCall)
315 return PushBackCall;
316 if (PushCall)
317 return PushCall;
318 if (PushFrontCall)
319 return PushFrontCall;
320 return EmplacyCall;
321 }();
322
323 assert(Call && "No call matched");
324 assert((CtorCall || MakeCall) && "No push_back parameter matched");
325
326 if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 &&
327 CtorCall->getArg(0)->getSourceRange() == CtorCall->getSourceRange())
328 return;
329
330 const auto FunctionNameSourceRange = CharSourceRange::getCharRange(
331 Call->getExprLoc(), Call->getArg(0)->getExprLoc());
332
333 auto Diag =
334 EmplacyCall
335 ? diag(TemporaryExpr ? TemporaryExpr->getBeginLoc()
336 : CtorCall ? CtorCall->getBeginLoc()
337 : MakeCall->getBeginLoc(),
338 "unnecessary temporary object created while calling %0")
339 : diag(Call->getExprLoc(), "use emplace%select{|_back|_front}0 "
340 "instead of push%select{|_back|_front}0");
341 if (EmplacyCall)
342 Diag << Call->getMethodDecl()->getName();
343 else if (PushCall)
344 Diag << 0;
345 else if (PushBackCall)
346 Diag << 1;
347 else
348 Diag << 2;
349
350 if (FunctionNameSourceRange.getBegin().isMacroID())
351 return;
352
353 if (PushBackCall) {
354 const char *EmplacePrefix = MakeCall ? "emplace_back" : "emplace_back(";
355 Diag << FixItHint::CreateReplacement(FunctionNameSourceRange,
356 EmplacePrefix);
357 } else if (PushCall) {
358 const char *EmplacePrefix = MakeCall ? "emplace" : "emplace(";
359 Diag << FixItHint::CreateReplacement(FunctionNameSourceRange,
360 EmplacePrefix);
361 } else if (PushFrontCall) {
362 const char *EmplacePrefix = MakeCall ? "emplace_front" : "emplace_front(";
363 Diag << FixItHint::CreateReplacement(FunctionNameSourceRange,
364 EmplacePrefix);
365 }
366
367 const SourceRange CallParensRange =
368 MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(),
369 MakeCall->getRParenLoc())
370 : CtorCall->getParenOrBraceRange();
371
372 // Finish if there is no explicit constructor call.
373 if (CallParensRange.getBegin().isInvalid())
374 return;
375
376 // FIXME: Will there ever be a CtorCall, if there is no TemporaryExpr?
377 const SourceLocation ExprBegin = TemporaryExpr ? TemporaryExpr->getExprLoc()
378 : CtorCall ? CtorCall->getExprLoc()
379 : MakeCall->getExprLoc();
380
381 // Range for constructor name and opening brace.
382 const auto ParamCallSourceRange =
383 CharSourceRange::getTokenRange(ExprBegin, CallParensRange.getBegin());
384
385 // Range for constructor closing brace and end of temporary expr.
386 const auto EndCallSourceRange = CharSourceRange::getTokenRange(
387 CallParensRange.getEnd(),
388 TemporaryExpr ? TemporaryExpr->getEndLoc() : CallParensRange.getEnd());
389
390 Diag << FixItHint::CreateRemoval(ParamCallSourceRange)
391 << FixItHint::CreateRemoval(EndCallSourceRange);
392
393 if (MakeCall && EmplacyCall) {
394 // Remove extra left parenthesis
395 Diag << FixItHint::CreateRemoval(
396 CharSourceRange::getCharRange(MakeCall->getCallee()->getEndLoc(),
397 MakeCall->getArg(0)->getBeginLoc()));
398 }
399}
400
402 Options.store(Opts, "IgnoreImplicitConstructors", IgnoreImplicitConstructors);
403 Options.store(Opts, "ContainersWithPushBack",
404 utils::options::serializeStringList(ContainersWithPushBack));
405 Options.store(Opts, "ContainersWithPush",
406 utils::options::serializeStringList(ContainersWithPush));
407 Options.store(Opts, "ContainersWithPushFront",
408 utils::options::serializeStringList(ContainersWithPushFront));
409 Options.store(Opts, "SmartPointers",
411 Options.store(Opts, "TupleTypes",
413 Options.store(Opts, "TupleMakeFunctions",
414 utils::options::serializeStringList(TupleMakeFunctions));
415 Options.store(Opts, "EmplacyFunctions",
416 utils::options::serializeStringList(EmplacyFunctions));
417}
418
419} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
UseEmplaceCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
AST_MATCHER(BinaryOperator, isRelationalOperator)
static const auto DefaultContainersWithPush
static const auto DefaultTupleMakeFunctions
static auto cxxMemberCallExprOnContainer(StringRef MethodName, llvm::ArrayRef< StringRef > ContainerNames)
static const auto DefaultContainersWithPushBack
static auto hasWantedType(llvm::ArrayRef< StringRef > TypeNames)
static const auto DefaultContainersWithPushFront
static const auto DefaultSmartPointers
static const auto DefaultEmplacyFunctions
static auto hasTypeOrPointeeType(const ast_matchers::internal::Matcher< QualType > &TypeMatcher)
static const auto DefaultTupleTypes
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap