Skip to content

Commit 987f9cb

Browse files
nicovankivanmurashko
authored andcommitted
[clang-tidy] Add proper emplace checks to modernize-use-emplace
modernize-use-emplace only recommends going from a push_back to an emplace_back, but does not provide a recommendation when emplace_back is improperly used. This adds the functionality of warning the user when an unecessary temporary is created while calling emplace_back or other "emplacy" functions from the STL containers. Reviewed By: kuhar, ivanmurashko Differential Revision: https://reviews.llvm.org/D101471
1 parent 0063344 commit 987f9cb

File tree

4 files changed

+654
-40
lines changed

4 files changed

+654
-40
lines changed

clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp

Lines changed: 159 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,69 @@ namespace tidy {
1515
namespace modernize {
1616

1717
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+
1881
AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) {
1982
return Node.hasExplicitTemplateArgs();
2083
}
@@ -25,6 +88,20 @@ const auto DefaultSmartPointers =
2588
"::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr";
2689
const auto DefaultTupleTypes = "::std::pair; ::std::tuple";
2790
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";
28105
} // namespace
29106

30107
UseEmplaceCheck::UseEmplaceCheck(StringRef Name, ClangTidyContext *Context)
@@ -37,7 +114,9 @@ UseEmplaceCheck::UseEmplaceCheck(StringRef Name, ClangTidyContext *Context)
37114
TupleTypes(utils::options::parseStringList(
38115
Options.get("TupleTypes", DefaultTupleTypes))),
39116
TupleMakeFunctions(utils::options::parseStringList(
40-
Options.get("TupleMakeFunctions", DefaultTupleMakeFunctions))) {}
117+
Options.get("TupleMakeFunctions", DefaultTupleMakeFunctions))),
118+
EmplacyFunctions(utils::options::parseStringList(
119+
Options.get("EmplacyFunctions", DefaultEmplacyFunctions))) {}
41120

42121
void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
43122
// FIXME: Bunch of functionality that could be easily added:
@@ -52,6 +131,13 @@ void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
52131
hasDeclaration(functionDecl(hasName("push_back"))),
53132
on(hasType(cxxRecordDecl(hasAnyName(ContainersWithPushBack)))));
54133

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+
55141
// We can't replace push_backs of smart pointer because
56142
// if emplacement fails (f.e. bad_alloc in vector) we will have leak of
57143
// passed pointer because smart pointer won't be constructed
@@ -73,8 +159,9 @@ void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
73159
auto ConstructingDerived =
74160
hasParent(implicitCastExpr(hasCastKind(CastKind::CK_DerivedToBase)));
75161

76-
// emplace_back can't access private constructor.
77-
auto IsPrivateCtor = hasDeclaration(cxxConstructorDecl(isPrivate()));
162+
// emplace_back can't access private or protected constructors.
163+
auto IsPrivateOrProtectedCtor =
164+
hasDeclaration(cxxConstructorDecl(anyOf(isPrivate(), isProtected())));
78165

79166
auto HasInitList = anyOf(has(ignoringImplicit(initListExpr())),
80167
has(cxxStdInitializerListExpr()));
@@ -85,7 +172,7 @@ void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
85172
cxxConstructExpr(
86173
unless(anyOf(IsCtorOfSmartPtr, HasInitList, BitFieldAsArgument,
87174
InitializerListAsArgument, NewExprAsArgument,
88-
ConstructingDerived, IsPrivateCtor)))
175+
ConstructingDerived, IsPrivateOrProtectedCtor)))
89176
.bind("ctor");
90177
auto HasConstructExpr = has(ignoringImplicit(SoughtConstructExpr));
91178

@@ -102,36 +189,86 @@ void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
102189
hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(TupleTypes))))));
103190

104191
auto SoughtParam = materializeTemporaryExpr(
105-
anyOf(has(MakeTuple), has(MakeTupleCtor),
106-
HasConstructExpr, has(cxxFunctionalCastExpr(HasConstructExpr))));
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)))));
107204

108205
Finder->addMatcher(
109206
traverse(TK_AsIs, cxxMemberCallExpr(CallPushBack, has(SoughtParam),
110207
unless(isInTemplateInstantiation()))
111-
.bind("call")),
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")),
112234
this);
113235
}
114236

115237
void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
116-
const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call");
238+
const auto *PushBackCall =
239+
Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_back_call");
240+
const auto *EmplacyCall =
241+
Result.Nodes.getNodeAs<CXXMemberCallExpr>("emplacy_call");
117242
const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");
118243
const auto *MakeCall = Result.Nodes.getNodeAs<CallExpr>("make");
244+
245+
assert((PushBackCall || EmplacyCall) && "No call matched");
119246
assert((CtorCall || MakeCall) && "No push_back parameter matched");
120247

248+
const CXXMemberCallExpr *Call = PushBackCall ? PushBackCall : EmplacyCall;
249+
121250
if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 &&
122251
CtorCall->getArg(0)->getSourceRange() == CtorCall->getSourceRange())
123252
return;
124253

125254
const auto FunctionNameSourceRange = CharSourceRange::getCharRange(
126255
Call->getExprLoc(), Call->getArg(0)->getExprLoc());
127256

128-
auto Diag = diag(Call->getExprLoc(), "use emplace_back instead of push_back");
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());
129263

130264
if (FunctionNameSourceRange.getBegin().isMacroID())
131265
return;
132266

