brintos

brintos / llvm-project-archived public Read only

0
0
Text · 22.5 KiB · a92b3f9 Raw
646 lines · cpp
1//===--- ClangRefactor.cpp - Clang-based refactoring tool -----------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8///9/// \file10/// This file implements a clang-refactor tool that performs various11/// source transformations.12///13//===----------------------------------------------------------------------===//14 15#include "TestSupport.h"16#include "clang/Frontend/CommandLineSourceLoc.h"17#include "clang/Frontend/TextDiagnosticPrinter.h"18#include "clang/Rewrite/Core/Rewriter.h"19#include "clang/Tooling/CommonOptionsParser.h"20#include "clang/Tooling/Refactoring.h"21#include "clang/Tooling/Refactoring/RefactoringAction.h"22#include "clang/Tooling/Refactoring/RefactoringOptions.h"23#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"24#include "clang/Tooling/Tooling.h"25#include "llvm/Support/CommandLine.h"26#include "llvm/Support/FileSystem.h"27#include "llvm/Support/Signals.h"28#include "llvm/Support/raw_ostream.h"29#include <optional>30#include <string>31 32using namespace clang;33using namespace tooling;34using namespace refactor;35namespace cl = llvm::cl;36 37namespace opts {38 39static cl::OptionCategory CommonRefactorOptions("Refactoring options");40 41static cl::opt<bool> Verbose("v", cl::desc("Use verbose output"),42                             cl::cat(cl::getGeneralCategory()),43                             cl::sub(cl::SubCommand::getAll()));44 45static cl::opt<bool> Inplace("i", cl::desc("Inplace edit <file>s"),46                             cl::cat(cl::getGeneralCategory()),47                             cl::sub(cl::SubCommand::getAll()));48 49} // end namespace opts50 51namespace {52 53/// Stores the parsed `-selection` argument.54class SourceSelectionArgument {55public:56  virtual ~SourceSelectionArgument() {}57 58  /// Parse the `-selection` argument.59  ///60  /// \returns A valid argument when the parse succedeed, null otherwise.61  static std::unique_ptr<SourceSelectionArgument> fromString(StringRef Value);62 63  /// Prints any additional state associated with the selection argument to64  /// the given output stream.65  virtual void print(raw_ostream &OS) {}66 67  /// Returns a replacement refactoring result consumer (if any) that should68  /// consume the results of a refactoring operation.69  ///70  /// The replacement refactoring result consumer is used by \c71  /// TestSourceSelectionArgument to inject a test-specific result handling72  /// logic into the refactoring operation. The test-specific consumer73  /// ensures that the individual results in a particular test group are74  /// identical.75  virtual std::unique_ptr<ClangRefactorToolConsumerInterface>76  createCustomConsumer() {77    return nullptr;78  }79 80  /// Runs the give refactoring function for each specified selection.81  ///82  /// \returns true if an error occurred, false otherwise.83  virtual bool84  forAllRanges(const SourceManager &SM,85               llvm::function_ref<void(SourceRange R)> Callback) = 0;86};87 88/// Stores the parsed -selection=test:<filename> option.89class TestSourceSelectionArgument final : public SourceSelectionArgument {90public:91  TestSourceSelectionArgument(TestSelectionRangesInFile TestSelections)92      : TestSelections(std::move(TestSelections)) {}93 94  void print(raw_ostream &OS) override { TestSelections.dump(OS); }95 96  std::unique_ptr<ClangRefactorToolConsumerInterface>97  createCustomConsumer() override {98    return TestSelections.createConsumer();99  }100 101  /// Testing support: invokes the selection action for each selection range in102  /// the test file.103  bool forAllRanges(const SourceManager &SM,104                    llvm::function_ref<void(SourceRange R)> Callback) override {105    return TestSelections.foreachRange(SM, Callback);106  }107 108private:109  TestSelectionRangesInFile TestSelections;110};111 112/// Stores the parsed -selection=filename:line:column[-line:column] option.113class SourceRangeSelectionArgument final : public SourceSelectionArgument {114public:115  SourceRangeSelectionArgument(ParsedSourceRange Range)116      : Range(std::move(Range)) {}117 118  bool forAllRanges(const SourceManager &SM,119                    llvm::function_ref<void(SourceRange R)> Callback) override {120    auto FE = SM.getFileManager().getOptionalFileRef(Range.FileName);121    FileID FID = FE ? SM.translateFile(*FE) : FileID();122    if (!FE || FID.isInvalid()) {123      llvm::errs() << "error: -selection=" << Range.FileName124                   << ":... : given file is not in the target TU\n";125      return true;126    }127 128    SourceLocation Start = SM.getMacroArgExpandedLocation(129        SM.translateLineCol(FID, Range.Begin.first, Range.Begin.second));130    SourceLocation End = SM.getMacroArgExpandedLocation(131        SM.translateLineCol(FID, Range.End.first, Range.End.second));132    if (Start.isInvalid() || End.isInvalid()) {133      llvm::errs() << "error: -selection=" << Range.FileName << ':'134                   << Range.Begin.first << ':' << Range.Begin.second << '-'135                   << Range.End.first << ':' << Range.End.second136                   << " : invalid source location\n";137      return true;138    }139    Callback(SourceRange(Start, End));140    return false;141  }142 143private:144  ParsedSourceRange Range;145};146 147std::unique_ptr<SourceSelectionArgument>148SourceSelectionArgument::fromString(StringRef Value) {149  if (Value.starts_with("test:")) {150    StringRef Filename = Value.drop_front(strlen("test:"));151    std::optional<TestSelectionRangesInFile> ParsedTestSelection =152        findTestSelectionRanges(Filename);153    if (!ParsedTestSelection)154      return nullptr; // A parsing error was already reported.155    return std::make_unique<TestSourceSelectionArgument>(156        std::move(*ParsedTestSelection));157  }158  std::optional<ParsedSourceRange> Range = ParsedSourceRange::fromString(Value);159  if (Range)160    return std::make_unique<SourceRangeSelectionArgument>(std::move(*Range));161  llvm::errs() << "error: '-selection' option must be specified using "162                  "<file>:<line>:<column> or "163                  "<file>:<line>:<column>-<line>:<column> format, "164                  "where <line> and <column> are integers greater than zero.\n";165  return nullptr;166}167 168/// A container that stores the command-line options used by a single169/// refactoring option.170class RefactoringActionCommandLineOptions {171public:172  void addStringOption(const RefactoringOption &Option,173                       std::unique_ptr<cl::opt<std::string>> CLOption) {174    StringOptions[&Option] = std::move(CLOption);175  }176 177  const cl::opt<std::string> &178  getStringOption(const RefactoringOption &Opt) const {179    auto It = StringOptions.find(&Opt);180    return *It->second;181  }182 183private:184  llvm::DenseMap<const RefactoringOption *,185                 std::unique_ptr<cl::opt<std::string>>>186      StringOptions;187};188 189/// Passes the command-line option values to the options used by a single190/// refactoring action rule.191class CommandLineRefactoringOptionVisitor final192    : public RefactoringOptionVisitor {193public:194  CommandLineRefactoringOptionVisitor(195      const RefactoringActionCommandLineOptions &Options)196      : Options(Options) {}197 198  void visit(const RefactoringOption &Opt,199             std::optional<std::string> &Value) override {200    const cl::opt<std::string> &CLOpt = Options.getStringOption(Opt);201    if (!CLOpt.getValue().empty()) {202      Value = CLOpt.getValue();203      return;204    }205    Value = std::nullopt;206    if (Opt.isRequired())207      MissingRequiredOptions.push_back(&Opt);208  }209 210  ArrayRef<const RefactoringOption *> getMissingRequiredOptions() const {211    return MissingRequiredOptions;212  }213 214private:215  llvm::SmallVector<const RefactoringOption *, 4> MissingRequiredOptions;216  const RefactoringActionCommandLineOptions &Options;217};218 219/// Creates the refactoring options used by all the rules in a single220/// refactoring action.221class CommandLineRefactoringOptionCreator final222    : public RefactoringOptionVisitor {223public:224  CommandLineRefactoringOptionCreator(225      cl::OptionCategory &Category, cl::SubCommand &Subcommand,226      RefactoringActionCommandLineOptions &Options)227      : Category(Category), Subcommand(Subcommand), Options(Options) {}228 229  void visit(const RefactoringOption &Opt,230             std::optional<std::string> &) override {231    if (Visited.insert(&Opt).second)232      Options.addStringOption(Opt, create<std::string>(Opt));233  }234 235private:236  template <typename T>237  std::unique_ptr<cl::opt<T>> create(const RefactoringOption &Opt) {238    if (!OptionNames.insert(Opt.getName()).second)239      llvm::report_fatal_error("Multiple identical refactoring options "240                               "specified for one refactoring action");241    // FIXME: cl::Required can be specified when this option is present242    // in all rules in an action.243    return std::make_unique<cl::opt<T>>(244        Opt.getName(), cl::desc(Opt.getDescription()), cl::Optional,245        cl::cat(Category), cl::sub(Subcommand));246  }247 248  llvm::SmallPtrSet<const RefactoringOption *, 8> Visited;249  llvm::StringSet<> OptionNames;250  cl::OptionCategory &Category;251  cl::SubCommand &Subcommand;252  RefactoringActionCommandLineOptions &Options;253};254 255/// A subcommand that corresponds to individual refactoring action.256class RefactoringActionSubcommand : public cl::SubCommand {257public:258  RefactoringActionSubcommand(std::unique_ptr<RefactoringAction> Action,259                              RefactoringActionRules ActionRules,260                              cl::OptionCategory &Category)261      : SubCommand(Action->getCommand(), Action->getDescription()),262        Action(std::move(Action)), ActionRules(std::move(ActionRules)) {263    // Check if the selection option is supported.264    for (const auto &Rule : this->ActionRules) {265      if (Rule->hasSelectionRequirement()) {266        Selection = std::make_unique<cl::opt<std::string>>(267            "selection",268            cl::desc(269                "The selected source range in which the refactoring should "270                "be initiated (<file>:<line>:<column>-<line>:<column> or "271                "<file>:<line>:<column>)"),272            cl::cat(Category), cl::sub(*this));273        break;274      }275    }276    // Create the refactoring options.277    for (const auto &Rule : this->ActionRules) {278      CommandLineRefactoringOptionCreator OptionCreator(Category, *this,279                                                        Options);280      Rule->visitRefactoringOptions(OptionCreator);281    }282  }283 284  ~RefactoringActionSubcommand() { unregisterSubCommand(); }285 286  const RefactoringActionRules &getActionRules() const { return ActionRules; }287 288  /// Parses the "-selection" command-line argument.289  ///290  /// \returns true on error, false otherwise.291  bool parseSelectionArgument() {292    if (Selection) {293      ParsedSelection = SourceSelectionArgument::fromString(*Selection);294      if (!ParsedSelection)295        return true;296    }297    return false;298  }299 300  SourceSelectionArgument *getSelection() const {301    assert(Selection && "selection not supported!");302    return ParsedSelection.get();303  }304 305  const RefactoringActionCommandLineOptions &getOptions() const {306    return Options;307  }308 309private:310  std::unique_ptr<RefactoringAction> Action;311  RefactoringActionRules ActionRules;312  std::unique_ptr<cl::opt<std::string>> Selection;313  std::unique_ptr<SourceSelectionArgument> ParsedSelection;314  RefactoringActionCommandLineOptions Options;315};316 317class ClangRefactorConsumer final : public ClangRefactorToolConsumerInterface {318public:319  ClangRefactorConsumer(AtomicChanges &Changes) : SourceChanges(&Changes) {}320 321  void handleError(llvm::Error Err) override {322    std::optional<PartialDiagnosticAt> Diag = DiagnosticError::take(Err);323    if (!Diag) {324      llvm::errs() << llvm::toString(std::move(Err)) << "\n";325      return;326    }327    llvm::cantFail(std::move(Err)); // This is a success.328    DiagnosticBuilder DB(329        getDiags().Report(Diag->first, Diag->second.getDiagID()));330    Diag->second.Emit(DB);331  }332 333  void handle(AtomicChanges Changes) override {334    SourceChanges->insert(SourceChanges->begin(), Changes.begin(),335                          Changes.end());336  }337 338  void handle(SymbolOccurrences Occurrences) override {339    llvm_unreachable("symbol occurrence results are not handled yet");340  }341 342private:343  AtomicChanges *SourceChanges;344};345 346class ClangRefactorTool {347public:348  ClangRefactorTool()349      : SelectedSubcommand(nullptr), MatchingRule(nullptr),350        Consumer(new ClangRefactorConsumer(Changes)), HasFailed(false) {351    std::vector<std::unique_ptr<RefactoringAction>> Actions =352        createRefactoringActions();353 354    // Actions must have unique command names so that we can map them to one355    // subcommand.356    llvm::StringSet<> CommandNames;357    for (const auto &Action : Actions) {358      if (!CommandNames.insert(Action->getCommand()).second) {359        llvm::errs() << "duplicate refactoring action command '"360                     << Action->getCommand() << "'!";361        exit(1);362      }363    }364 365    // Create subcommands and command-line options.366    for (auto &Action : Actions) {367      SubCommands.push_back(std::make_unique<RefactoringActionSubcommand>(368          std::move(Action), Action->createActiveActionRules(),369          opts::CommonRefactorOptions));370    }371  }372 373  // Initializes the selected subcommand and refactoring rule based on the374  // command line options.375  llvm::Error Init() {376    auto Subcommand = getSelectedSubcommand();377    if (!Subcommand)378      return Subcommand.takeError();379    auto Rule = getMatchingRule(**Subcommand);380    if (!Rule)381      return Rule.takeError();382 383    SelectedSubcommand = *Subcommand;384    MatchingRule = *Rule;385 386    return llvm::Error::success();387  }388 389  bool hasFailed() const { return HasFailed; }390 391  using TUCallbackType = std::function<void(ASTContext &)>;392 393  // Callback of an AST action. This invokes the matching rule on the given AST.394  void callback(ASTContext &AST) {395    assert(SelectedSubcommand && MatchingRule && Consumer);396    RefactoringRuleContext Context(AST.getSourceManager());397    Context.setASTContext(AST);398 399    // If the selection option is test specific, we use a test-specific400    // consumer.401    std::unique_ptr<ClangRefactorToolConsumerInterface> TestConsumer;402    bool HasSelection = MatchingRule->hasSelectionRequirement();403    if (HasSelection)404      TestConsumer = SelectedSubcommand->getSelection()->createCustomConsumer();405    ClangRefactorToolConsumerInterface *ActiveConsumer =406        TestConsumer ? TestConsumer.get() : Consumer.get();407    ActiveConsumer->beginTU(AST);408 409    auto InvokeRule = [&](RefactoringResultConsumer &Consumer) {410      if (opts::Verbose)411        logInvocation(*SelectedSubcommand, Context);412      MatchingRule->invoke(*ActiveConsumer, Context);413    };414    if (HasSelection) {415      assert(SelectedSubcommand->getSelection() &&416             "Missing selection argument?");417      if (opts::Verbose)418        SelectedSubcommand->getSelection()->print(llvm::outs());419      if (SelectedSubcommand->getSelection()->forAllRanges(420              Context.getSources(), [&](SourceRange R) {421                Context.setSelectionRange(R);422                InvokeRule(*ActiveConsumer);423              }))424        HasFailed = true;425      ActiveConsumer->endTU();426      return;427    }428    InvokeRule(*ActiveConsumer);429    ActiveConsumer->endTU();430  }431 432  llvm::Expected<std::unique_ptr<FrontendActionFactory>>433  getFrontendActionFactory() {434    class ToolASTConsumer : public ASTConsumer {435    public:436      TUCallbackType Callback;437      ToolASTConsumer(TUCallbackType Callback)438          : Callback(std::move(Callback)) {}439 440      void HandleTranslationUnit(ASTContext &Context) override {441        Callback(Context);442      }443    };444    class ToolASTAction : public ASTFrontendAction {445    public:446      explicit ToolASTAction(TUCallbackType Callback)447          : Callback(std::move(Callback)) {}448 449    protected:450      std::unique_ptr<clang::ASTConsumer>451      CreateASTConsumer(clang::CompilerInstance &compiler,452                        StringRef /* dummy */) override {453        std::unique_ptr<clang::ASTConsumer> Consumer{454            new ToolASTConsumer(Callback)};455        return Consumer;456      }457 458    private:459      TUCallbackType Callback;460    };461 462    class ToolActionFactory : public FrontendActionFactory {463    public:464      ToolActionFactory(TUCallbackType Callback)465          : Callback(std::move(Callback)) {}466 467      std::unique_ptr<FrontendAction> create() override {468        return std::make_unique<ToolASTAction>(Callback);469      }470 471    private:472      TUCallbackType Callback;473    };474 475    return std::make_unique<ToolActionFactory>(476        [this](ASTContext &AST) { return callback(AST); });477  }478 479  // FIXME(ioeric): this seems to only works for changes in a single file at480  // this point.481  bool applySourceChanges() {482    std::set<std::string> Files;483    for (const auto &Change : Changes)484      Files.insert(Change.getFilePath());485    // FIXME: Add automatic formatting support as well.486    tooling::ApplyChangesSpec Spec;487    // FIXME: We should probably cleanup the result by default as well.488    Spec.Cleanup = false;489    for (const auto &File : Files) {490      llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferErr =491          llvm::MemoryBuffer::getFile(File);492      if (!BufferErr) {493        llvm::errs() << "error: failed to open " << File << " for rewriting\n";494        return true;495      }496      auto Result = tooling::applyAtomicChanges(File, (*BufferErr)->getBuffer(),497                                                Changes, Spec);498      if (!Result) {499        llvm::errs() << toString(Result.takeError());500        return true;501      }502 503      if (opts::Inplace) {504        std::error_code EC;505        llvm::raw_fd_ostream OS(File, EC, llvm::sys::fs::OF_TextWithCRLF);506        if (EC) {507          llvm::errs() << EC.message() << "\n";508          return true;509        }510        OS << *Result;511        continue;512      }513 514      llvm::outs() << *Result;515    }516    return false;517  }518 519private:520  /// Logs an individual refactoring action invocation to STDOUT.521  void logInvocation(RefactoringActionSubcommand &Subcommand,522                     const RefactoringRuleContext &Context) {523    llvm::outs() << "invoking action '" << Subcommand.getName() << "':\n";524    if (Context.getSelectionRange().isValid()) {525      SourceRange R = Context.getSelectionRange();526      llvm::outs() << "  -selection=";527      R.getBegin().print(llvm::outs(), Context.getSources());528      llvm::outs() << " -> ";529      R.getEnd().print(llvm::outs(), Context.getSources());530      llvm::outs() << "\n";531    }532  }533 534  llvm::Expected<RefactoringActionRule *>535  getMatchingRule(RefactoringActionSubcommand &Subcommand) {536    SmallVector<RefactoringActionRule *, 4> MatchingRules;537    llvm::StringSet<> MissingOptions;538 539    for (const auto &Rule : Subcommand.getActionRules()) {540      CommandLineRefactoringOptionVisitor Visitor(Subcommand.getOptions());541      Rule->visitRefactoringOptions(Visitor);542      if (Visitor.getMissingRequiredOptions().empty()) {543        if (!Rule->hasSelectionRequirement()) {544          MatchingRules.push_back(Rule.get());545        } else {546          Subcommand.parseSelectionArgument();547          if (Subcommand.getSelection()) {548            MatchingRules.push_back(Rule.get());549          } else {550            MissingOptions.insert("selection");551          }552        }553      }554      for (const RefactoringOption *Opt : Visitor.getMissingRequiredOptions())555        MissingOptions.insert(Opt->getName());556    }557    if (MatchingRules.empty()) {558      std::string Error;559      llvm::raw_string_ostream OS(Error);560      OS << "ERROR: '" << Subcommand.getName()561         << "' can't be invoked with the given arguments:\n";562      for (const auto &Opt : MissingOptions)563        OS << "  missing '-" << Opt.getKey() << "' option\n";564      return llvm::make_error<llvm::StringError>(565          Error, llvm::inconvertibleErrorCode());566    }567    if (MatchingRules.size() != 1) {568      return llvm::make_error<llvm::StringError>(569          llvm::Twine("ERROR: more than one matching rule of action") +570              Subcommand.getName() + "was found with given options.",571          llvm::inconvertibleErrorCode());572    }573    return MatchingRules.front();574  }575  // Figure out which action is specified by the user. The user must specify the576  // action using a command-line subcommand, e.g. the invocation `clang-refactor577  // local-rename` corresponds to the `LocalRename` refactoring action. All578  // subcommands must have a unique names. This allows us to figure out which579  // refactoring action should be invoked by looking at the first subcommand580  // that's enabled by LLVM's command-line parser.581  llvm::Expected<RefactoringActionSubcommand *> getSelectedSubcommand() {582    auto It = llvm::find_if(583        SubCommands,584        [](const std::unique_ptr<RefactoringActionSubcommand> &SubCommand) {585          return !!(*SubCommand);586        });587    if (It == SubCommands.end()) {588      std::string Error;589      llvm::raw_string_ostream OS(Error);590      OS << "error: no refactoring action given\n";591      OS << "note: the following actions are supported:\n";592      for (const auto &Subcommand : SubCommands)593        OS.indent(2) << Subcommand->getName() << "\n";594      return llvm::make_error<llvm::StringError>(595          Error, llvm::inconvertibleErrorCode());596    }597    RefactoringActionSubcommand *Subcommand = &(**It);598    return Subcommand;599  }600 601  std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands;602  RefactoringActionSubcommand *SelectedSubcommand;603  RefactoringActionRule *MatchingRule;604  std::unique_ptr<ClangRefactorToolConsumerInterface> Consumer;605  AtomicChanges Changes;606  bool HasFailed;607};608 609} // end anonymous namespace610 611int main(int argc, const char **argv) {612  llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);613 614  ClangRefactorTool RefactorTool;615 616  auto ExpectedParser = CommonOptionsParser::create(617      argc, argv, cl::getGeneralCategory(), cl::ZeroOrMore,618      "Clang-based refactoring tool for C, C++ and Objective-C");619  if (!ExpectedParser) {620    llvm::errs() << ExpectedParser.takeError();621    return 1;622  }623  CommonOptionsParser &Options = ExpectedParser.get();624 625  if (auto Err = RefactorTool.Init()) {626    llvm::errs() << llvm::toString(std::move(Err)) << "\n";627    return 1;628  }629 630  auto ActionFactory = RefactorTool.getFrontendActionFactory();631  if (!ActionFactory) {632    llvm::errs() << llvm::toString(ActionFactory.takeError()) << "\n";633    return 1;634  }635  ClangTool Tool(Options.getCompilations(), Options.getSourcePathList());636  bool Failed = false;637  if (Tool.run(ActionFactory->get()) != 0) {638    llvm::errs() << "Failed to run refactoring action on files\n";639    // It is possible that TUs are broken while changes are generated correctly,640    // so we still try applying changes.641    Failed = true;642  }643  return RefactorTool.applySourceChanges() || Failed ||644         RefactorTool.hasFailed();645}646