clang-tools 24.0.0git
UseEmplaceCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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"
11using namespace clang::ast_matchers;
12
13namespace clang::tidy::modernize {
14
15namespace {
16AST_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.
24AST_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 // 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 return llvm::any_of(Names, [&](const StringRef Pattern) {
46 if (Pattern.starts_with("::"))
47 return FullNameTrimmed == Pattern;
48 return FullNameTrimmedRef.ends_with(Pattern) &&
49 FullNameTrimmedRef.drop_back(Pattern.size()).ends_with("::");
50 });
51}
52
53// Checks if the given matcher is the last argument of the given CallExpr.
54AST_MATCHER_P(CallExpr, hasLastArgument, ast_matchers::internal::Matcher<Expr>,
55 InnerMatcher) {
56 if (Node.getNumArgs() == 0)
57 return false;
58
59 return InnerMatcher.matches(*Node.getArg(Node.getNumArgs() - 1), Finder,
60 Builder);
61}
62
63// Checks if the given member call has the same number of arguments as the
64// function had parameters defined (this is useful to check if there is only one
65// variadic argument).
66AST_MATCHER(CXXMemberCallExpr, hasSameNumArgsAsDeclNumParams) {
67 if (const FunctionTemplateDecl *Primary =
68 Node.getMethodDecl()->getPrimaryTemplate())
69 return Node.getNumArgs() == Primary->getTemplatedDecl()->getNumParams();
70
71 return Node.getNumArgs() == Node.getMethodDecl()->getNumParams();
72}
73
74AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) {
75 return Node.hasExplicitTemplateArgs();
76}
77} // namespace
78
79// Helper Matcher which applies the given QualType Matcher either directly or by
80// resolving a pointer type to its pointee. Used to match v.push_back() as well
81// as p->push_back().
83 const ast_matchers::internal::Matcher<QualType> &TypeMatcher) {
84 return anyOf(hasType(TypeMatcher),
85 hasType(pointerType(pointee(TypeMatcher))));
86}
87
88// Matches if the node has canonical type matching any of the given names.
89static auto hasWantedType(llvm::ArrayRef<StringRef> TypeNames) {
90 return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasAnyName(TypeNames))));
91}
92
93// Matches member call expressions of the named method on the listed container
94// types.
95static auto
96cxxMemberCallExprOnContainer(StringRef MethodName,
97 llvm::ArrayRef<StringRef> ContainerNames) {
98 return cxxMemberCallExpr(
99 hasDeclaration(functionDecl(hasName(MethodName))),
100 on(hasTypeOrPointeeType(hasWantedType(ContainerNames))));
101}
102
103static constexpr char DefaultContainersWithPushBack[] =
104 "::std::vector; ::std::list; ::std::deque";
105static constexpr char DefaultContainersWithPush[] =
106 "::std::stack; ::std::queue; ::std::priority_queue";
107static constexpr char DefaultContainersWithPushFront[] =
108 "::std::forward_list; ::std::list; ::std::deque";
109static constexpr char DefaultSmartPointers[] =
110 "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr";
111static constexpr char DefaultTupleTypes[] = "::std::pair; ::std::tuple";
112static constexpr char DefaultTupleMakeFunctions[] =
113 "::std::make_pair; ::std::make_tuple";
114static constexpr char 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";
128
130 : ClangTidyCheck(Name, Context), IgnoreImplicitConstructors(Options.get(
131 "IgnoreImplicitConstructors", false)),
132 ContainersWithPushBack(utils::options::parseStringList(Options.get(
133 "ContainersWithPushBack", DefaultContainersWithPushBack))),
134 ContainersWithPush(utils::options::parseStringList(
135 Options.get("ContainersWithPush", DefaultContainersWithPush))),
136 ContainersWithPushFront(utils::options::parseStringList(Options.get(
137 "ContainersWithPushFront", DefaultContainersWithPushFront))),
138 SmartPointers(utils::options::parseStringList(
139 Options.get("SmartPointers", DefaultSmartPointers))),
140 TupleTypes(utils::options::parseStringList(
141 Options.get("TupleTypes", DefaultTupleTypes))),
142 TupleMakeFunctions(utils::options::parseStringList(
143 Options.get("TupleMakeFunctions", DefaultTupleMakeFunctions))),
144 EmplacyFunctions(utils::options::parseStringList(
145 Options.get("EmplacyFunctions", DefaultEmplacyFunctions))) {}
146
147void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
148 // FIXME: Bunch of functionality that could be easily added:
149 // + add handling of `insert` for stl associative container, but be careful
150 // because this requires special treatment (it could cause performance
151 // regression)
152 // + match for emplace calls that should be replaced with insertion
153 const auto CallPushBack =
154 cxxMemberCallExprOnContainer("push_back", ContainersWithPushBack);
155 const auto CallPush =
156 cxxMemberCallExprOnContainer("push", ContainersWithPush);
157 const auto CallPushFront =
158 cxxMemberCallExprOnContainer("push_front", ContainersWithPushFront);
159
160 const auto CallEmplacy = cxxMemberCallExpr(
161 hasDeclaration(
162 functionDecl(hasAnyNameIgnoringTemplates(EmplacyFunctions))),
164 hasCanonicalType(hasDeclaration(has(typedefNameDecl(
165 hasName("value_type"),
166 hasType(hasCanonicalType(recordType().bind("value_type"))))))))));
167
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))));
174
175 // Bitfields binds only to consts and emplace_back take it by universal ref.
176 auto BitFieldAsArgument = hasAnyArgument(
177 ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField())))));
178
179 // Initializer list can't be passed to universal reference.
180 auto InitializerListAsArgument = hasAnyArgument(
181 ignoringImplicit(allOf(cxxConstructExpr(isListInitialization()),
182 unless(cxxTemporaryObjectExpr()))));
183
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)));
189
190 // emplace_back can't access private or protected constructors.
191 auto IsPrivateOrProtectedCtor =
192 hasDeclaration(cxxConstructorDecl(anyOf(isPrivate(), isProtected())));
193
194 auto HasInitList = anyOf(has(ignoringImplicit(initListExpr())),
195 has(cxxStdInitializerListExpr()));
196
197 // FIXME: Discard 0/NULL (as nullptr), static inline const data members,
198 // overloaded functions and template names.
199 const auto SoughtConstructExpr =
200 cxxConstructExpr(
201 unless(anyOf(IsCtorOfSmartPtr, HasInitList, BitFieldAsArgument,
202 InitializerListAsArgument, NewExprAsArgument,
203 ConstructingDerived, IsPrivateOrProtectedCtor)))
204 .bind("ctor");
205 auto HasConstructExpr = has(ignoringImplicit(SoughtConstructExpr));
206
207 // allow for T{} to be replaced, even if no CTOR is declared
208 auto HasConstructInitListExpr = has(initListExpr(
209 initCountLeq(1), anyOf(allOf(has(SoughtConstructExpr),
210 has(cxxConstructExpr(argumentCountIs(0)))),
211 has(cxxBindTemporaryExpr(
212 has(SoughtConstructExpr),
213 has(cxxConstructExpr(argumentCountIs(0))))))));
214 auto HasBracedInitListExpr =
215 anyOf(has(cxxBindTemporaryExpr(HasConstructInitListExpr)),
216 HasConstructInitListExpr);
217
218 const auto MakeTuple = ignoringImplicit(
219 callExpr(callee(expr(ignoringImplicit(declRefExpr(
220 unless(hasExplicitTemplateArgs()),
221 to(functionDecl(hasAnyName(TupleMakeFunctions))))))))
222 .bind("make"));
223
224 // make_something can return type convertible to container's element type.
225 // Allow the conversion only on containers of pairs.
226 const auto MakeTupleCtor = ignoringImplicit(cxxConstructExpr(
227 has(materializeTemporaryExpr(MakeTuple)),
228 hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(TupleTypes))))));
229
230 const auto SoughtParam =
231 materializeTemporaryExpr(
232 anyOf(has(MakeTuple), has(MakeTupleCtor), HasConstructExpr,
233 HasBracedInitListExpr,
234 has(cxxFunctionalCastExpr(HasConstructExpr)),
235 has(cxxFunctionalCastExpr(HasBracedInitListExpr))))
236 .bind("temporary_expr");
237
238 auto HasConstructExprWithValueTypeType =
239 has(ignoringImplicit(cxxConstructExpr(
240 SoughtConstructExpr,
241 hasType(hasCanonicalType(type(equalsBoundNode("value_type")))))));
242
243 auto HasBracedInitListWithValueTypeType = anyOf(
244 allOf(HasConstructInitListExpr,
245 has(initListExpr(hasType(
246 hasCanonicalType(type(equalsBoundNode("value_type"))))))),
247 has(cxxBindTemporaryExpr(HasConstructInitListExpr,
248 has(initListExpr(hasType(hasCanonicalType(
249 type(equalsBoundNode("value_type")))))))));
250
251 const auto HasConstructExprWithValueTypeTypeAsLastArgument = hasLastArgument(
252 materializeTemporaryExpr(
253 anyOf(HasConstructExprWithValueTypeType,
254 HasBracedInitListWithValueTypeType,
255 has(cxxFunctionalCastExpr(HasConstructExprWithValueTypeType)),
256 has(cxxFunctionalCastExpr(HasBracedInitListWithValueTypeType))))
257 .bind("temporary_expr"));
258
259 Finder->addMatcher(
260 traverse(TK_AsIs, cxxMemberCallExpr(CallPushBack, has(SoughtParam),
261 unless(isInTemplateInstantiation()))
262 .bind("push_back_call")),
263 this);
264
265 Finder->addMatcher(
266 traverse(TK_AsIs, cxxMemberCallExpr(CallPush, has(SoughtParam),
267 unless(isInTemplateInstantiation()))
268 .bind("push_call")),
269 this);
270
271 Finder->addMatcher(
272 traverse(TK_AsIs, cxxMemberCallExpr(CallPushFront, has(SoughtParam),
273 unless(isInTemplateInstantiation()))
274 .bind("push_front_call")),
275 this);
276
277 Finder->addMatcher(
278 traverse(TK_AsIs,
279 cxxMemberCallExpr(
280 CallEmplacy, HasConstructExprWithValueTypeTypeAsLastArgument,
281 hasSameNumArgsAsDeclNumParams(),
282 unless(isInTemplateInstantiation()))
283 .bind("emplacy_call")),
284 this);
285
286 Finder->addMatcher(
287 traverse(TK_AsIs,
288 cxxMemberCallExpr(
289 CallEmplacy,
290 on(hasType(cxxRecordDecl(has(typedefNameDecl(
291 hasName("value_type"),
292 hasType(hasCanonicalType(recordType(hasDeclaration(
293 cxxRecordDecl(hasAnyName(SmallVector<StringRef, 2>(
294 TupleTypes.begin(), TupleTypes.end())))))))))))),
295 has(MakeTuple), hasSameNumArgsAsDeclNumParams(),
296 unless(isInTemplateInstantiation()))
297 .bind("emplacy_call")),
298 this);
299}
300
301void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
302 const auto *PushBackCall =
303 Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_back_call");
304 const auto *PushCall = Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_call");
305 const auto *PushFrontCall =
306 Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_front_call");
307 const auto *EmplacyCall =
308 Result.Nodes.getNodeAs<CXXMemberCallExpr>("emplacy_call");
309 const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");
310 const auto *MakeCall = Result.Nodes.getNodeAs<CallExpr>("make");
311 const auto *TemporaryExpr =
312 Result.Nodes.getNodeAs<MaterializeTemporaryExpr>("temporary_expr");
313
314 const CXXMemberCallExpr *Call = [&]() {
315 if (PushBackCall)
316 return PushBackCall;
317 if (PushCall)
318 return PushCall;
319 if (PushFrontCall)
320 return PushFrontCall;
321 return EmplacyCall;
322 }();
323
324 assert(Call && "No call matched");
325 assert((CtorCall || MakeCall) && "No push_back parameter matched");
326
327 if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 &&
328 CtorCall->getArg(0)->getSourceRange() == CtorCall->getSourceRange())
329 return;
330
331 const auto FunctionNameSourceRange = CharSourceRange::getCharRange(
332 Call->getExprLoc(), Call->getArg(0)->getExprLoc());
333
334 const auto Diag =
335 EmplacyCall
336 ? diag(TemporaryExpr ? TemporaryExpr->getBeginLoc()
337 : CtorCall ? CtorCall->getBeginLoc()
338 : MakeCall->getBeginLoc(),
339 "unnecessary temporary object created while calling %0")
340 : diag(Call->getExprLoc(), "use emplace%select{|_back|_front}0 "
341 "instead of push%select{|_back|_front}0");
342 if (EmplacyCall)
343 Diag << Call->getMethodDecl()->getName();
344 else if (PushCall)
345 Diag << 0;
346 else if (PushBackCall)
347 Diag << 1;
348 else
349 Diag << 2;
350
351 if (FunctionNameSourceRange.getBegin().isMacroID())
352 return;
353
354 if (PushBackCall) {
355 const char *EmplacePrefix = MakeCall ? "emplace_back" : "emplace_back(";
356 Diag << FixItHint::CreateReplacement(FunctionNameSourceRange,
357 EmplacePrefix);
358 } else if (PushCall) {
359 const char *EmplacePrefix = MakeCall ? "emplace" : "emplace(";
360 Diag << FixItHint::CreateReplacement(FunctionNameSourceRange,
361 EmplacePrefix);
362 } else if (PushFrontCall) {
363 const char *EmplacePrefix = MakeCall ? "emplace_front" : "emplace_front(";
364 Diag << FixItHint::CreateReplacement(FunctionNameSourceRange,
365 EmplacePrefix);
366 }
367
368 const SourceRange CallParensRange =
369 MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(),
370 MakeCall->getRParenLoc())
371 : CtorCall->getParenOrBraceRange();
372
373 // Finish if there is no explicit constructor call.
374 if (CallParensRange.getBegin().isInvalid())
375 return;
376
377 // FIXME: Will there ever be a CtorCall, if there is no TemporaryExpr?
378 const SourceLocation ExprBegin = TemporaryExpr ? TemporaryExpr->getExprLoc()
379 : CtorCall ? CtorCall->getExprLoc()
380 : MakeCall->getExprLoc();
381
382 // Range for constructor name and opening brace.
383 const auto ParamCallSourceRange =
384 CharSourceRange::getTokenRange(ExprBegin, CallParensRange.getBegin());
385
386 // Range for constructor closing brace and end of temporary expr.
387 const auto EndCallSourceRange = CharSourceRange::getTokenRange(
388 CallParensRange.getEnd(),
389 TemporaryExpr ? TemporaryExpr->getEndLoc() : CallParensRange.getEnd());
390
391 Diag << FixItHint::CreateRemoval(ParamCallSourceRange)
392 << FixItHint::CreateRemoval(EndCallSourceRange);
393
394 if (MakeCall && EmplacyCall) {
395 // Remove extra left parenthesis
396 Diag << FixItHint::CreateRemoval(
397 CharSourceRange::getCharRange(MakeCall->getCallee()->getEndLoc(),
398 MakeCall->getArg(0)->getBeginLoc()));
399 }
400}
401
403 Options.store(Opts, "IgnoreImplicitConstructors", IgnoreImplicitConstructors);
404 Options.store(Opts, "ContainersWithPushBack",
405 utils::options::serializeStringList(ContainersWithPushBack));
406 Options.store(Opts, "ContainersWithPush",
407 utils::options::serializeStringList(ContainersWithPush));
408 Options.store(Opts, "ContainersWithPushFront",
409 utils::options::serializeStringList(ContainersWithPushFront));
410 Options.store(Opts, "SmartPointers",
412 Options.store(Opts, "TupleTypes",
414 Options.store(Opts, "TupleMakeFunctions",
415 utils::options::serializeStringList(TupleMakeFunctions));
416 Options.store(Opts, "EmplacyFunctions",
417 utils::options::serializeStringList(EmplacyFunctions));
418}
419
420} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
UseEmplaceCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
AST_MATCHER(BinaryOperator, isRelationalOperator)
static constexpr char DefaultContainersWithPushFront[]
static auto cxxMemberCallExprOnContainer(StringRef MethodName, llvm::ArrayRef< StringRef > ContainerNames)
static constexpr char DefaultEmplacyFunctions[]
static constexpr char DefaultContainersWithPushBack[]
static constexpr char DefaultSmartPointers[]
static constexpr char DefaultContainersWithPush[]
static constexpr char DefaultTupleTypes[]
static auto hasWantedType(llvm::ArrayRef< StringRef > TypeNames)
static constexpr char DefaultTupleMakeFunctions[]
static auto hasTypeOrPointeeType(const ast_matchers::internal::Matcher< QualType > &TypeMatcher)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap