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 19 using clang::ast_matchers::MatchFinder; 20 using clang::ast_matchers::argumentCountIs; 21 using clang::ast_matchers::bindTemporaryExpr; 22 using clang::ast_matchers::constructorDecl; 23 using clang::ast_matchers::constructExpr; 24 using clang::ast_matchers::defaultArgExpr; 25 using clang::ast_matchers::expr; 26 using clang::ast_matchers::forEach; 27 using clang::ast_matchers::has; 28 using clang::ast_matchers::hasArgument; 29 using clang::ast_matchers::hasDeclaration; 30 using clang::ast_matchers::hasName; 31 using clang::ast_matchers::id; 32 using clang::ast_matchers::methodDecl; 33 using clang::ast_matchers::newExpr; 34 using clang::ast_matchers::ofClass; 35 using clang::ast_matchers::stringLiteral; 36 using clang::ast_matchers::varDecl; 37 using clang::tooling::CommonOptionsParser; 38 using clang::tooling::Replacement; 39 using clang::tooling::Replacements; 40 41 namespace { 42 43 // Handles replacements for stack and heap-allocated instances, e.g.: 44 // std::string a(""); 45 // std::string* b = new std::string(""); 46 class 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. 59 class 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. 73 class 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 83 class 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 98 void 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 121 void 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 134 void 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 145 void 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 170 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); 171 172 int 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