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