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 "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::hasName;
     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::stringLiteral;
     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 // Handles replacements for stack and heap-allocated instances, e.g.:
     44 // std::string a("");
     45 // std::string* b = new std::string("");
     46 class 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.
     59 class 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.
     73 class 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 
     83 class 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 
     98 void 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 
    121 void 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 
    134 void 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 
    145 void 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 
    170 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage);
    171 
    172 int 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