| OLD | NEW |
| (Empty) |
| 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 } | |
| OLD | NEW |