xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp (revision 11a411a49b62c129bba551df4587dd446fcdc660)
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"
11 using namespace clang::ast_matchers;
12 
13 namespace clang::tidy::modernize {
14 
15 namespace {
AST_MATCHER_P(InitListExpr,initCountLeq,unsigned,N)16 AST_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.
AST_MATCHER_P(NamedDecl,hasAnyNameIgnoringTemplates,std::vector<StringRef>,Names)24 AST_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.
AST_MATCHER_P(CallExpr,hasLastArgument,clang::ast_matchers::internal::Matcher<Expr>,InnerMatcher)61 AST_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).
AST_MATCHER(CXXMemberCallExpr,hasSameNumArgsAsDeclNumParams)73 AST_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 
AST_MATCHER(DeclRefExpr,hasExplicitTemplateArgs)81 AST_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().
hasTypeOrPointeeType(const ast_matchers::internal::Matcher<QualType> & TypeMatcher)88 auto hasTypeOrPointeeType(
89     const ast_matchers::internal::Matcher<QualType> &TypeMatcher) {
90   return anyOf(hasType(TypeMatcher),
91                hasType(pointerType(pointee(TypeMatcher))));
92 }
93 
94 // Matches if the node has canonical type matching any of the given names.
hasWantedType(llvm::ArrayRef<StringRef> TypeNames)95 auto hasWantedType(llvm::ArrayRef<StringRef> TypeNames) {
96   return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasAnyName(TypeNames))));
97 }
98 
99 // Matches member call expressions of the named method on the listed container
100 // types.
cxxMemberCallExprOnContainer(StringRef MethodName,llvm::ArrayRef<StringRef> ContainerNames)101 auto cxxMemberCallExprOnContainer(
102     StringRef MethodName, llvm::ArrayRef<StringRef> ContainerNames) {
103   return cxxMemberCallExpr(
104       hasDeclaration(functionDecl(hasName(MethodName))),
105       on(hasTypeOrPointeeType(hasWantedType(ContainerNames))));
106 }
107 
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";
132 } // namespace
133 
UseEmplaceCheck(StringRef Name,ClangTidyContext * Context)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))) {}
151 
registerMatchers(MatchFinder * Finder)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
156   // regression)
157   // + match for emplace calls that should be replaced with insertion
158   auto CallPushBack =
159       cxxMemberCallExprOnContainer("push_back", ContainersWithPushBack);
160   auto CallPush = cxxMemberCallExprOnContainer("push", ContainersWithPush);
161   auto CallPushFront =
162       cxxMemberCallExprOnContainer("push_front", ContainersWithPushFront);
163 
164   auto CallEmplacy = cxxMemberCallExpr(
165       hasDeclaration(
166           functionDecl(hasAnyNameIgnoringTemplates(EmplacyFunctions))),
167       on(hasTypeOrPointeeType(hasCanonicalType(hasDeclaration(
168           has(typedefNameDecl(hasName("value_type"),
169                               hasType(type(hasUnqualifiedDesugaredType(
170                                   recordType().bind("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(cxxConstructorDecl(ofClass(hasAnyName(SmartPointers))));
178 
179   // Bitfields binds only to consts and emplace_back take it by universal ref.
180   auto BitFieldAsArgument = hasAnyArgument(
181       ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField())))));
182 
183   // Initializer list can't be passed to universal reference.
184   auto InitializerListAsArgument = hasAnyArgument(
185       ignoringImplicit(allOf(cxxConstructExpr(isListInitialization()),
186                              unless(cxxTemporaryObjectExpr()))));
187 
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)));
193 
194   // emplace_back can't access private or protected constructors.
195   auto IsPrivateOrProtectedCtor =
196       hasDeclaration(cxxConstructorDecl(anyOf(isPrivate(), isProtected())));
197 
198   auto HasInitList = anyOf(has(ignoringImplicit(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("ctor");
209   auto HasConstructExpr = has(ignoringImplicit(SoughtConstructExpr));
210 
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);
221 
222   auto MakeTuple = ignoringImplicit(
223       callExpr(callee(expr(ignoringImplicit(declRefExpr(
224                    unless(hasExplicitTemplateArgs()),
225                    to(functionDecl(hasAnyName(TupleMakeFunctions))))))))
226           .bind("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(cxxConstructExpr(
231       has(materializeTemporaryExpr(MakeTuple)),
232       hasDeclaration(cxxConstructorDecl(ofClass(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("temporary_expr");
241 
242   auto HasConstructExprWithValueTypeType =
243       has(ignoringImplicit(cxxConstructExpr(
244           SoughtConstructExpr, hasType(type(hasUnqualifiedDesugaredType(
245                                    type(equalsBoundNode("value_type"))))))));
246 
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"))))))))));
255 
256   auto HasConstructExprWithValueTypeTypeAsLastArgument = hasLastArgument(
257       materializeTemporaryExpr(
258           anyOf(HasConstructExprWithValueTypeType,
259                 HasBracedInitListWithValueTypeType,
260                 has(cxxFunctionalCastExpr(HasConstructExprWithValueTypeType)),
261                 has(cxxFunctionalCastExpr(HasBracedInitListWithValueTypeType))))
262           .bind("temporary_expr"));
263 
264   Finder->addMatcher(
265       traverse(TK_AsIs, cxxMemberCallExpr(CallPushBack, has(SoughtParam),
266                                           unless(isInTemplateInstantiation()))
267                             .bind("push_back_call")),
268       this);
269 
270   Finder->addMatcher(
271       traverse(TK_AsIs, cxxMemberCallExpr(CallPush, has(SoughtParam),
272                                           unless(isInTemplateInstantiation()))
273                             .bind("push_call")),
274       this);
275 
276   Finder->addMatcher(
277       traverse(TK_AsIs, cxxMemberCallExpr(CallPushFront, has(SoughtParam),
278                                           unless(isInTemplateInstantiation()))
279                             .bind("push_front_call")),
280       this);
281 
282   Finder->addMatcher(
283       traverse(TK_AsIs,
284                cxxMemberCallExpr(
285                    CallEmplacy, HasConstructExprWithValueTypeTypeAsLastArgument,
286                    hasSameNumArgsAsDeclNumParams(),
287                    unless(isInTemplateInstantiation()))
288                    .bind("emplacy_call")),
289       this);
290 
291   Finder->addMatcher(
292       traverse(
293           TK_AsIs,
294           cxxMemberCallExpr(
295               CallEmplacy,
296               on(hasType(cxxRecordDecl(has(typedefNameDecl(
297                   hasName("value_type"),
298                   hasType(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")),
305       this);
306 }
307 
check(const MatchFinder::MatchResult & Result)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");
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(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(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");
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(ExprBegin, CallParensRange.getBegin());
395 
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());
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(0)->getBeginLoc()));
409   }
410 }
411 
storeOptions(ClangTidyOptions::OptionMap & Opts)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));
428 }
429 
430 } // namespace clang::tidy::modernize
431