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::matchesName; 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::unless; 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 bool IsNullConstant(const clang::Expr& expr, clang::ASTContext* context) { 44 return expr.isNullPointerConstant(*context, 45 clang::Expr::NPC_ValueDependentIsNotNull) != 46 clang::Expr::NPCK_NotNull; 47 } 48 49 // Handles replacements for stack and heap-allocated instances, e.g.: 50 // scoped_ptr<T> a(NULL); 51 // scoped_ptr<T>* b = new scoped_ptr<T>(NULL); 52 // ...though the latter should be pretty rare. 53 class ConstructorCallback : public MatchFinder::MatchCallback { 54 public: 55 ConstructorCallback(Replacements* replacements) 56 : replacements_(replacements) {} 57 58 virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; 59 60 private: 61 Replacements* const replacements_; 62 }; 63 64 // Handles replacements for invocations of scoped_ptr<T>(NULL) in an initializer 65 // list. 66 class InitializerCallback : public MatchFinder::MatchCallback { 67 public: 68 InitializerCallback(Replacements* replacements) 69 : replacements_(replacements) {} 70 71 virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; 72 73 private: 74 Replacements* const replacements_; 75 }; 76 77 // Handles replacements for invocations of scoped_ptr<T>(NULL) in a temporary 78 // context, e.g. return scoped_ptr<T>(NULL). 79 class TemporaryCallback : public MatchFinder::MatchCallback { 80 public: 81 TemporaryCallback(Replacements* replacements) : replacements_(replacements) {} 82 83 virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; 84 85 private: 86 Replacements* const replacements_; 87 }; 88 89 class EmptyStringConverter { 90 public: 91 explicit EmptyStringConverter(Replacements* replacements) 92 : constructor_callback_(replacements), 93 initializer_callback_(replacements), 94 temporary_callback_(replacements) {} 95 96 void SetupMatchers(MatchFinder* match_finder); 97 98 private: 99 ConstructorCallback constructor_callback_; 100 InitializerCallback initializer_callback_; 101 TemporaryCallback temporary_callback_; 102 }; 103 104 void EmptyStringConverter::SetupMatchers(MatchFinder* match_finder) { 105 const char kPattern[] = "^::(scoped_ptr|scoped_ptr_malloc)$"; 106 const clang::ast_matchers::StatementMatcher& constructor_call = id( 107 "call", 108 constructExpr(hasDeclaration(methodDecl(ofClass(matchesName(kPattern)))), 109 argumentCountIs(1), 110 hasArgument(0, id("arg", expr())), 111 unless(hasArgument(0, defaultArgExpr())))); 112 113 match_finder->addMatcher(varDecl(forEach(constructor_call)), 114 &constructor_callback_); 115 match_finder->addMatcher(newExpr(has(constructor_call)), 116 &constructor_callback_); 117 match_finder->addMatcher(bindTemporaryExpr(has(constructor_call)), 118 &temporary_callback_); 119 match_finder->addMatcher(constructorDecl(forEach(constructor_call)), 120 &initializer_callback_); 121 } 122 123 void ConstructorCallback::run(const MatchFinder::MatchResult& result) { 124 const clang::Expr* arg = result.Nodes.getNodeAs<clang::Expr>("arg"); 125 if (!IsNullConstant(*arg, result.Context)) 126 return; 127 128 const clang::CXXConstructExpr* call = 129 result.Nodes.getNodeAs<clang::CXXConstructExpr>("call"); 130 clang::CharSourceRange range = 131 clang::CharSourceRange::getTokenRange(call->getParenRange()); 132 replacements_->insert(Replacement(*result.SourceManager, range, "")); 133 } 134 135 void InitializerCallback::run(const MatchFinder::MatchResult& result) { 136 const clang::Expr* arg = result.Nodes.getNodeAs<clang::Expr>("arg"); 137 if (!IsNullConstant(*arg, result.Context)) 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::Expr* arg = result.Nodes.getNodeAs<clang::Expr>("arg"); 147 if (!IsNullConstant(*arg, result.Context)) 148 return; 149 150 // TODO(dcheng): File a bug with clang. There should be an easier way to do 151 // this replacement, but getTokenRange(call->getParenRange()) and the obvious 152 // (but incorrect) arg both don't work. The former is presumably just buggy, 153 // while the latter probably has to do with the fact that NULL is actually a 154 // macro which expands to a built-in. 155 clang::SourceRange range = arg->getSourceRange(); 156 clang::SourceRange expansion_range( 157 result.SourceManager->getExpansionLoc(range.getBegin()), 158 result.SourceManager->getExpansionLoc(range.getEnd())); 159 replacements_->insert( 160 Replacement(*result.SourceManager, 161 clang::CharSourceRange::getTokenRange(expansion_range), 162 "")); 163 } 164 165 } // namespace 166 167 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); 168 169 int main(int argc, const char* argv[]) { 170 CommonOptionsParser options(argc, argv); 171 clang::tooling::ClangTool tool(options.getCompilations(), 172 options.getSourcePathList()); 173 174 Replacements replacements; 175 EmptyStringConverter converter(&replacements); 176 MatchFinder match_finder; 177 converter.SetupMatchers(&match_finder); 178 179 int result = 180 tool.run(clang::tooling::newFrontendActionFactory(&match_finder)); 181 if (result != 0) 182 return result; 183 184 // Each replacement line should have the following format: 185 // r:<file path>:<offset>:<length>:<replacement text> 186 // Only the <replacement text> field can contain embedded ":" characters. 187 // TODO(dcheng): Use a more clever serialization. 188 llvm::outs() << "==== BEGIN EDITS ====\n"; 189 for (Replacements::const_iterator it = replacements.begin(); 190 it != replacements.end(); 191 ++it) { 192 llvm::outs() << "r:" << it->getFilePath() << ":" << it->getOffset() << ":" 193 << it->getLength() << ":" << it->getReplacementText() << "\n"; 194 } 195 llvm::outs() << "==== END EDITS ====\n"; 196 197 return 0; 198 } 199