1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // This implements a Clang tool to convert all instances of std::string("") to
6 // std::string(). The latter is more efficient (as std::string doesn't have to
7 // take a copy of an empty string) and generates fewer instructions as well. It
8 // should be run using the tools/clang/scripts/run_tool.py helper.
11 #include "clang/ASTMatchers/ASTMatchers.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Basic/SourceManager.h"
14 #include "clang/Frontend/FrontendActions.h"
15 #include "clang/Tooling/CommonOptionsParser.h"
16 #include "clang/Tooling/Refactoring.h"
17 #include "clang/Tooling/Tooling.h"
18 #include "llvm/Support/CommandLine.h"
20 using clang::ast_matchers::MatchFinder
;
21 using clang::ast_matchers::argumentCountIs
;
22 using clang::ast_matchers::bindTemporaryExpr
;
23 using clang::ast_matchers::constructorDecl
;
24 using clang::ast_matchers::constructExpr
;
25 using clang::ast_matchers::defaultArgExpr
;
26 using clang::ast_matchers::expr
;
27 using clang::ast_matchers::forEach
;
28 using clang::ast_matchers::has
;
29 using clang::ast_matchers::hasArgument
;
30 using clang::ast_matchers::hasDeclaration
;
31 using clang::ast_matchers::hasName
;
32 using clang::ast_matchers::id
;
33 using clang::ast_matchers::methodDecl
;
34 using clang::ast_matchers::newExpr
;
35 using clang::ast_matchers::ofClass
;
36 using clang::ast_matchers::stringLiteral
;
37 using clang::ast_matchers::varDecl
;
38 using clang::tooling::CommonOptionsParser
;
39 using clang::tooling::Replacement
;
40 using clang::tooling::Replacements
;
44 // Handles replacements for stack and heap-allocated instances, e.g.:
46 // std::string* b = new std::string("");
47 class ConstructorCallback
: public MatchFinder::MatchCallback
{
49 ConstructorCallback(Replacements
* replacements
)
50 : replacements_(replacements
) {}
52 virtual void run(const MatchFinder::MatchResult
& result
) override
;
55 Replacements
* const replacements_
;
58 // Handles replacements for invocations of std::string("") in an initializer
60 class InitializerCallback
: public MatchFinder::MatchCallback
{
62 InitializerCallback(Replacements
* replacements
)
63 : replacements_(replacements
) {}
65 virtual void run(const MatchFinder::MatchResult
& result
) override
;
68 Replacements
* const replacements_
;
71 // Handles replacements for invocations of std::string("") in a temporary
72 // context, e.g. FunctionThatTakesString(std::string("")). Note that this
73 // handles implicits construction of std::string as well.
74 class TemporaryCallback
: public MatchFinder::MatchCallback
{
76 TemporaryCallback(Replacements
* replacements
) : replacements_(replacements
) {}
78 virtual void run(const MatchFinder::MatchResult
& result
) override
;
81 Replacements
* const replacements_
;
84 class EmptyStringConverter
{
86 explicit EmptyStringConverter(Replacements
* replacements
)
87 : constructor_callback_(replacements
),
88 initializer_callback_(replacements
),
89 temporary_callback_(replacements
) {}
91 void SetupMatchers(MatchFinder
* match_finder
);
94 ConstructorCallback constructor_callback_
;
95 InitializerCallback initializer_callback_
;
96 TemporaryCallback temporary_callback_
;
99 void EmptyStringConverter::SetupMatchers(MatchFinder
* match_finder
) {
100 const clang::ast_matchers::StatementMatcher
& constructor_call
=
103 hasDeclaration(methodDecl(ofClass(hasName("std::basic_string")))),
105 hasArgument(0, id("literal", stringLiteral())),
106 hasArgument(1, defaultArgExpr())));
108 // Note that expr(has()) in the matcher is significant; the Clang AST wraps
109 // calls to the std::string constructor with exprWithCleanups nodes. Without
110 // the expr(has()) matcher, the first and last rules would not match anything!
111 match_finder
->addMatcher(varDecl(forEach(expr(has(constructor_call
)))),
112 &constructor_callback_
);
113 match_finder
->addMatcher(newExpr(has(constructor_call
)),
114 &constructor_callback_
);
115 match_finder
->addMatcher(bindTemporaryExpr(has(constructor_call
)),
116 &temporary_callback_
);
117 match_finder
->addMatcher(
118 constructorDecl(forEach(expr(has(constructor_call
)))),
119 &initializer_callback_
);
122 void ConstructorCallback::run(const MatchFinder::MatchResult
& result
) {
123 const clang::StringLiteral
* literal
=
124 result
.Nodes
.getNodeAs
<clang::StringLiteral
>("literal");
125 if (literal
->getLength() > 0)
128 const clang::CXXConstructExpr
* call
=
129 result
.Nodes
.getNodeAs
<clang::CXXConstructExpr
>("call");
130 clang::CharSourceRange range
=
131 clang::CharSourceRange::getTokenRange(call
->getParenOrBraceRange());
132 replacements_
->insert(Replacement(*result
.SourceManager
, range
, ""));
135 void InitializerCallback::run(const MatchFinder::MatchResult
& result
) {
136 const clang::StringLiteral
* literal
=
137 result
.Nodes
.getNodeAs
<clang::StringLiteral
>("literal");
138 if (literal
->getLength() > 0)
141 const clang::CXXConstructExpr
* call
=
142 result
.Nodes
.getNodeAs
<clang::CXXConstructExpr
>("call");
143 replacements_
->insert(Replacement(*result
.SourceManager
, call
, ""));
146 void TemporaryCallback::run(const MatchFinder::MatchResult
& result
) {
147 const clang::StringLiteral
* literal
=
148 result
.Nodes
.getNodeAs
<clang::StringLiteral
>("literal");
149 if (literal
->getLength() > 0)
152 const clang::CXXConstructExpr
* call
=
153 result
.Nodes
.getNodeAs
<clang::CXXConstructExpr
>("call");
154 // Differentiate between explicit and implicit calls to std::string's
155 // constructor. An implicitly generated constructor won't have a valid
156 // source range for the parenthesis. We do this because the matched expression
157 // for |call| in the explicit case doesn't include the closing parenthesis.
158 clang::SourceRange range
= call
->getParenOrBraceRange();
159 if (range
.isValid()) {
160 replacements_
->insert(Replacement(*result
.SourceManager
, literal
, ""));
162 replacements_
->insert(
163 Replacement(*result
.SourceManager
,
165 literal
->isWide() ? "std::wstring()" : "std::string()"));
171 static llvm::cl::extrahelp
common_help(CommonOptionsParser::HelpMessage
);
173 int main(int argc
, const char* argv
[]) {
174 llvm::cl::OptionCategory
category("EmptyString Tool");
175 CommonOptionsParser
options(argc
, argv
, category
);
176 clang::tooling::ClangTool
tool(options
.getCompilations(),
177 options
.getSourcePathList());
179 Replacements replacements
;
180 EmptyStringConverter
converter(&replacements
);
181 MatchFinder match_finder
;
182 converter
.SetupMatchers(&match_finder
);
184 std::unique_ptr
<clang::tooling::FrontendActionFactory
> frontend_factory
=
185 clang::tooling::newFrontendActionFactory(&match_finder
);
186 int result
= tool
.run(frontend_factory
.get());
190 // Each replacement line should have the following format:
191 // r:<file path>:<offset>:<length>:<replacement text>
192 // Only the <replacement text> field can contain embedded ":" characters.
193 // TODO(dcheng): Use a more clever serialization. Ideally we'd use the YAML
194 // serialization and then use clang-apply-replacements, but that would require
195 // copying and pasting a larger amount of boilerplate for all Chrome clang
197 llvm::outs() << "==== BEGIN EDITS ====\n";
198 for (const auto& r
: replacements
) {
199 llvm::outs() << "r:" << r
.getFilePath() << ":" << r
.getOffset() << ":"
200 << r
.getLength() << ":" << r
.getReplacementText() << "\n";
202 llvm::outs() << "==== END EDITS ====\n";