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.
4 //
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.
9
10 #include <memory>
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/ASTMatchers/ASTMatchers.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"
19
20 using namespace clang::ast_matchers;
21 using clang::tooling::CommonOptionsParser;
22 using clang::tooling::Replacement;
23 using clang::tooling::Replacements;
24
25 namespace {
26
27 // Handles replacements for stack and heap-allocated instances, e.g.:
28 // std::string a("");
29 // std::string* b = new std::string("");
30 class ConstructorCallback : public MatchFinder::MatchCallback {
31 public:
ConstructorCallback(Replacements * replacements)32 ConstructorCallback(Replacements* replacements)
33 : replacements_(replacements) {}
34
35 virtual void run(const MatchFinder::MatchResult& result) override;
36
37 private:
38 Replacements* const replacements_;
39 };
40
41 // Handles replacements for invocations of std::string("") in an initializer
42 // list.
43 class InitializerCallback : public MatchFinder::MatchCallback {
44 public:
InitializerCallback(Replacements * replacements)45 InitializerCallback(Replacements* replacements)
46 : replacements_(replacements) {}
47
48 virtual void run(const MatchFinder::MatchResult& result) override;
49
50 private:
51 Replacements* const replacements_;
52 };
53
54 // Handles replacements for invocations of std::string("") in a temporary
55 // context, e.g. FunctionThatTakesString(std::string("")). Note that this
56 // handles implicits construction of std::string as well.
57 class TemporaryCallback : public MatchFinder::MatchCallback {
58 public:
TemporaryCallback(Replacements * replacements)59 TemporaryCallback(Replacements* replacements) : replacements_(replacements) {}
60
61 virtual void run(const MatchFinder::MatchResult& result) override;
62
63 private:
64 Replacements* const replacements_;
65 };
66
67 class EmptyStringConverter {
68 public:
EmptyStringConverter(Replacements * replacements)69 explicit EmptyStringConverter(Replacements* replacements)
70 : constructor_callback_(replacements),
71 initializer_callback_(replacements),
72 temporary_callback_(replacements) {}
73
74 void SetupMatchers(MatchFinder* match_finder);
75
76 private:
77 ConstructorCallback constructor_callback_;
78 InitializerCallback initializer_callback_;
79 TemporaryCallback temporary_callback_;
80 };
81
SetupMatchers(MatchFinder * match_finder)82 void EmptyStringConverter::SetupMatchers(MatchFinder* match_finder) {
83 const clang::ast_matchers::StatementMatcher& constructor_call = id(
84 "call",
85 cxxConstructExpr(
86 hasDeclaration(cxxMethodDecl(ofClass(hasName("std::basic_string")))),
87 argumentCountIs(2), hasArgument(0, id("literal", stringLiteral())),
88 hasArgument(1, cxxDefaultArgExpr())));
89
90 // Note that expr(has()) in the matcher is significant; the Clang AST wraps
91 // calls to the std::string constructor with exprWithCleanups nodes. Without
92 // the expr(has()) matcher, the first and last rules would not match anything!
93 match_finder->addMatcher(varDecl(forEach(expr(has(constructor_call)))),
94 &constructor_callback_);
95 match_finder->addMatcher(cxxNewExpr(has(constructor_call)),
96 &constructor_callback_);
97 match_finder->addMatcher(cxxBindTemporaryExpr(has(constructor_call)),
98 &temporary_callback_);
99 match_finder->addMatcher(
100 cxxConstructorDecl(forEach(expr(has(constructor_call)))),
101 &initializer_callback_);
102 }
103
run(const MatchFinder::MatchResult & result)104 void ConstructorCallback::run(const MatchFinder::MatchResult& result) {
105 const clang::StringLiteral* literal =
106 result.Nodes.getNodeAs<clang::StringLiteral>("literal");
107 if (literal->getLength() > 0)
108 return;
109
110 const clang::CXXConstructExpr* call =
111 result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
112 clang::CharSourceRange range =
113 clang::CharSourceRange::getTokenRange(call->getParenOrBraceRange());
114 replacements_->insert(Replacement(*result.SourceManager, range, ""));
115 }
116
run(const MatchFinder::MatchResult & result)117 void InitializerCallback::run(const MatchFinder::MatchResult& result) {
118 const clang::StringLiteral* literal =
119 result.Nodes.getNodeAs<clang::StringLiteral>("literal");
120 if (literal->getLength() > 0)
121 return;
122
123 const clang::CXXConstructExpr* call =
124 result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
125 replacements_->insert(Replacement(*result.SourceManager, call, ""));
126 }
127
run(const MatchFinder::MatchResult & result)128 void TemporaryCallback::run(const MatchFinder::MatchResult& result) {
129 const clang::StringLiteral* literal =
130 result.Nodes.getNodeAs<clang::StringLiteral>("literal");
131 if (literal->getLength() > 0)
132 return;
133
134 const clang::CXXConstructExpr* call =
135 result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
136 // Differentiate between explicit and implicit calls to std::string's
137 // constructor. An implicitly generated constructor won't have a valid
138 // source range for the parenthesis. We do this because the matched expression
139 // for |call| in the explicit case doesn't include the closing parenthesis.
140 clang::SourceRange range = call->getParenOrBraceRange();
141 if (range.isValid()) {
142 replacements_->insert(Replacement(*result.SourceManager, literal, ""));
143 } else {
144 replacements_->insert(
145 Replacement(*result.SourceManager, call,
146 literal->isWide() ? "std::wstring()" : "std::string()"));
147 }
148 }
149
150 } // namespace
151
152 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage);
153
main(int argc,const char * argv[])154 int main(int argc, const char* argv[]) {
155 llvm::cl::OptionCategory category("EmptyString Tool");
156 CommonOptionsParser options(argc, argv, category);
157 clang::tooling::ClangTool tool(options.getCompilations(),
158 options.getSourcePathList());
159
160 Replacements replacements;
161 EmptyStringConverter converter(&replacements);
162 MatchFinder match_finder;
163 converter.SetupMatchers(&match_finder);
164
165 std::unique_ptr<clang::tooling::FrontendActionFactory> frontend_factory =
166 clang::tooling::newFrontendActionFactory(&match_finder);
167 int result = tool.run(frontend_factory.get());
168 if (result != 0)
169 return result;
170
171 // Each replacement line should have the following format:
172 // r:<file path>:<offset>:<length>:<replacement text>
173 // Only the <replacement text> field can contain embedded ":" characters.
174 // TODO(dcheng): Use a more clever serialization. Ideally we'd use the YAML
175 // serialization and then use clang-apply-replacements, but that would require
176 // copying and pasting a larger amount of boilerplate for all Chrome clang
177 // tools.
178 llvm::outs() << "==== BEGIN EDITS ====\n";
179 for (const auto& r : replacements) {
180 llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset()
181 << ":::" << r.getLength() << ":::" << r.getReplacementText()
182 << "\n";
183 }
184 llvm::outs() << "==== END EDITS ====\n";
185
186 return 0;
187 }
188