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