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