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

source code of clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp