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 188 189 190 191 192 193 194 195 196 197 198 199 200 201
|
// Copyright 2013 The Chromium Authors
// 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 =
cxxConstructExpr(
hasDeclaration(cxxMethodDecl(ofClass(hasName("std::basic_string")))),
argumentCountIs(2), hasArgument(0, stringLiteral().bind("literal")),
hasArgument(1, cxxDefaultArgExpr()))
.bind("call");
// 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_);
// The implicitly generated constructor for temporary could be wrapped by
// implicitCastExpr, so ignoringParenImpCasts is needed.
match_finder->addMatcher(
cxxBindTemporaryExpr(ignoringParenImpCasts(forEach(constructor_call))),
&temporary_callback_);
// Note that forEachConstructorInitializer is needed. The std::string
// constructor is wrapped by exprWithCleanups and cxxCtorInitializer.
// forEach() would not work.
match_finder->addMatcher(cxxConstructorDecl(forEachConstructorInitializer(
withInitializer(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());
auto err = replacements_->add(Replacement(*result.SourceManager, range, ""));
assert(!err);
}
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");
auto err = replacements_->add(Replacement(*result.SourceManager, call, ""));
assert(!err);
}
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()) {
auto err =
replacements_->add(Replacement(*result.SourceManager, literal, ""));
assert(!err);
} else {
auto err = replacements_->add(
Replacement(*result.SourceManager, call,
literal->isWide() ? "std::wstring()" : "std::string()"));
assert(!err);
}
}
} // 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;
if (replacements.empty())
return 0;
// 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;
}
|