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