clang-tools  15.0.0git
UseEmplaceCheck.cpp
Go to the documentation of this file.
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.
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.
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).
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 
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 
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 
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 
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 
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",
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
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
clang::tidy::modernize::UseEmplaceCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: UseEmplaceCheck.cpp:121
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:53
clang::tidy::modernize::UseEmplaceCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: UseEmplaceCheck.cpp:301
UseEmplaceCheck.h
clang::clangd::remote::Character
char Character
Definition: Header.h:5
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:415
Builder
CodeCompletionBuilder Builder
Definition: CodeCompletionStringsTests.cpp:36
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:67
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidyCheck.cpp:25
Name
Token Name
Definition: MacroToEnumCheck.cpp:89
clang::ast_matchers::AST_MATCHER
AST_MATCHER(Expr, isMacroID)
Definition: PreferIsaOrDynCastInConditionalsCheck.cpp:19
clang::tidy::modernize::UseEmplaceCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: UseEmplaceCheck.cpp:237
clang::tidy::utils::options::serializeStringList
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
Definition: OptionsUtils.cpp:62
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::utils::options::parseStringList
std::vector< StringRef > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
Definition: OptionsUtils.cpp:19
clang::tidy::ClangTidyCheck::OptionsView::store
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Definition: ClangTidyCheck.cpp:129
clang::tidy::bugprone::AST_MATCHER_P
AST_MATCHER_P(FunctionDecl, parameterCountGE, unsigned, N)
Matches functions that have at least the specified amount of parameters.
Definition: EasilySwappableParametersCheck.cpp:1888