1 //===--- UseEmplaceCheck.cpp - clang-tidy----------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "UseEmplaceCheck.h"
10 #include "../utils/OptionsUtils.h"
11 using namespace clang::ast_matchers
;
13 namespace clang::tidy::modernize
{
16 AST_MATCHER_P(InitListExpr
, initCountLeq
, unsigned, N
) {
17 return Node
.getNumInits() <= N
;
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
24 AST_MATCHER_P(NamedDecl
, hasAnyNameIgnoringTemplates
, std::vector
<StringRef
>,
26 const std::string FullName
= "::" + Node
.getQualifiedNameAsString();
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
;
33 for (const auto &Character
: FullName
) {
34 if (Character
== '<') {
36 } else if (Character
== '>') {
38 } else if (Depth
== 0) {
39 FullNameTrimmed
.append(1, Character
);
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
)
51 } else if (FullNameTrimmedRef
.ends_with(Pattern
) &&
52 FullNameTrimmedRef
.drop_back(Pattern
.size()).ends_with("::")) {
60 // Checks if the given matcher is the last argument of the given CallExpr.
61 AST_MATCHER_P(CallExpr
, hasLastArgument
,
62 clang::ast_matchers::internal::Matcher
<Expr
>, InnerMatcher
) {
63 if (Node
.getNumArgs() == 0)
66 return InnerMatcher
.matches(*Node
.getArg(Node
.getNumArgs() - 1), Finder
,
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).
73 AST_MATCHER(CXXMemberCallExpr
, hasSameNumArgsAsDeclNumParams
) {
74 if (const FunctionTemplateDecl
*Primary
=
75 Node
.getMethodDecl()->getPrimaryTemplate())
76 return Node
.getNumArgs() == Primary
->getTemplatedDecl()->getNumParams();
78 return Node
.getNumArgs() == Node
.getMethodDecl()->getNumParams();
81 AST_MATCHER(DeclRefExpr
, hasExplicitTemplateArgs
) {
82 return Node
.hasExplicitTemplateArgs();
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
88 auto hasTypeOrPointeeType(
89 const ast_matchers::internal::Matcher
<QualType
> &TypeMatcher
) {
90 return anyOf(hasType(TypeMatcher
),
91 hasType(pointerType(pointee(TypeMatcher
))));
94 // Matches if the node has canonical type matching any of the given names.
95 auto hasWantedType(llvm::ArrayRef
<StringRef
> TypeNames
) {
96 return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasAnyName(TypeNames
))));
99 // Matches member call expressions of the named method on the listed container
101 auto cxxMemberCallExprOnContainer(
102 StringRef MethodName
, llvm::ArrayRef
<StringRef
> ContainerNames
) {
103 return cxxMemberCallExpr(
104 hasDeclaration(functionDecl(hasName(MethodName
))),
105 on(hasTypeOrPointeeType(hasWantedType(ContainerNames
))));
108 const auto DefaultContainersWithPushBack
=
109 "::std::vector; ::std::list; ::std::deque";
110 const auto DefaultContainersWithPush
=
111 "::std::stack; ::std::queue; ::std::priority_queue";
112 const auto DefaultContainersWithPushFront
=
113 "::std::forward_list; ::std::list; ::std::deque";
114 const auto DefaultSmartPointers
=
115 "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr";
116 const auto DefaultTupleTypes
= "::std::pair; ::std::tuple";
117 const auto DefaultTupleMakeFunctions
= "::std::make_pair; ::std::make_tuple";
118 const 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";
134 UseEmplaceCheck::UseEmplaceCheck(StringRef Name
, ClangTidyContext
*Context
)
135 : ClangTidyCheck(Name
, Context
), IgnoreImplicitConstructors(Options
.get(
136 "IgnoreImplicitConstructors", false)),
137 ContainersWithPushBack(utils::options::parseStringList(Options
.get(
138 "ContainersWithPushBack", DefaultContainersWithPushBack
))),
139 ContainersWithPush(utils::options::parseStringList(
140 Options
.get("ContainersWithPush", DefaultContainersWithPush
))),
141 ContainersWithPushFront(utils::options::parseStringList(Options
.get(
142 "ContainersWithPushFront", DefaultContainersWithPushFront
))),
143 SmartPointers(utils::options::parseStringList(
144 Options
.get("SmartPointers", DefaultSmartPointers
))),
145 TupleTypes(utils::options::parseStringList(
146 Options
.get("TupleTypes", DefaultTupleTypes
))),
147 TupleMakeFunctions(utils::options::parseStringList(
148 Options
.get("TupleMakeFunctions", DefaultTupleMakeFunctions
))),
149 EmplacyFunctions(utils::options::parseStringList(
150 Options
.get("EmplacyFunctions", DefaultEmplacyFunctions
))) {}
152 void 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
157 // + match for emplace calls that should be replaced with insertion
159 cxxMemberCallExprOnContainer("push_back", ContainersWithPushBack
);
160 auto CallPush
= cxxMemberCallExprOnContainer("push", ContainersWithPush
);
162 cxxMemberCallExprOnContainer("push_front", ContainersWithPushFront
);
164 auto CallEmplacy
= cxxMemberCallExpr(
166 functionDecl(hasAnyNameIgnoringTemplates(EmplacyFunctions
))),
167 on(hasTypeOrPointeeType(hasCanonicalType(hasDeclaration(
168 has(typedefNameDecl(hasName("value_type"),
169 hasType(type(hasUnqualifiedDesugaredType(
170 recordType().bind("value_type")))))))))));
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(cxxConstructorDecl(ofClass(hasAnyName(SmartPointers
))));
179 // Bitfields binds only to consts and emplace_back take it by universal ref.
180 auto BitFieldAsArgument
= hasAnyArgument(
181 ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField())))));
183 // Initializer list can't be passed to universal reference.
184 auto InitializerListAsArgument
= hasAnyArgument(
185 ignoringImplicit(allOf(cxxConstructExpr(isListInitialization()),
186 unless(cxxTemporaryObjectExpr()))));
188 // We could have leak of resource.
189 auto NewExprAsArgument
= hasAnyArgument(ignoringImplicit(cxxNewExpr()));
190 // We would call another constructor.
191 auto ConstructingDerived
=
192 hasParent(implicitCastExpr(hasCastKind(CastKind::CK_DerivedToBase
)));
194 // emplace_back can't access private or protected constructors.
195 auto IsPrivateOrProtectedCtor
=
196 hasDeclaration(cxxConstructorDecl(anyOf(isPrivate(), isProtected())));
198 auto HasInitList
= anyOf(has(ignoringImplicit(initListExpr())),
199 has(cxxStdInitializerListExpr()));
201 // FIXME: Discard 0/NULL (as nullptr), static inline const data members,
202 // overloaded functions and template names.
203 auto SoughtConstructExpr
=
205 unless(anyOf(IsCtorOfSmartPtr
, HasInitList
, BitFieldAsArgument
,
206 InitializerListAsArgument
, NewExprAsArgument
,
207 ConstructingDerived
, IsPrivateOrProtectedCtor
)))
209 auto HasConstructExpr
= has(ignoringImplicit(SoughtConstructExpr
));
211 // allow for T{} to be replaced, even if no CTOR is declared
212 auto HasConstructInitListExpr
= has(initListExpr(
213 initCountLeq(1), anyOf(allOf(has(SoughtConstructExpr
),
214 has(cxxConstructExpr(argumentCountIs(0)))),
215 has(cxxBindTemporaryExpr(
216 has(SoughtConstructExpr
),
217 has(cxxConstructExpr(argumentCountIs(0))))))));
218 auto HasBracedInitListExpr
=
219 anyOf(has(cxxBindTemporaryExpr(HasConstructInitListExpr
)),
220 HasConstructInitListExpr
);
222 auto MakeTuple
= ignoringImplicit(
223 callExpr(callee(expr(ignoringImplicit(declRefExpr(
224 unless(hasExplicitTemplateArgs()),
225 to(functionDecl(hasAnyName(TupleMakeFunctions
))))))))
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(cxxConstructExpr(
231 has(materializeTemporaryExpr(MakeTuple
)),
232 hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(TupleTypes
))))));
235 materializeTemporaryExpr(
236 anyOf(has(MakeTuple
), has(MakeTupleCtor
), HasConstructExpr
,
237 HasBracedInitListExpr
,
238 has(cxxFunctionalCastExpr(HasConstructExpr
)),
239 has(cxxFunctionalCastExpr(HasBracedInitListExpr
))))
240 .bind("temporary_expr");
242 auto HasConstructExprWithValueTypeType
=
243 has(ignoringImplicit(cxxConstructExpr(
244 SoughtConstructExpr
, hasType(type(hasUnqualifiedDesugaredType(
245 type(equalsBoundNode("value_type"))))))));
247 auto HasBracedInitListWithValueTypeType
=
248 anyOf(allOf(HasConstructInitListExpr
,
249 has(initListExpr(hasType(type(hasUnqualifiedDesugaredType(
250 type(equalsBoundNode("value_type")))))))),
251 has(cxxBindTemporaryExpr(
252 HasConstructInitListExpr
,
253 has(initListExpr(hasType(type(hasUnqualifiedDesugaredType(
254 type(equalsBoundNode("value_type"))))))))));
256 auto HasConstructExprWithValueTypeTypeAsLastArgument
= hasLastArgument(
257 materializeTemporaryExpr(
258 anyOf(HasConstructExprWithValueTypeType
,
259 HasBracedInitListWithValueTypeType
,
260 has(cxxFunctionalCastExpr(HasConstructExprWithValueTypeType
)),
261 has(cxxFunctionalCastExpr(HasBracedInitListWithValueTypeType
))))
262 .bind("temporary_expr"));
265 traverse(TK_AsIs
, cxxMemberCallExpr(CallPushBack
, has(SoughtParam
),
266 unless(isInTemplateInstantiation()))
267 .bind("push_back_call")),
271 traverse(TK_AsIs
, cxxMemberCallExpr(CallPush
, has(SoughtParam
),
272 unless(isInTemplateInstantiation()))
277 traverse(TK_AsIs
, cxxMemberCallExpr(CallPushFront
, has(SoughtParam
),
278 unless(isInTemplateInstantiation()))
279 .bind("push_front_call")),
285 CallEmplacy
, HasConstructExprWithValueTypeTypeAsLastArgument
,
286 hasSameNumArgsAsDeclNumParams(),
287 unless(isInTemplateInstantiation()))
288 .bind("emplacy_call")),
296 on(hasType(cxxRecordDecl(has(typedefNameDecl(
297 hasName("value_type"),
299 hasUnqualifiedDesugaredType(recordType(hasDeclaration(
300 cxxRecordDecl(hasAnyName(SmallVector
<StringRef
, 2>(
301 TupleTypes
.begin(), TupleTypes
.end()))))))))))))),
302 has(MakeTuple
), hasSameNumArgsAsDeclNumParams(),
303 unless(isInTemplateInstantiation()))
304 .bind("emplacy_call")),
308 void UseEmplaceCheck::check(const MatchFinder::MatchResult
&Result
) {
309 const auto *PushBackCall
=
310 Result
.Nodes
.getNodeAs
<CXXMemberCallExpr
>("push_back_call");
311 const auto *PushCall
= Result
.Nodes
.getNodeAs
<CXXMemberCallExpr
>("push_call");
312 const auto *PushFrontCall
=
313 Result
.Nodes
.getNodeAs
<CXXMemberCallExpr
>("push_front_call");
314 const auto *EmplacyCall
=
315 Result
.Nodes
.getNodeAs
<CXXMemberCallExpr
>("emplacy_call");
316 const auto *CtorCall
= Result
.Nodes
.getNodeAs
<CXXConstructExpr
>("ctor");
317 const auto *MakeCall
= Result
.Nodes
.getNodeAs
<CallExpr
>("make");
318 const auto *TemporaryExpr
=
319 Result
.Nodes
.getNodeAs
<MaterializeTemporaryExpr
>("temporary_expr");
321 const CXXMemberCallExpr
*Call
= [&]() {
329 return PushFrontCall
;
334 assert(Call
&& "No call matched");
335 assert((CtorCall
|| MakeCall
) && "No push_back parameter matched");
337 if (IgnoreImplicitConstructors
&& CtorCall
&& CtorCall
->getNumArgs() >= 1 &&
338 CtorCall
->getArg(0)->getSourceRange() == CtorCall
->getSourceRange())
341 const auto FunctionNameSourceRange
= CharSourceRange::getCharRange(
342 Call
->getExprLoc(), Call
->getArg(0)->getExprLoc());
346 ? diag(TemporaryExpr
? TemporaryExpr
->getBeginLoc()
347 : CtorCall
? CtorCall
->getBeginLoc()
348 : MakeCall
->getBeginLoc(),
349 "unnecessary temporary object created while calling %0")
350 : diag(Call
->getExprLoc(), "use emplace%select{|_back|_front}0 "
351 "instead of push%select{|_back|_front}0");
353 Diag
<< Call
->getMethodDecl()->getName();
356 else if (PushBackCall
)
361 if (FunctionNameSourceRange
.getBegin().isMacroID())
365 const char *EmplacePrefix
= MakeCall
? "emplace_back" : "emplace_back(";
366 Diag
<< FixItHint::CreateReplacement(FunctionNameSourceRange
,
368 } else if (PushCall
) {
369 const char *EmplacePrefix
= MakeCall
? "emplace" : "emplace(";
370 Diag
<< FixItHint::CreateReplacement(FunctionNameSourceRange
,
372 } else if (PushFrontCall
) {
373 const char *EmplacePrefix
= MakeCall
? "emplace_front" : "emplace_front(";
374 Diag
<< FixItHint::CreateReplacement(FunctionNameSourceRange
,
378 const SourceRange CallParensRange
=
379 MakeCall
? SourceRange(MakeCall
->getCallee()->getEndLoc(),
380 MakeCall
->getRParenLoc())
381 : CtorCall
->getParenOrBraceRange();
383 // Finish if there is no explicit constructor call.
384 if (CallParensRange
.getBegin().isInvalid())
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();
392 // Range for constructor name and opening brace.
393 const auto ParamCallSourceRange
=
394 CharSourceRange::getTokenRange(ExprBegin
, CallParensRange
.getBegin());
396 // Range for constructor closing brace and end of temporary expr.
397 const auto EndCallSourceRange
= CharSourceRange::getTokenRange(
398 CallParensRange
.getEnd(),
399 TemporaryExpr
? TemporaryExpr
->getEndLoc() : CallParensRange
.getEnd());
401 Diag
<< FixItHint::CreateRemoval(ParamCallSourceRange
)
402 << FixItHint::CreateRemoval(EndCallSourceRange
);
404 if (MakeCall
&& EmplacyCall
) {
405 // Remove extra left parenthesis
406 Diag
<< FixItHint::CreateRemoval(
407 CharSourceRange::getCharRange(MakeCall
->getCallee()->getEndLoc(),
408 MakeCall
->getArg(0)->getBeginLoc()));
412 void UseEmplaceCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
413 Options
.store(Opts
, "IgnoreImplicitConstructors", IgnoreImplicitConstructors
);
414 Options
.store(Opts
, "ContainersWithPushBack",
415 utils::options::serializeStringList(ContainersWithPushBack
));
416 Options
.store(Opts
, "ContainersWithPush",
417 utils::options::serializeStringList(ContainersWithPush
));
418 Options
.store(Opts
, "ContainersWithPushFront",
419 utils::options::serializeStringList(ContainersWithPushFront
));
420 Options
.store(Opts
, "SmartPointers",
421 utils::options::serializeStringList(SmartPointers
));
422 Options
.store(Opts
, "TupleTypes",
423 utils::options::serializeStringList(TupleTypes
));
424 Options
.store(Opts
, "TupleMakeFunctions",
425 utils::options::serializeStringList(TupleMakeFunctions
));
426 Options
.store(Opts
, "EmplacyFunctions",
427 utils::options::serializeStringList(EmplacyFunctions
));
430 } // namespace clang::tidy::modernize