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