133-
const auto *EmplacePrefix = MakeCall ? "emplace_back" : "emplace_back(";
134-
Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, EmplacePrefix);
267+
if (PushBackCall) {
268+
const char *EmplacePrefix = MakeCall ? "emplace_back" : "emplace_back(";
269+
Diag << FixItHint::CreateReplacement(FunctionNameSourceRange,
270+
EmplacePrefix);
271+
}
135272

136273
const SourceRange CallParensRange =
137274
MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(),
@@ -143,15 +280,22 @@ void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
143280
return;
144281

145282
const SourceLocation ExprBegin =
146-
MakeCall ? MakeCall->getExprLoc() : CtorCall->getExprLoc();
283+
CtorCall ? CtorCall->getExprLoc() : MakeCall->getExprLoc();
147284

148285
// Range for constructor name and opening brace.
149286
const auto ParamCallSourceRange =
150287
CharSourceRange::getTokenRange(ExprBegin, CallParensRange.getBegin());
151288

152289
Diag << FixItHint::CreateRemoval(ParamCallSourceRange)
153290
<< FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
154-
CallParensRange.getEnd(), CallParensRange.getEnd()));
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+
}
155299
}
156300

157301
void UseEmplaceCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
@@ -164,6 +308,8 @@ void UseEmplaceCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
164308
utils::options::serializeStringList(TupleTypes));
165309
Options.store(Opts, "TupleMakeFunctions",
166310
utils::options::serializeStringList(TupleMakeFunctions));
311+
Options.store(Opts, "EmplacyFunctions",
312+
utils::options::serializeStringList(EmplacyFunctions));
167313
}
168314

169315
} // namespace modernize

clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class UseEmplaceCheck : public ClangTidyCheck {
4040
const std::vector<StringRef> SmartPointers;
4141
const std::vector<StringRef> TupleTypes;
4242
const std::vector<StringRef> TupleMakeFunctions;
43+
const std::vector<StringRef> EmplacyFunctions;
4344
};
4445

4546
} // namespace modernize

clang-tools-extra/docs/clang-tidy/checks/modernize-use-emplace.rst

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,42 @@ because replacing ``insert`` with ``emplace`` may result in
1515
By default only ``std::vector``, ``std::deque``, ``std::list`` are considered.
1616
This list can be modified using the :option:`ContainersWithPushBack` option.
1717

18+
This check also reports when an ``emplace``-like method is improperly used,
19+
for example using ``emplace_back`` while also calling a constructor. This
20+
creates a temporary that requires at best a move and at worst a copy. Almost all
21+
``emplace``-like functions in the STL are covered by this, with ``try_emplace``
22+
on ``std::map`` and ``std::unordered_map`` being the exception as it behaves
23+
slightly differently than all the others. More containers can be added with the
24+
:option:`EmplacyFunctions` option, so long as the container defines a
25+
``value_type`` type, and the ``emplace``-like functions construct a
26+
``value_type`` object.
27+
1828
Before:
1929

2030
.. code-block:: c++
2131

2232
std::vector<MyClass> v;
2333
v.push_back(MyClass(21, 37));
34+
v.emplace_back(MyClass(21, 37));
2435

2536
std::vector<std::pair<int, int>> w;
2637

2738
w.push_back(std::pair<int, int>(21, 37));
2839
w.push_back(std::make_pair(21L, 37L));
40+
w.emplace_back(std::make_pair(21L, 37L));
2941

3042
After:
3143

3244
.. code-block:: c++
3345

3446
std::vector<MyClass> v;
3547
v.emplace_back(21, 37);
48+
v.emplace_back(21, 37);
3649

3750
std::vector<std::pair<int, int>> w;
3851
w.emplace_back(21, 37);
3952
w.emplace_back(21L, 37L);
53+
w.emplace_back(21L, 37L);
4054

4155
By default, the check is able to remove unnecessary ``std::make_pair`` and
4256
``std::make_tuple`` calls from ``push_back`` calls on containers of
@@ -128,20 +142,30 @@ Options
128142
function calls will be removed from ``push_back`` calls and turned into
129143
``emplace_back``.
130144

145+
.. option:: EmplacyFunctions
146+
147+
Semicolon-separated list of containers without their template parameters
148+
and some ``emplace``-like method of the container. Example:
149+
``vector::emplace_back``. Those methods will be checked for improper use and
150+
the check will report when a temporary is unnecessarily created.
151+
131152
Example
132153
^^^^^^^
133154

134155
.. code-block:: c++
135156

136157
std::vector<MyTuple<int, bool, char>> x;
137158
x.push_back(MakeMyTuple(1, false, 'x'));
159+
x.emplace_back(MakeMyTuple(1, false, 'x'));
138160

139161
transforms to:
140162

141163
.. code-block:: c++
142164

143165
std::vector<MyTuple<int, bool, char>> x;
144166
x.emplace_back(1, false, 'x');
167+
x.emplace_back(1, false, 'x');
145168

146-
when :option:`TupleTypes` is set to ``MyTuple`` and :option:`TupleMakeFunctions`
147-
is set to ``MakeMyTuple``.
169+
when :option:`TupleTypes` is set to ``MyTuple``, :option:`TupleMakeFunctions`
170+
is set to ``MakeMyTuple``, and :option:`EmplacyFunctions` is set to
171+
``vector::emplace_back``.

0 commit comments

Comments
 (0)