Ben Murdoch | f91f061 | 2016-11-29 16:50:11 +0000 | [diff] [blame] | 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: |
| 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: |
| 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: |
| 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: |
| 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 | |
| 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_); |
Ben Murdoch | 62ed631 | 2017-06-06 11:06:27 +0100 | [diff] [blame] | 97 | // The implicitly generated constructor for temporary could be wrapped by |
| 98 | // implicitCastExpr, so ignoringParenImpCasts is needed. |
Ben Murdoch | f91f061 | 2016-11-29 16:50:11 +0000 | [diff] [blame] | 99 | match_finder->addMatcher( |
Ben Murdoch | 62ed631 | 2017-06-06 11:06:27 +0100 | [diff] [blame] | 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_); |
Ben Murdoch | f91f061 | 2016-11-29 16:50:11 +0000 | [diff] [blame] | 108 | } |
| 109 | |
| 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()); |
Ben Murdoch | 62ed631 | 2017-06-06 11:06:27 +0100 | [diff] [blame] | 120 | auto err = replacements_->add(Replacement(*result.SourceManager, range, "")); |
| 121 | assert(!err); |
Ben Murdoch | f91f061 | 2016-11-29 16:50:11 +0000 | [diff] [blame] | 122 | } |
| 123 | |
| 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"); |
Ben Murdoch | 62ed631 | 2017-06-06 11:06:27 +0100 | [diff] [blame] | 132 | auto err = replacements_->add(Replacement(*result.SourceManager, call, "")); |
| 133 | assert(!err); |
Ben Murdoch | f91f061 | 2016-11-29 16:50:11 +0000 | [diff] [blame] | 134 | } |
| 135 | |
| 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()) { |
Ben Murdoch | 62ed631 | 2017-06-06 11:06:27 +0100 | [diff] [blame] | 150 | auto err = |
| 151 | replacements_->add(Replacement(*result.SourceManager, literal, "")); |
| 152 | assert(!err); |
Ben Murdoch | f91f061 | 2016-11-29 16:50:11 +0000 | [diff] [blame] | 153 | } else { |
Ben Murdoch | 62ed631 | 2017-06-06 11:06:27 +0100 | [diff] [blame] | 154 | auto err = replacements_->add( |
Ben Murdoch | f91f061 | 2016-11-29 16:50:11 +0000 | [diff] [blame] | 155 | Replacement(*result.SourceManager, call, |
| 156 | literal->isWide() ? "std::wstring()" : "std::string()")); |
Ben Murdoch | 62ed631 | 2017-06-06 11:06:27 +0100 | [diff] [blame] | 157 | assert(!err); |
Ben Murdoch | f91f061 | 2016-11-29 16:50:11 +0000 | [diff] [blame] | 158 | } |
| 159 | } |
| 160 | |
| 161 | } // namespace |
| 162 | |
| 163 | static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); |
| 164 | |
| 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 | } |