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