• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   // The implicitly generated constructor for temporary could be wrapped by
98   // implicitCastExpr, so ignoringParenImpCasts is needed.
99   match_finder->addMatcher(
100       cxxBindTemporaryExpr(ignoringParenImpCasts(forEach(constructor_call))),
101       &temporary_callback_);
102   // Note that forEachConstructorInitializer is needed. The std::string
103   // constructor is wrapped by exprWithCleanups and cxxCtorInitializer.
104   // forEach() would not work.
105   match_finder->addMatcher(cxxConstructorDecl(forEachConstructorInitializer(
106                                withInitializer(expr(has(constructor_call))))),
107                            &initializer_callback_);
108 }
109 
run(const MatchFinder::MatchResult & result)110 void ConstructorCallback::run(const MatchFinder::MatchResult& result) {
111   const clang::StringLiteral* literal =
112       result.Nodes.getNodeAs<clang::StringLiteral>("literal");
113   if (literal->getLength() > 0)
114     return;
115 
116   const clang::CXXConstructExpr* call =
117       result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
118   clang::CharSourceRange range =
119       clang::CharSourceRange::getTokenRange(call->getParenOrBraceRange());
120   auto err = replacements_->add(Replacement(*result.SourceManager, range, ""));
121   assert(!err);
122 }
123 
run(const MatchFinder::MatchResult & result)124 void InitializerCallback::run(const MatchFinder::MatchResult& result) {
125   const clang::StringLiteral* literal =
126       result.Nodes.getNodeAs<clang::StringLiteral>("literal");
127   if (literal->getLength() > 0)
128     return;
129 
130   const clang::CXXConstructExpr* call =
131       result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
132   auto err = replacements_->add(Replacement(*result.SourceManager, call, ""));
133   assert(!err);
134 }
135 
run(const MatchFinder::MatchResult & result)136 void TemporaryCallback::run(const MatchFinder::MatchResult& result) {
137   const clang::StringLiteral* literal =
138       result.Nodes.getNodeAs<clang::StringLiteral>("literal");
139   if (literal->getLength() > 0)
140     return;
141 
142   const clang::CXXConstructExpr* call =
143       result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
144   // Differentiate between explicit and implicit calls to std::string's
145   // constructor. An implicitly generated constructor won't have a valid
146   // source range for the parenthesis. We do this because the matched expression
147   // for |call| in the explicit case doesn't include the closing parenthesis.
148   clang::SourceRange range = call->getParenOrBraceRange();
149   if (range.isValid()) {
150     auto err =
151         replacements_->add(Replacement(*result.SourceManager, literal, ""));
152     assert(!err);
153   } else {
154     auto err = replacements_->add(
155         Replacement(*result.SourceManager, call,
156                     literal->isWide() ? "std::wstring()" : "std::string()"));
157     assert(!err);
158   }
159 }
160 
161 }  // namespace
162 
163 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage);
164 
main(int argc,const char * argv[])165 int main(int argc, const char* argv[]) {
166   llvm::cl::OptionCategory category("EmptyString Tool");
167   CommonOptionsParser options(argc, argv, category);
168   clang::tooling::ClangTool tool(options.getCompilations(),
169                                  options.getSourcePathList());
170 
171   Replacements replacements;
172   EmptyStringConverter converter(&replacements);
173   MatchFinder match_finder;
174   converter.SetupMatchers(&match_finder);
175 
176   std::unique_ptr<clang::tooling::FrontendActionFactory> frontend_factory =
177       clang::tooling::newFrontendActionFactory(&match_finder);
178   int result = tool.run(frontend_factory.get());
179   if (result != 0)
180     return result;
181 
182   // Each replacement line should have the following format:
183   // r:<file path>:<offset>:<length>:<replacement text>
184   // Only the <replacement text> field can contain embedded ":" characters.
185   // TODO(dcheng): Use a more clever serialization. Ideally we'd use the YAML
186   // serialization and then use clang-apply-replacements, but that would require
187   // copying and pasting a larger amount of boilerplate for all Chrome clang
188   // tools.
189   llvm::outs() << "==== BEGIN EDITS ====\n";
190   for (const auto& r : replacements) {
191     llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset()
192                  << ":::" << r.getLength() << ":::" << r.getReplacementText()
193                  << "\n";
194   }
195   llvm::outs() << "==== END EDITS ====\n";
196 
197   return 0;
198 }
199