aboutsummaryrefslogtreecommitdiff
path: root/tools/clang/empty_string/EmptyStringConverter.cpp
blob: fce692ff0340c0dfa9622b2d12c728811897a890 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This implements a Clang tool to convert all instances of std::string("") to
// std::string(). The latter is more efficient (as std::string doesn't have to
// take a copy of an empty string) and generates fewer instructions as well. It
// should be run using the tools/clang/scripts/run_tool.py helper.

#include <memory>
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"

using namespace clang::ast_matchers;
using clang::tooling::CommonOptionsParser;
using clang::tooling::Replacement;
using clang::tooling::Replacements;

namespace {

// Handles replacements for stack and heap-allocated instances, e.g.:
// std::string a("");
// std::string* b = new std::string("");
class ConstructorCallback : public MatchFinder::MatchCallback {
 public:
  ConstructorCallback(Replacements* replacements)
      : replacements_(replacements) {}

  virtual void run(const MatchFinder::MatchResult& result) override;

 private:
  Replacements* const replacements_;
};

// Handles replacements for invocations of std::string("") in an initializer
// list.
class InitializerCallback : public MatchFinder::MatchCallback {
 public:
  InitializerCallback(Replacements* replacements)
      : replacements_(replacements) {}

  virtual void run(const MatchFinder::MatchResult& result) override;

 private:
  Replacements* const replacements_;
};

// Handles replacements for invocations of std::string("") in a temporary
// context, e.g. FunctionThatTakesString(std::string("")). Note that this
// handles implicits construction of std::string as well.
class TemporaryCallback : public MatchFinder::MatchCallback {
 public:
  TemporaryCallback(Replacements* replacements) : replacements_(replacements) {}

  virtual void run(const MatchFinder::MatchResult& result) override;

 private:
  Replacements* const replacements_;
};

class EmptyStringConverter {
 public:
  explicit EmptyStringConverter(Replacements* replacements)
      : constructor_callback_(replacements),
        initializer_callback_(replacements),
        temporary_callback_(replacements) {}

  void SetupMatchers(MatchFinder* match_finder);

 private:
  ConstructorCallback constructor_callback_;
  InitializerCallback initializer_callback_;
  TemporaryCallback temporary_callback_;
};

void EmptyStringConverter::SetupMatchers(MatchFinder* match_finder) {
  const clang::ast_matchers::StatementMatcher& constructor_call = id(
      "call",
      cxxConstructExpr(
          hasDeclaration(cxxMethodDecl(ofClass(hasName("std::basic_string")))),
          argumentCountIs(2), hasArgument(0, id("literal", stringLiteral())),
          hasArgument(1, cxxDefaultArgExpr())));

  // Note that expr(has()) in the matcher is significant; the Clang AST wraps
  // calls to the std::string constructor with exprWithCleanups nodes. Without
  // the expr(has()) matcher, the first and last rules would not match anything!
  match_finder->addMatcher(varDecl(forEach(expr(has(constructor_call)))),
                           &constructor_callback_);
  match_finder->addMatcher(cxxNewExpr(has(constructor_call)),
                           &constructor_callback_);
  match_finder->addMatcher(cxxBindTemporaryExpr(has(constructor_call)),
                           &temporary_callback_);
  match_finder->addMatcher(
      cxxConstructorDecl(forEach(expr(has(constructor_call)))),
      &initializer_callback_);
}

void ConstructorCallback::run(const MatchFinder::MatchResult& result) {
  const clang::StringLiteral* literal =
      result.Nodes.getNodeAs<clang::StringLiteral>("literal");
  if (literal->getLength() > 0)
    return;

  const clang::CXXConstructExpr* call =
      result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
  clang::CharSourceRange range =
      clang::CharSourceRange::getTokenRange(call->getParenOrBraceRange());
  replacements_->insert(Replacement(*result.SourceManager, range, ""));
}

void InitializerCallback::run(const MatchFinder::MatchResult& result) {
  const clang::StringLiteral* literal =
      result.Nodes.getNodeAs<clang::StringLiteral>("literal");
  if (literal->getLength() > 0)
    return;

  const clang::CXXConstructExpr* call =
      result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
  replacements_->insert(Replacement(*result.SourceManager, call, ""));
}

void TemporaryCallback::run(const MatchFinder::MatchResult& result) {
  const clang::StringLiteral* literal =
      result.Nodes.getNodeAs<clang::StringLiteral>("literal");
  if (literal->getLength() > 0)
    return;

  const clang::CXXConstructExpr* call =
      result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
  // Differentiate between explicit and implicit calls to std::string's
  // constructor. An implicitly generated constructor won't have a valid
  // source range for the parenthesis. We do this because the matched expression
  // for |call| in the explicit case doesn't include the closing parenthesis.
  clang::SourceRange range = call->getParenOrBraceRange();
  if (range.isValid()) {
    replacements_->insert(Replacement(*result.SourceManager, literal, ""));
  } else {
    replacements_->insert(
        Replacement(*result.SourceManager, call,
                    literal->isWide() ? "std::wstring()" : "std::string()"));
  }
}

}  // namespace

static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage);

int main(int argc, const char* argv[]) {
  llvm::cl::OptionCategory category("EmptyString Tool");
  CommonOptionsParser options(argc, argv, category);
  clang::tooling::ClangTool tool(options.getCompilations(),
                                 options.getSourcePathList());

  Replacements replacements;
  EmptyStringConverter converter(&replacements);
  MatchFinder match_finder;
  converter.SetupMatchers(&match_finder);

  std::unique_ptr<clang::tooling::FrontendActionFactory> frontend_factory =
      clang::tooling::newFrontendActionFactory(&match_finder);
  int result = tool.run(frontend_factory.get());
  if (result != 0)
    return result;

  // Each replacement line should have the following format:
  // r:<file path>:<offset>:<length>:<replacement text>
  // Only the <replacement text> field can contain embedded ":" characters.
  // TODO(dcheng): Use a more clever serialization. Ideally we'd use the YAML
  // serialization and then use clang-apply-replacements, but that would require
  // copying and pasting a larger amount of boilerplate for all Chrome clang
  // tools.
  llvm::outs() << "==== BEGIN EDITS ====\n";
  for (const auto& r : replacements) {
    llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset()
                 << ":::" << r.getLength() << ":::" << r.getReplacementText()
                 << "\n";
  }
  llvm::outs() << "==== END EDITS ====\n";

  return 0;
}