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_); 97 // The implicitly generated constructor for temporary could be wrapped by 98 // implicitCastExpr, so ignoringParenImpCasts is needed. 99 match_finder->addMatcher( 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_); 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()); 120 auto err = replacements_->add(Replacement(*result.SourceManager, range, "")); 121 assert(!err); 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"); 132 auto err = replacements_->add(Replacement(*result.SourceManager, call, "")); 133 assert(!err); 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()) { 150 auto err = 151 replacements_->add(Replacement(*result.SourceManager, literal, "")); 152 assert(!err); 153 } else { 154 auto err = replacements_->add( 155 Replacement(*result.SourceManager, call, 156 literal->isWide() ? "std::wstring()" : "std::string()")); 157 assert(!err); 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 } 199