Home | History | Annotate | Download | only in empty_string
      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/ASTMatchers.h"
     12 #include "clang/ASTMatchers/ASTMatchFinder.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 clang::ast_matchers::MatchFinder;
     21 using clang::ast_matchers::argumentCountIs;
     22 using clang::ast_matchers::bindTemporaryExpr;
     23 using clang::ast_matchers::constructorDecl;
     24 using clang::ast_matchers::constructExpr;
     25 using clang::ast_matchers::defaultArgExpr;
     26 using clang::ast_matchers::expr;
     27 using clang::ast_matchers::forEach;
     28 using clang::ast_matchers::has;
     29 using clang::ast_matchers::hasArgument;
     30 using clang::ast_matchers::hasDeclaration;
     31 using clang::ast_matchers::hasName;
     32 using clang::ast_matchers::id;
     33 using clang::ast_matchers::methodDecl;
     34 using clang::ast_matchers::newExpr;
     35 using clang::ast_matchers::ofClass;
     36 using clang::ast_matchers::stringLiteral;
     37 using clang::ast_matchers::varDecl;
     38 using clang::tooling::CommonOptionsParser;
     39 using clang::tooling::Replacement;
     40 using clang::tooling::Replacements;
     41 
     42 namespace {
     43 
     44 // Handles replacements for stack and heap-allocated instances, e.g.:
     45 // std::string a("");
     46 // std::string* b = new std::string("");
     47 class ConstructorCallback : public MatchFinder::MatchCallback {
     48  public:
     49   ConstructorCallback(Replacements* replacements)
     50       : replacements_(replacements) {}
     51 
     52   virtual void run(const MatchFinder::MatchResult& result) override;
     53 
     54  private:
     55   Replacements* const replacements_;
     56 };
     57 
     58 // Handles replacements for invocations of std::string("") in an initializer
     59 // list.
     60 class InitializerCallback : public MatchFinder::MatchCallback {
     61  public:
     62   InitializerCallback(Replacements* replacements)
     63       : replacements_(replacements) {}
     64 
     65   virtual void run(const MatchFinder::MatchResult& result) override;
     66 
     67  private:
     68   Replacements* const replacements_;
     69 };
     70 
     71 // Handles replacements for invocations of std::string("") in a temporary
     72 // context, e.g. FunctionThatTakesString(std::string("")). Note that this
     73 // handles implicits construction of std::string as well.
     74 class TemporaryCallback : public MatchFinder::MatchCallback {
     75  public:
     76   TemporaryCallback(Replacements* replacements) : replacements_(replacements) {}
     77 
     78   virtual void run(const MatchFinder::MatchResult& result) override;
     79 
     80  private:
     81   Replacements* const replacements_;
     82 };
     83 
     84 class EmptyStringConverter {
     85  public:
     86   explicit EmptyStringConverter(Replacements* replacements)
     87       : constructor_callback_(replacements),
     88         initializer_callback_(replacements),
     89         temporary_callback_(replacements) {}
     90 
     91   void SetupMatchers(MatchFinder* match_finder);
     92 
     93  private:
     94   ConstructorCallback constructor_callback_;
     95   InitializerCallback initializer_callback_;
     96   TemporaryCallback temporary_callback_;
     97 };
     98 
     99 void EmptyStringConverter::SetupMatchers(MatchFinder* match_finder) {
    100   const clang::ast_matchers::StatementMatcher& constructor_call =
    101       id("call",
    102          constructExpr(
    103              hasDeclaration(methodDecl(ofClass(hasName("std::basic_string")))),
    104              argumentCountIs(2),
    105              hasArgument(0, id("literal", stringLiteral())),
    106              hasArgument(1, defaultArgExpr())));
    107 
    108   // Note that expr(has()) in the matcher is significant; the Clang AST wraps
    109   // calls to the std::string constructor with exprWithCleanups nodes. Without
    110   // the expr(has()) matcher, the first and last rules would not match anything!
    111   match_finder->addMatcher(varDecl(forEach(expr(has(constructor_call)))),
    112                            &constructor_callback_);
    113   match_finder->addMatcher(newExpr(has(constructor_call)),
    114                            &constructor_callback_);
    115   match_finder->addMatcher(bindTemporaryExpr(has(constructor_call)),
    116                            &temporary_callback_);
    117   match_finder->addMatcher(
    118       constructorDecl(forEach(expr(has(constructor_call)))),
    119       &initializer_callback_);
    120 }
    121 
    122 void ConstructorCallback::run(const MatchFinder::MatchResult& result) {
    123   const clang::StringLiteral* literal =
    124       result.Nodes.getNodeAs<clang::StringLiteral>("literal");
    125   if (literal->getLength() > 0)
    126     return;
    127 
    128   const clang::CXXConstructExpr* call =
    129       result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
    130   clang::CharSourceRange range =
    131       clang::CharSourceRange::getTokenRange(call->getParenOrBraceRange());
    132   replacements_->insert(Replacement(*result.SourceManager, range, ""));
    133 }
    134 
    135 void InitializerCallback::run(const MatchFinder::MatchResult& result) {
    136   const clang::StringLiteral* literal =
    137       result.Nodes.getNodeAs<clang::StringLiteral>("literal");
    138   if (literal->getLength() > 0)
    139     return;
    140 
    141   const clang::CXXConstructExpr* call =
    142       result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
    143   replacements_->insert(Replacement(*result.SourceManager, call, ""));
    144 }
    145 
    146 void TemporaryCallback::run(const MatchFinder::MatchResult& result) {
    147   const clang::StringLiteral* literal =
    148       result.Nodes.getNodeAs<clang::StringLiteral>("literal");
    149   if (literal->getLength() > 0)
    150     return;
    151 
    152   const clang::CXXConstructExpr* call =
    153       result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
    154   // Differentiate between explicit and implicit calls to std::string's
    155   // constructor. An implicitly generated constructor won't have a valid
    156   // source range for the parenthesis. We do this because the matched expression
    157   // for |call| in the explicit case doesn't include the closing parenthesis.
    158   clang::SourceRange range = call->getParenOrBraceRange();
    159   if (range.isValid()) {
    160     replacements_->insert(Replacement(*result.SourceManager, literal, ""));
    161   } else {
    162     replacements_->insert(
    163         Replacement(*result.SourceManager,
    164                     call,
    165                     literal->isWide() ? "std::wstring()" : "std::string()"));
    166   }
    167 }
    168 
    169 }  // namespace
    170 
    171 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage);
    172 
    173 int main(int argc, const char* argv[]) {
    174   llvm::cl::OptionCategory category("EmptyString Tool");
    175   CommonOptionsParser options(argc, argv, category);
    176   clang::tooling::ClangTool tool(options.getCompilations(),
    177                                  options.getSourcePathList());
    178 
    179   Replacements replacements;
    180   EmptyStringConverter converter(&replacements);
    181   MatchFinder match_finder;
    182   converter.SetupMatchers(&match_finder);
    183 
    184   std::unique_ptr<clang::tooling::FrontendActionFactory> frontend_factory =
    185       clang::tooling::newFrontendActionFactory(&match_finder);
    186   int result = tool.run(frontend_factory.get());
    187   if (result != 0)
    188     return result;
    189 
    190   // Each replacement line should have the following format:
    191   // r:<file path>:<offset>:<length>:<replacement text>
    192   // Only the <replacement text> field can contain embedded ":" characters.
    193   // TODO(dcheng): Use a more clever serialization. Ideally we'd use the YAML
    194   // serialization and then use clang-apply-replacements, but that would require
    195   // copying and pasting a larger amount of boilerplate for all Chrome clang
    196   // tools.
    197   llvm::outs() << "==== BEGIN EDITS ====\n";
    198   for (const auto& r : replacements) {
    199     llvm::outs() << "r:" << r.getFilePath() << ":" << r.getOffset() << ":"
    200                  << r.getLength() << ":" << r.getReplacementText() << "\n";
    201   }
    202   llvm::outs() << "==== END EDITS ====\n";
    203 
    204   return 0;
    205 }
    206