• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
AST_MATCHER(DeclRefExpr,hasExplicitTemplateArgs)18 AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) {
19   return Node.hasExplicitTemplateArgs();
20 }
21 
22 const auto DefaultContainersWithPushBack =
23     "::std::vector; ::std::list; ::std::deque";
24 const auto DefaultSmartPointers =
25     "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr";
26 const auto DefaultTupleTypes = "::std::pair; ::std::tuple";
27 const auto DefaultTupleMakeFunctions = "::std::make_pair; ::std::make_tuple";
28 } // namespace
29 
UseEmplaceCheck(StringRef Name,ClangTidyContext * Context)30 UseEmplaceCheck::UseEmplaceCheck(StringRef Name, ClangTidyContext *Context)
31     : ClangTidyCheck(Name, Context), IgnoreImplicitConstructors(Options.get(
32                                          "IgnoreImplicitConstructors", false)),
33       ContainersWithPushBack(utils::options::parseStringList(Options.get(
34           "ContainersWithPushBack", DefaultContainersWithPushBack))),
35       SmartPointers(utils::options::parseStringList(
36           Options.get("SmartPointers", DefaultSmartPointers))),
37       TupleTypes(utils::options::parseStringList(
38           Options.get("TupleTypes", DefaultTupleTypes))),
39       TupleMakeFunctions(utils::options::parseStringList(
40           Options.get("TupleMakeFunctions", DefaultTupleMakeFunctions))) {}
41 
registerMatchers(MatchFinder * Finder)42 void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
43   // FIXME: Bunch of functionality that could be easily added:
44   // + add handling of `push_front` for std::forward_list, std::list
45   // and std::deque.
46   // + add handling of `push` for std::stack, std::queue, std::priority_queue
47   // + add handling of `insert` for stl associative container, but be careful
48   // because this requires special treatment (it could cause performance
49   // regression)
50   // + match for emplace calls that should be replaced with insertion
51   auto CallPushBack = cxxMemberCallExpr(
52       hasDeclaration(functionDecl(hasName("push_back"))),
53       on(hasType(cxxRecordDecl(hasAnyName(SmallVector<StringRef, 5>(
54           ContainersWithPushBack.begin(), ContainersWithPushBack.end()))))));
55 
56   // We can't replace push_backs of smart pointer because
57   // if emplacement fails (f.e. bad_alloc in vector) we will have leak of
58   // passed pointer because smart pointer won't be constructed
59   // (and destructed) as in push_back case.
60   auto IsCtorOfSmartPtr = hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(
61       SmallVector<StringRef, 5>(SmartPointers.begin(), SmartPointers.end())))));
62 
63   // Bitfields binds only to consts and emplace_back take it by universal ref.
64   auto BitFieldAsArgument = hasAnyArgument(
65       ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField())))));
66 
67   // Initializer list can't be passed to universal reference.
68   auto InitializerListAsArgument = hasAnyArgument(
69       ignoringImplicit(cxxConstructExpr(isListInitialization())));
70 
71   // We could have leak of resource.
72   auto NewExprAsArgument = hasAnyArgument(ignoringImplicit(cxxNewExpr()));
73   // We would call another constructor.
74   auto ConstructingDerived =
75       hasParent(implicitCastExpr(hasCastKind(CastKind::CK_DerivedToBase)));
76 
77   // emplace_back can't access private constructor.
78   auto IsPrivateCtor = hasDeclaration(cxxConstructorDecl(isPrivate()));
79 
80   auto HasInitList = anyOf(has(ignoringImplicit(initListExpr())),
81                            has(cxxStdInitializerListExpr()));
82 
83   // FIXME: Discard 0/NULL (as nullptr), static inline const data members,
84   // overloaded functions and template names.
85   auto SoughtConstructExpr =
86       cxxConstructExpr(
87           unless(anyOf(IsCtorOfSmartPtr, HasInitList, BitFieldAsArgument,
88                        InitializerListAsArgument, NewExprAsArgument,
89                        ConstructingDerived, IsPrivateCtor)))
90           .bind("ctor");
91   auto HasConstructExpr = has(ignoringImplicit(SoughtConstructExpr));
92 
93   auto MakeTuple = ignoringImplicit(
94       callExpr(
95           callee(expr(ignoringImplicit(declRefExpr(
96               unless(hasExplicitTemplateArgs()),
97               to(functionDecl(hasAnyName(SmallVector<StringRef, 2>(
98                   TupleMakeFunctions.begin(), TupleMakeFunctions.end())))))))))
99           .bind("make"));
100 
101   // make_something can return type convertible to container's element type.
102   // Allow the conversion only on containers of pairs.
103   auto MakeTupleCtor = ignoringImplicit(cxxConstructExpr(
104       has(materializeTemporaryExpr(MakeTuple)),
105       hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(
106           SmallVector<StringRef, 2>(TupleTypes.begin(), TupleTypes.end())))))));
107 
108   auto SoughtParam = materializeTemporaryExpr(
109       anyOf(has(MakeTuple), has(MakeTupleCtor),
110             HasConstructExpr, has(cxxFunctionalCastExpr(HasConstructExpr))));
111 
112   Finder->addMatcher(
113       traverse(ast_type_traits::TK_AsIs,
114                cxxMemberCallExpr(CallPushBack, has(SoughtParam),
115                                  unless(isInTemplateInstantiation()))
116                    .bind("call")),
117       this);
118 }
119 
check(const MatchFinder::MatchResult & Result)120 void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
121   const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call");
122   const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");
123   const auto *MakeCall = Result.Nodes.getNodeAs<CallExpr>("make");
124   assert((CtorCall || MakeCall) && "No push_back parameter matched");
125 
126   if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 &&
127       CtorCall->getArg(0)->getSourceRange() == CtorCall->getSourceRange())
128     return;
129 
130   const auto FunctionNameSourceRange = CharSourceRange::getCharRange(
131       Call->getExprLoc(), Call->getArg(0)->getExprLoc());
132 
133   auto Diag = diag(Call->getExprLoc(), "use emplace_back instead of push_back");
134 
135   if (FunctionNameSourceRange.getBegin().isMacroID())
136     return;
137 
138   const auto *EmplacePrefix = MakeCall ? "emplace_back" : "emplace_back(";
139   Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, EmplacePrefix);
140 
141   const SourceRange CallParensRange =
142       MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(),
143                              MakeCall->getRParenLoc())
144                : CtorCall->getParenOrBraceRange();
145 
146   // Finish if there is no explicit constructor call.
147   if (CallParensRange.getBegin().isInvalid())
148     return;
149 
150   const SourceLocation ExprBegin =
151       MakeCall ? MakeCall->getExprLoc() : CtorCall->getExprLoc();
152 
153   // Range for constructor name and opening brace.
154   const auto ParamCallSourceRange =
155       CharSourceRange::getTokenRange(ExprBegin, CallParensRange.getBegin());
156 
157   Diag << FixItHint::CreateRemoval(ParamCallSourceRange)
158        << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
159            CallParensRange.getEnd(), CallParensRange.getEnd()));
160 }
161 
storeOptions(ClangTidyOptions::OptionMap & Opts)162 void UseEmplaceCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
163   Options.store(Opts, "IgnoreImplicitConstructors", IgnoreImplicitConstructors);
164   Options.store(Opts, "ContainersWithPushBack",
165                 utils::options::serializeStringList(ContainersWithPushBack));
166   Options.store(Opts, "SmartPointers",
167                 utils::options::serializeStringList(SmartPointers));
168   Options.store(Opts, "TupleTypes",
169                 utils::options::serializeStringList(TupleTypes));
170   Options.store(Opts, "TupleMakeFunctions",
171                 utils::options::serializeStringList(TupleMakeFunctions));
172 }
173 
174 } // namespace modernize
175 } // namespace tidy
176 } // namespace clang
177