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