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/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