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 // Identical to hasAnyName, except it does not take template specifiers into
17 // account. This is used to match the functions names as in
18 // DefaultEmplacyFunctions below without caring about the template types of the
20 AST_MATCHER_P(NamedDecl
, hasAnyNameIgnoringTemplates
, std::vector
<StringRef
>,
22 const std::string FullName
= "::" + Node
.getQualifiedNameAsString();
24 // This loop removes template specifiers by only keeping characters not within
25 // template brackets. We keep a depth count to handle nested templates. For
26 // example, it'll transform a::b<c<d>>::e<f> to simply a::b::e.
27 std::string FullNameTrimmed
;
29 for (const auto &Character
: FullName
) {
30 if (Character
== '<') {
32 } else if (Character
== '>') {
34 } else if (Depth
== 0) {
35 FullNameTrimmed
.append(1, Character
);
39 // This loop is taken from HasNameMatcher::matchesNodeFullSlow in
40 // clang/lib/ASTMatchers/ASTMatchersInternal.cpp and checks whether
41 // FullNameTrimmed matches any of the given Names.
42 const StringRef FullNameTrimmedRef
= FullNameTrimmed
;
43 for (const StringRef Pattern
: Names
) {
44 if (Pattern
.startswith("::")) {
45 if (FullNameTrimmed
== Pattern
)
47 } else if (FullNameTrimmedRef
.endswith(Pattern
) &&
48 FullNameTrimmedRef
.drop_back(Pattern
.size()).endswith("::")) {
56 // Checks if the given matcher is the last argument of the given CallExpr.
57 AST_MATCHER_P(CallExpr
, hasLastArgument
,
58 clang::ast_matchers::internal::Matcher
<Expr
>, InnerMatcher
) {
59 if (Node
.getNumArgs() == 0)
62 return InnerMatcher
.matches(*Node
.getArg(Node
.getNumArgs() - 1), Finder
,
66 // Checks if the given member call has the same number of arguments as the
67 // function had parameters defined (this is useful to check if there is only one
68 // variadic argument).
69 AST_MATCHER(CXXMemberCallExpr
, hasSameNumArgsAsDeclNumParams
) {
70 if (const FunctionTemplateDecl
*Primary
=
71 Node
.getMethodDecl()->getPrimaryTemplate())
72 return Node
.getNumArgs() == Primary
->getTemplatedDecl()->getNumParams();
74 return Node
.getNumArgs() == Node
.getMethodDecl()->getNumParams();
77 AST_MATCHER(DeclRefExpr
, hasExplicitTemplateArgs
) {
78 return Node
.hasExplicitTemplateArgs();
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
84 auto hasTypeOrPointeeType(
85 const ast_matchers::internal::Matcher
<QualType
> &TypeMatcher
) {
86 return anyOf(hasType(TypeMatcher
),
87 hasType(pointerType(pointee(TypeMatcher
))));
90 // Matches if the node has canonical type matching any of the given names.
91 auto hasWantedType(llvm::ArrayRef
<StringRef
> TypeNames
) {
92 return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasAnyName(TypeNames
))));
95 // Matches member call expressions of the named method on the listed container
97 auto cxxMemberCallExprOnContainer(
98 StringRef MethodName
, llvm::ArrayRef
<StringRef
> ContainerNames
) {
99 return cxxMemberCallExpr(
100 hasDeclaration(functionDecl(hasName(MethodName
))),
101 on(hasTypeOrPointeeType(hasWantedType(ContainerNames
))));
104 const auto DefaultContainersWithPushBack
=
105 "::std::vector; ::std::list; ::std::deque";
106 const auto DefaultContainersWithPush
=
107 "::std::stack; ::std::queue; ::std::priority_queue";
108 const auto DefaultContainersWithPushFront
=
109 "::std::forward_list; ::std::list; ::std::deque";
110 const auto DefaultSmartPointers
=
111 "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr";
112 const auto DefaultTupleTypes
= "::std::pair; ::std::tuple";
113 const auto DefaultTupleMakeFunctions
= "::std::make_pair; ::std::make_tuple";
114 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";
130 UseEmplaceCheck::UseEmplaceCheck(StringRef Name
, ClangTidyContext
*Context
)
131 : ClangTidyCheck(Name
, Context
), IgnoreImplicitConstructors(Options
.get(
132 "IgnoreImplicitConstructors", false)),
133 ContainersWithPushBack(utils::options::parseStringList(Options
.get(
134 "ContainersWithPushBack", DefaultContainersWithPushBack
))),
135 ContainersWithPush(utils::options::parseStringList(
136 Options
.get("ContainersWithPush", DefaultContainersWithPush
))),
137 ContainersWithPushFront(utils::options::parseStringList(Options
.get(
138 "ContainersWithPushFront", DefaultContainersWithPushFront
))),
139 SmartPointers(utils::options::parseStringList(
140 Options
.get("SmartPointers", DefaultSmartPointers
))),
141 TupleTypes(utils::options::parseStringList(
142 Options
.get("TupleTypes", DefaultTupleTypes
))),
143 TupleMakeFunctions(utils::options::parseStringList(
144 Options
.get("TupleMakeFunctions", DefaultTupleMakeFunctions
))),
145 EmplacyFunctions(utils::options::parseStringList(
146 Options
.get("EmplacyFunctions", DefaultEmplacyFunctions
))) {}
148 void UseEmplaceCheck::registerMatchers(MatchFinder
*Finder
) {
149 // FIXME: Bunch of functionality that could be easily added:
150 // + add handling of `insert` for stl associative container, but be careful
151 // because this requires special treatment (it could cause performance
153 // + match for emplace calls that should be replaced with insertion
155 cxxMemberCallExprOnContainer("push_back", ContainersWithPushBack
);
156 auto CallPush
= cxxMemberCallExprOnContainer("push", ContainersWithPush
);
158 cxxMemberCallExprOnContainer("push_front", ContainersWithPushFront
);
160 auto CallEmplacy
= cxxMemberCallExpr(
162 functionDecl(hasAnyNameIgnoringTemplates(EmplacyFunctions
))),
163 on(hasTypeOrPointeeType(hasCanonicalType(hasDeclaration(
164 has(typedefNameDecl(hasName("value_type"),
165 hasType(type(hasUnqualifiedDesugaredType(
166 recordType().bind("value_type")))))))))));
168 // We can't replace push_backs of smart pointer because
169 // if emplacement fails (f.e. bad_alloc in vector) we will have leak of
170 // passed pointer because smart pointer won't be constructed
171 // (and destructed) as in push_back case.
172 auto IsCtorOfSmartPtr
=
173 hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(SmartPointers
))));
175 // Bitfields binds only to consts and emplace_back take it by universal ref.
176 auto BitFieldAsArgument
= hasAnyArgument(
177 ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField())))));
179 // Initializer list can't be passed to universal reference.
180 auto InitializerListAsArgument
= hasAnyArgument(
181 ignoringImplicit(allOf(cxxConstructExpr(isListInitialization()),
182 unless(cxxTemporaryObjectExpr()))));
184 // We could have leak of resource.
185 auto NewExprAsArgument
= hasAnyArgument(ignoringImplicit(cxxNewExpr()));
186 // We would call another constructor.
187 auto ConstructingDerived
=
188 hasParent(implicitCastExpr(hasCastKind(CastKind::CK_DerivedToBase
)));
190 // emplace_back can't access private or protected constructors.
191 auto IsPrivateOrProtectedCtor
=
192 hasDeclaration(cxxConstructorDecl(anyOf(isPrivate(), isProtected())));
194 auto HasInitList
= anyOf(has(ignoringImplicit(initListExpr())),
195 has(cxxStdInitializerListExpr()));
197 // FIXME: Discard 0/NULL (as nullptr), static inline const data members,
198 // overloaded functions and template names.
199 auto SoughtConstructExpr
=
201 unless(anyOf(IsCtorOfSmartPtr
, HasInitList
, BitFieldAsArgument
,
202 InitializerListAsArgument
, NewExprAsArgument
,
203 ConstructingDerived
, IsPrivateOrProtectedCtor
)))
205 auto HasConstructExpr
= has(ignoringImplicit(SoughtConstructExpr
));
207 // allow for T{} to be replaced, even if no CTOR is declared
208 auto HasConstructInitListExpr
= has(initListExpr(anyOf(
209 allOf(has(SoughtConstructExpr
),
210 has(cxxConstructExpr(argumentCountIs(0)))),
211 has(cxxBindTemporaryExpr(has(SoughtConstructExpr
),
212 has(cxxConstructExpr(argumentCountIs(0))))))));
213 auto HasBracedInitListExpr
=
214 anyOf(has(cxxBindTemporaryExpr(HasConstructInitListExpr
)),
215 HasConstructInitListExpr
);
217 auto MakeTuple
= ignoringImplicit(
218 callExpr(callee(expr(ignoringImplicit(declRefExpr(
219 unless(hasExplicitTemplateArgs()),
220 to(functionDecl(hasAnyName(TupleMakeFunctions
))))))))
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
))))));
230 materializeTemporaryExpr(
231 anyOf(has(MakeTuple
), has(MakeTupleCtor
), HasConstructExpr
,
232 HasBracedInitListExpr
,
233 has(cxxFunctionalCastExpr(HasConstructExpr
)),
234 has(cxxFunctionalCastExpr(HasBracedInitListExpr
))))
235 .bind("temporary_expr");
237 auto HasConstructExprWithValueTypeType
=
238 has(ignoringImplicit(cxxConstructExpr(
239 SoughtConstructExpr
, hasType(type(hasUnqualifiedDesugaredType(
240 type(equalsBoundNode("value_type"))))))));
242 auto HasBracedInitListWithValueTypeType
=
243 anyOf(allOf(HasConstructInitListExpr
,
244 has(initListExpr(hasType(type(hasUnqualifiedDesugaredType(
245 type(equalsBoundNode("value_type")))))))),
246 has(cxxBindTemporaryExpr(
247 HasConstructInitListExpr
,
248 has(initListExpr(hasType(type(hasUnqualifiedDesugaredType(
249 type(equalsBoundNode("value_type"))))))))));
251 auto HasConstructExprWithValueTypeTypeAsLastArgument
= hasLastArgument(
252 materializeTemporaryExpr(
253 anyOf(HasConstructExprWithValueTypeType
,
254 HasBracedInitListWithValueTypeType
,
255 has(cxxFunctionalCastExpr(HasConstructExprWithValueTypeType
)),
256 has(cxxFunctionalCastExpr(HasBracedInitListWithValueTypeType
))))
257 .bind("temporary_expr"));
260 traverse(TK_AsIs
, cxxMemberCallExpr(CallPushBack
, has(SoughtParam
),
261 unless(isInTemplateInstantiation()))
262 .bind("push_back_call")),
266 traverse(TK_AsIs
, cxxMemberCallExpr(CallPush
, has(SoughtParam
),
267 unless(isInTemplateInstantiation()))
272 traverse(TK_AsIs
, cxxMemberCallExpr(CallPushFront
, has(SoughtParam
),
273 unless(isInTemplateInstantiation()))
274 .bind("push_front_call")),
280 CallEmplacy
, HasConstructExprWithValueTypeTypeAsLastArgument
,
281 hasSameNumArgsAsDeclNumParams(),
282 unless(isInTemplateInstantiation()))
283 .bind("emplacy_call")),
291 on(hasType(cxxRecordDecl(has(typedefNameDecl(
292 hasName("value_type"),
294 hasUnqualifiedDesugaredType(recordType(hasDeclaration(
295 cxxRecordDecl(hasAnyName(SmallVector
<StringRef
, 2>(
296 TupleTypes
.begin(), TupleTypes
.end()))))))))))))),
297 has(MakeTuple
), hasSameNumArgsAsDeclNumParams(),
298 unless(isInTemplateInstantiation()))
299 .bind("emplacy_call")),
303 void UseEmplaceCheck::check(const MatchFinder::MatchResult
&Result
) {
304 const auto *PushBackCall
=
305 Result
.Nodes
.getNodeAs
<CXXMemberCallExpr
>("push_back_call");
306 const auto *PushCall
= Result
.Nodes
.getNodeAs
<CXXMemberCallExpr
>("push_call");
307 const auto *PushFrontCall
=
308 Result
.Nodes
.getNodeAs
<CXXMemberCallExpr
>("push_front_call");
309 const auto *EmplacyCall
=
310 Result
.Nodes
.getNodeAs
<CXXMemberCallExpr
>("emplacy_call");
311 const auto *CtorCall
= Result
.Nodes
.getNodeAs
<CXXConstructExpr
>("ctor");
312 const auto *MakeCall
= Result
.Nodes
.getNodeAs
<CallExpr
>("make");
313 const auto *TemporaryExpr
=
314 Result
.Nodes
.getNodeAs
<MaterializeTemporaryExpr
>("temporary_expr");
316 const CXXMemberCallExpr
*Call
= [&]() {
324 return PushFrontCall
;
329 assert(Call
&& "No call matched");
330 assert((CtorCall
|| MakeCall
) && "No push_back parameter matched");
332 if (IgnoreImplicitConstructors
&& CtorCall
&& CtorCall
->getNumArgs() >= 1 &&
333 CtorCall
->getArg(0)->getSourceRange() == CtorCall
->getSourceRange())
336 const auto FunctionNameSourceRange
= CharSourceRange::getCharRange(
337 Call
->getExprLoc(), Call
->getArg(0)->getExprLoc());
341 ? diag(TemporaryExpr
? TemporaryExpr
->getBeginLoc()
342 : CtorCall
? CtorCall
->getBeginLoc()
343 : MakeCall
->getBeginLoc(),
344 "unnecessary temporary object created while calling %0")
345 : diag(Call
->getExprLoc(), "use emplace%select{|_back|_front}0 "
346 "instead of push%select{|_back|_front}0");
348 Diag
<< Call
->getMethodDecl()->getName();
351 else if (PushBackCall
)
356 if (FunctionNameSourceRange
.getBegin().isMacroID())
360 const char *EmplacePrefix
= MakeCall
? "emplace_back" : "emplace_back(";
361 Diag
<< FixItHint::CreateReplacement(FunctionNameSourceRange
,
363 } else if (PushCall
) {
364 const char *EmplacePrefix
= MakeCall
? "emplace" : "emplace(";
365 Diag
<< FixItHint::CreateReplacement(FunctionNameSourceRange
,
367 } else if (PushFrontCall
) {
368 const char *EmplacePrefix
= MakeCall
? "emplace_front" : "emplace_front(";
369 Diag
<< FixItHint::CreateReplacement(FunctionNameSourceRange
,
373 const SourceRange CallParensRange
=
374 MakeCall
? SourceRange(MakeCall
->getCallee()->getEndLoc(),
375 MakeCall
->getRParenLoc())
376 : CtorCall
->getParenOrBraceRange();
378 // Finish if there is no explicit constructor call.
379 if (CallParensRange
.getBegin().isInvalid())
382 // FIXME: Will there ever be a CtorCall, if there is no TemporaryExpr?
383 const SourceLocation ExprBegin
= TemporaryExpr
? TemporaryExpr
->getExprLoc()
384 : CtorCall
? CtorCall
->getExprLoc()
385 : MakeCall
->getExprLoc();
387 // Range for constructor name and opening brace.
388 const auto ParamCallSourceRange
=
389 CharSourceRange::getTokenRange(ExprBegin
, CallParensRange
.getBegin());
391 // Range for constructor closing brace and end of temporary expr.
392 const auto EndCallSourceRange
= CharSourceRange::getTokenRange(
393 CallParensRange
.getEnd(),
394 TemporaryExpr
? TemporaryExpr
->getEndLoc() : CallParensRange
.getEnd());
396 Diag
<< FixItHint::CreateRemoval(ParamCallSourceRange
)
397 << FixItHint::CreateRemoval(EndCallSourceRange
);
399 if (MakeCall
&& EmplacyCall
) {
400 // Remove extra left parenthesis
401 Diag
<< FixItHint::CreateRemoval(
402 CharSourceRange::getCharRange(MakeCall
->getCallee()->getEndLoc(),
403 MakeCall
->getArg(0)->getBeginLoc()));
407 void UseEmplaceCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
408 Options
.store(Opts
, "IgnoreImplicitConstructors", IgnoreImplicitConstructors
);
409 Options
.store(Opts
, "ContainersWithPushBack",
410 utils::options::serializeStringList(ContainersWithPushBack
));
411 Options
.store(Opts
, "ContainersWithPush",
412 utils::options::serializeStringList(ContainersWithPush
));
413 Options
.store(Opts
, "ContainersWithPushFront",
414 utils::options::serializeStringList(ContainersWithPushFront
));
415 Options
.store(Opts
, "SmartPointers",
416 utils::options::serializeStringList(SmartPointers
));
417 Options
.store(Opts
, "TupleTypes",
418 utils::options::serializeStringList(TupleTypes
));
419 Options
.store(Opts
, "TupleMakeFunctions",
420 utils::options::serializeStringList(TupleMakeFunctions
));
421 Options
.store(Opts
, "EmplacyFunctions",
422 utils::options::serializeStringList(EmplacyFunctions
));
425 } // namespace clang::tidy::modernize