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 {
14 namespace tidy {
15 namespace modernize {
16 
17 namespace {
18 // Identical to hasAnyName, except it does not take template specifiers into
19 // account. This is used to match the functions names as in
20 // DefaultEmplacyFunctions below without caring about the template types of the
21 // containers.
AST_MATCHER_P(NamedDecl,hasAnyNameIgnoringTemplates,std::vector<StringRef>,Names)22 AST_MATCHER_P(NamedDecl, hasAnyNameIgnoringTemplates, std::vector<StringRef>,
23               Names) {
24   const std::string FullName = "::" + Node.getQualifiedNameAsString();
25 
26   // This loop removes template specifiers by only keeping characters not within
27   // template brackets. We keep a depth count to handle nested templates. For
28   // example, it'll transform a::b<c<d>>::e<f> to simply a::b::e.
29   std::string FullNameTrimmed;
30   int Depth = 0;
31   for (const auto &Character : FullName) {
32     if (Character == '<') {
33       ++Depth;
34     } else if (Character == '>') {
35       --Depth;
36     } else if (Depth == 0) {
37       FullNameTrimmed.append(1, Character);
38     }
39   }
40 
41   // This loop is taken from HasNameMatcher::matchesNodeFullSlow in
42   // clang/lib/ASTMatchers/ASTMatchersInternal.cpp and checks whether
43   // FullNameTrimmed matches any of the given Names.
44   const StringRef FullNameTrimmedRef = FullNameTrimmed;
45   for (const StringRef Pattern : Names) {
46     if (Pattern.startswith("::")) {
47       if (FullNameTrimmed == Pattern)
48         return true;
49     } else if (FullNameTrimmedRef.endswith(Pattern) &&
50                FullNameTrimmedRef.drop_back(Pattern.size()).endswith("::")) {
51       return true;
52     }
53   }
54 
55   return false;
56 }
57 
58 // 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)59 AST_MATCHER_P(CallExpr, hasLastArgument,
60               clang::ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
61   if (Node.getNumArgs() == 0)
62     return false;
63 
64   return InnerMatcher.matches(*Node.getArg(Node.getNumArgs() - 1), Finder,
65                               Builder);
66 }
67 
68 // Checks if the given member call has the same number of arguments as the
69 // function had parameters defined (this is useful to check if there is only one
70 // variadic argument).
AST_MATCHER(CXXMemberCallExpr,hasSameNumArgsAsDeclNumParams)71 AST_MATCHER(CXXMemberCallExpr, hasSameNumArgsAsDeclNumParams) {
72   if (Node.getMethodDecl()->isFunctionTemplateSpecialization())
73     return Node.getNumArgs() == Node.getMethodDecl()
74                                     ->getPrimaryTemplate()
75                                     ->getTemplatedDecl()
76                                     ->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 const auto DefaultContainersWithPushBack =
86     "::std::vector; ::std::list; ::std::deque";
87 const auto DefaultSmartPointers =
88     "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr";
89 const auto DefaultTupleTypes = "::std::pair; ::std::tuple";
90 const auto DefaultTupleMakeFunctions = "::std::make_pair; ::std::make_tuple";
91 const auto DefaultEmplacyFunctions =
92     "vector::emplace_back; vector::emplace;"
93     "deque::emplace; deque::emplace_front; deque::emplace_back;"
94     "forward_list::emplace_after; forward_list::emplace_front;"
95     "list::emplace; list::emplace_back; list::emplace_front;"
96     "set::emplace; set::emplace_hint;"
97     "map::emplace; map::emplace_hint;"
98     "multiset::emplace; multiset::emplace_hint;"
99     "multimap::emplace; multimap::emplace_hint;"
100     "unordered_set::emplace; unordered_set::emplace_hint;"
101     "unordered_map::emplace; unordered_map::emplace_hint;"
102     "unordered_multiset::emplace; unordered_multiset::emplace_hint;"
103     "unordered_multimap::emplace; unordered_multimap::emplace_hint;"
104     "stack::emplace; queue::emplace; priority_queue::emplace";
105 } // namespace
106 
UseEmplaceCheck(StringRef Name,ClangTidyContext * Context)107 UseEmplaceCheck::UseEmplaceCheck(StringRef Name, ClangTidyContext *Context)
108     : ClangTidyCheck(Name, Context), IgnoreImplicitConstructors(Options.get(
109                                          "IgnoreImplicitConstructors", false)),
110       ContainersWithPushBack(utils::options::parseStringList(Options.get(
111           "ContainersWithPushBack", DefaultContainersWithPushBack))),
112       SmartPointers(utils::options::parseStringList(
113           Options.get("SmartPointers", DefaultSmartPointers))),
114       TupleTypes(utils::options::parseStringList(
115           Options.get("TupleTypes", DefaultTupleTypes))),
116       TupleMakeFunctions(utils::options::parseStringList(
117           Options.get("TupleMakeFunctions", DefaultTupleMakeFunctions))),
118       EmplacyFunctions(utils::options::parseStringList(
119           Options.get("EmplacyFunctions", DefaultEmplacyFunctions))) {}
120 
registerMatchers(MatchFinder * Finder)121 void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
122   // FIXME: Bunch of functionality that could be easily added:
123   // + add handling of `push_front` for std::forward_list, std::list
124   // and std::deque.
125   // + add handling of `push` for std::stack, std::queue, std::priority_queue
126   // + add handling of `insert` for stl associative container, but be careful
127   // because this requires special treatment (it could cause performance
128   // regression)
129   // + match for emplace calls that should be replaced with insertion
130   auto CallPushBack = cxxMemberCallExpr(
131       hasDeclaration(functionDecl(hasName("push_back"))),
132       on(hasType(cxxRecordDecl(hasAnyName(ContainersWithPushBack)))));
133 
134   auto CallEmplacy = cxxMemberCallExpr(
135       hasDeclaration(
136           functionDecl(hasAnyNameIgnoringTemplates(EmplacyFunctions))),
137       on(hasType(cxxRecordDecl(has(typedefNameDecl(
138           hasName("value_type"), hasType(type(hasUnqualifiedDesugaredType(
139                                      recordType().bind("value_type"))))))))));
140 
141   // We can't replace push_backs of smart pointer because
142   // if emplacement fails (f.e. bad_alloc in vector) we will have leak of
143   // passed pointer because smart pointer won't be constructed
144   // (and destructed) as in push_back case.
145   auto IsCtorOfSmartPtr =
146       hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(SmartPointers))));
147 
148   // Bitfields binds only to consts and emplace_back take it by universal ref.
149   auto BitFieldAsArgument = hasAnyArgument(
150       ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField())))));
151 
152   // Initializer list can't be passed to universal reference.
153   auto InitializerListAsArgument = hasAnyArgument(
154       ignoringImplicit(cxxConstructExpr(isListInitialization())));
155 
156   // We could have leak of resource.
157   auto NewExprAsArgument = hasAnyArgument(ignoringImplicit(cxxNewExpr()));
158   // We would call another constructor.
159   auto ConstructingDerived =
160       hasParent(implicitCastExpr(hasCastKind(CastKind::CK_DerivedToBase)));
161 
162   // emplace_back can't access private or protected constructors.
163   auto IsPrivateOrProtectedCtor =
164       hasDeclaration(cxxConstructorDecl(anyOf(isPrivate(), isProtected())));
165 
166   auto HasInitList = anyOf(has(ignoringImplicit(initListExpr())),
167                            has(cxxStdInitializerListExpr()));
168 
169   // FIXME: Discard 0/NULL (as nullptr), static inline const data members,
170   // overloaded functions and template names.
171   auto SoughtConstructExpr =
172       cxxConstructExpr(
173           unless(anyOf(IsCtorOfSmartPtr, HasInitList, BitFieldAsArgument,
174                        InitializerListAsArgument, NewExprAsArgument,
175                        ConstructingDerived, IsPrivateOrProtectedCtor)))
176           .bind("ctor");
177   auto HasConstructExpr = has(ignoringImplicit(SoughtConstructExpr));
178 
179   auto MakeTuple = ignoringImplicit(
180       callExpr(callee(expr(ignoringImplicit(declRefExpr(
181                    unless(hasExplicitTemplateArgs()),
182                    to(functionDecl(hasAnyName(TupleMakeFunctions))))))))
183           .bind("make"));
184 
185   // make_something can return type convertible to container's element type.
186   // Allow the conversion only on containers of pairs.
187   auto MakeTupleCtor = ignoringImplicit(cxxConstructExpr(
188       has(materializeTemporaryExpr(MakeTuple)),
189       hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(TupleTypes))))));
190 
191   auto SoughtParam = materializeTemporaryExpr(
192       anyOf(has(MakeTuple), has(MakeTupleCtor), HasConstructExpr,
193             has(cxxFunctionalCastExpr(HasConstructExpr))));
194 
195   auto HasConstructExprWithValueTypeType =
196       has(ignoringImplicit(cxxConstructExpr(
197           SoughtConstructExpr, hasType(type(hasUnqualifiedDesugaredType(
198                                    type(equalsBoundNode("value_type"))))))));
199 
200   auto HasConstructExprWithValueTypeTypeAsLastArgument =
201       hasLastArgument(materializeTemporaryExpr(anyOf(
202           HasConstructExprWithValueTypeType,
203           has(cxxFunctionalCastExpr(HasConstructExprWithValueTypeType)))));
204 
205   Finder->addMatcher(
206       traverse(TK_AsIs, cxxMemberCallExpr(CallPushBack, has(SoughtParam),
207                                           unless(isInTemplateInstantiation()))
208                             .bind("push_back_call")),
209       this);
210 
211   Finder->addMatcher(
212       traverse(TK_AsIs,
213                cxxMemberCallExpr(
214                    CallEmplacy, HasConstructExprWithValueTypeTypeAsLastArgument,
215                    hasSameNumArgsAsDeclNumParams(),
216                    unless(isInTemplateInstantiation()))
217                    .bind("emplacy_call")),
218       this);
219 
220   Finder->addMatcher(
221       traverse(
222           TK_AsIs,
223           cxxMemberCallExpr(
224               CallEmplacy,
225               on(hasType(cxxRecordDecl(has(typedefNameDecl(
226                   hasName("value_type"),
227                   hasType(type(
228                       hasUnqualifiedDesugaredType(recordType(hasDeclaration(
229                           cxxRecordDecl(hasAnyName(SmallVector<StringRef, 2>(
230                               TupleTypes.begin(), TupleTypes.end()))))))))))))),
231               has(MakeTuple), hasSameNumArgsAsDeclNumParams(),
232               unless(isInTemplateInstantiation()))
233               .bind("emplacy_call")),
234       this);
235 }
236 
check(const MatchFinder::MatchResult & Result)237 void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
238   const auto *PushBackCall =
239       Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_back_call");
240   const auto *EmplacyCall =
241       Result.Nodes.getNodeAs<CXXMemberCallExpr>("emplacy_call");
242   const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");
243   const auto *MakeCall = Result.Nodes.getNodeAs<CallExpr>("make");
244 
245   assert((PushBackCall || EmplacyCall) && "No call matched");
246   assert((CtorCall || MakeCall) && "No push_back parameter matched");
247 
248   const CXXMemberCallExpr *Call = PushBackCall ? PushBackCall : EmplacyCall;
249 
250   if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 &&
251       CtorCall->getArg(0)->getSourceRange() == CtorCall->getSourceRange())
252     return;
253 
254   const auto FunctionNameSourceRange = CharSourceRange::getCharRange(
255       Call->getExprLoc(), Call->getArg(0)->getExprLoc());
256 
257   auto Diag =
258       PushBackCall
259           ? diag(Call->getExprLoc(), "use emplace_back instead of push_back")
260           : diag(CtorCall ? CtorCall->getBeginLoc() : MakeCall->getBeginLoc(),
261                  "unnecessary temporary object created while calling " +
262                      Call->getMethodDecl()->getName().str());
263 
264   if (FunctionNameSourceRange.getBegin().isMacroID())
265     return;
266 
267   if (PushBackCall) {
268     const char *EmplacePrefix = MakeCall ? "emplace_back" : "emplace_back(";
269     Diag << FixItHint::CreateReplacement(FunctionNameSourceRange,
270                                          EmplacePrefix);
271   }
272 
273   const SourceRange CallParensRange =
274       MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(),
275                              MakeCall->getRParenLoc())
276                : CtorCall->getParenOrBraceRange();
277 
278   // Finish if there is no explicit constructor call.
279   if (CallParensRange.getBegin().isInvalid())
280     return;
281 
282   const SourceLocation ExprBegin =
283       CtorCall ? CtorCall->getExprLoc() : MakeCall->getExprLoc();
284 
285   // Range for constructor name and opening brace.
286   const auto ParamCallSourceRange =
287       CharSourceRange::getTokenRange(ExprBegin, CallParensRange.getBegin());
288 
289   Diag << FixItHint::CreateRemoval(ParamCallSourceRange)
290        << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
291               CallParensRange.getEnd(), CallParensRange.getEnd()));
292 
293   if (MakeCall && EmplacyCall) {
294     // Remove extra left parenthesis
295     Diag << FixItHint::CreateRemoval(
296         CharSourceRange::getCharRange(MakeCall->getCallee()->getEndLoc(),
297                                       MakeCall->getArg(0)->getBeginLoc()));
298   }
299 }
300 
storeOptions(ClangTidyOptions::OptionMap & Opts)301 void UseEmplaceCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
302   Options.store(Opts, "IgnoreImplicitConstructors", IgnoreImplicitConstructors);
303   Options.store(Opts, "ContainersWithPushBack",
304                 utils::options::serializeStringList(ContainersWithPushBack));
305   Options.store(Opts, "SmartPointers",
306                 utils::options::serializeStringList(SmartPointers));
307   Options.store(Opts, "TupleTypes",
308                 utils::options::serializeStringList(TupleTypes));
309   Options.store(Opts, "TupleMakeFunctions",
310                 utils::options::serializeStringList(TupleMakeFunctions));
311   Options.store(Opts, "EmplacyFunctions",
312                 utils::options::serializeStringList(EmplacyFunctions));
313 }
314 
315 } // namespace modernize
316 } // namespace tidy
317 } // namespace clang
318