| //===--- Migrator.cpp -----------------------------------------------------===// |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "swift/Basic/Diff.h" |
| #include "swift/Frontend/Frontend.h" |
| #include "swift/Migrator/ASTMigratorPass.h" |
| #include "swift/Migrator/EditorAdapter.h" |
| #include "swift/Migrator/FixitApplyDiagnosticConsumer.h" |
| #include "swift/Migrator/Migrator.h" |
| #include "swift/Migrator/RewriteBufferEditsReceiver.h" |
| #include "clang/Basic/Diagnostic.h" |
| #include "clang/Basic/FileManager.h" |
| #include "clang/Basic/SourceManager.h" |
| #include "clang/Edit/EditedSource.h" |
| #include "clang/Rewrite/Core/RewriteBuffer.h" |
| #include "llvm/Support/FileSystem.h" |
| |
| using namespace swift; |
| using namespace swift::migrator; |
| |
| bool migrator::updateCodeAndEmitRemapIfNeeded( |
| CompilerInstance *Instance, const CompilerInvocation &Invocation) { |
| if (!Invocation.getMigratorOptions().shouldRunMigrator()) |
| return false; |
| |
| // Delete the remap file, in case someone is re-running the Migrator. If the |
| // file fails to compile and we don't get a chance to overwrite it, the old |
| // changes may get picked up. |
| llvm::sys::fs::remove(Invocation.getMigratorOptions().EmitRemapFilePath); |
| |
| Migrator M { Instance, Invocation }; // Provide inputs and configuration |
| |
| // Phase 1: Pre Fix-it passes |
| // These uses the initial frontend invocation to apply any obvious fix-its |
| // to see if we can get an error-free AST to get to Phase 2. |
| std::unique_ptr<swift::CompilerInstance> PreFixItInstance; |
| if (Instance->getASTContext().hadError()) { |
| PreFixItInstance = M.repeatFixitMigrations(2, |
| Invocation.getLangOptions().EffectiveLanguageVersion); |
| |
| // If we still couldn't fix all of the errors, give up. |
| if (PreFixItInstance == nullptr || |
| !PreFixItInstance->hasASTContext() || |
| PreFixItInstance->getASTContext().hadError()) { |
| return true; |
| } |
| M.StartInstance = PreFixItInstance.get(); |
| } |
| |
| // Phase 2: Syntactic Transformations |
| if (Invocation.getLangOptions().EffectiveLanguageVersion[0] < 4) { |
| auto FailedSyntacticPasses = M.performSyntacticPasses(); |
| if (FailedSyntacticPasses) { |
| return true; |
| } |
| } |
| |
| // Phase 3: Post Fix-it Passes |
| // Perform fix-it based migrations on the compiler, some number of times in |
| // order to give the compiler an opportunity to |
| // take its time reaching a fixed point. |
| // This is the end of the pipeline, so we throw away the compiler instance(s) |
| // we used in these fix-it runs. |
| |
| if (M.getMigratorOptions().EnableMigratorFixits) { |
| M.repeatFixitMigrations(Migrator::MaxCompilerFixitPassIterations, |
| {4, 0, 0}); |
| } |
| |
| // OK, we have a final resulting text. Now we compare against the input |
| // to calculate a replacement map describing the changes to the input |
| // necessary to get the output. |
| // TODO: Document replacement map format. |
| |
| |
| auto EmitRemapFailed = M.emitRemap(); |
| auto EmitMigratedFailed = M.emitMigratedFile(); |
| auto DumpMigrationStatesFailed = M.dumpStates(); |
| return EmitRemapFailed || EmitMigratedFailed || DumpMigrationStatesFailed; |
| } |
| |
| Migrator::Migrator(CompilerInstance *StartInstance, |
| const CompilerInvocation &StartInvocation) |
| : StartInstance(StartInstance), StartInvocation(StartInvocation) { |
| |
| auto ErrorOrStartBuffer = llvm::MemoryBuffer::getFile(getInputFilename()); |
| auto &StartBuffer = ErrorOrStartBuffer.get(); |
| auto StartBufferID = SrcMgr.addNewSourceBuffer(std::move(StartBuffer)); |
| States.push_back(MigrationState::start(SrcMgr, StartBufferID)); |
| } |
| |
| std::unique_ptr<swift::CompilerInstance> |
| Migrator::repeatFixitMigrations(const unsigned Iterations, |
| version::Version SwiftLanguageVersion) { |
| for (unsigned i = 0; i < Iterations; ++i) { |
| auto ThisInstance = performAFixItMigration(SwiftLanguageVersion); |
| if (ThisInstance == nullptr) { |
| break; |
| } else { |
| if (States.back()->noChangesOccurred()) { |
| return ThisInstance; |
| } |
| } |
| } |
| return nullptr; |
| } |
| |
| std::unique_ptr<swift::CompilerInstance> |
| Migrator::performAFixItMigration(version::Version SwiftLanguageVersion) { |
| auto InputState = States.back(); |
| auto InputText = InputState->getOutputText(); |
| auto InputBuffer = |
| llvm::MemoryBuffer::getMemBufferCopy(InputText, getInputFilename()); |
| |
| CompilerInvocation Invocation { StartInvocation }; |
| Invocation.getFrontendOptions().InputsAndOutputs.clearInputs(); |
| Invocation.getLangOptions().EffectiveLanguageVersion = SwiftLanguageVersion; |
| auto &LLVMArgs = Invocation.getFrontendOptions().LLVMArgs; |
| auto aarch64_use_tbi = std::find(LLVMArgs.begin(), LLVMArgs.end(), |
| "-aarch64-use-tbi"); |
| if (aarch64_use_tbi != LLVMArgs.end()) { |
| LLVMArgs.erase(aarch64_use_tbi); |
| } |
| |
| // SE-0160: When migrating, always use the Swift 3 @objc inference rules, |
| // which drives warnings with the "@objc" Fix-Its. |
| Invocation.getLangOptions().EnableSwift3ObjCInference = true; |
| |
| // The default behavior of the migrator, referred to as "minimal" migration |
| // in SE-0160, only adds @objc Fix-Its to those cases where the Objective-C |
| // entry point is explicitly used somewhere in the source code. The user |
| // may also select a workflow that adds @objc for every declaration that |
| // would infer @objc under the Swift 3 rules but would no longer infer |
| // @objc in Swift 4. |
| Invocation.getLangOptions().WarnSwift3ObjCInference = |
| getMigratorOptions().KeepObjcVisibility |
| ? Swift3ObjCInferenceWarnings::Complete |
| : Swift3ObjCInferenceWarnings::Minimal; |
| |
| const auto &OrigFrontendOpts = StartInvocation.getFrontendOptions(); |
| |
| assert(OrigFrontendOpts.InputsAndOutputs.hasPrimaryInputs() && |
| "Migration must have a primary"); |
| for (const auto &input : OrigFrontendOpts.InputsAndOutputs.getAllInputs()) { |
| Invocation.getFrontendOptions().InputsAndOutputs.addInput( |
| InputFile(input.file(), input.isPrimary(), |
| input.isPrimary() ? InputBuffer.get() : input.buffer())); |
| } |
| |
| auto Instance = llvm::make_unique<swift::CompilerInstance>(); |
| if (Instance->setup(Invocation)) { |
| return nullptr; |
| } |
| |
| FixitApplyDiagnosticConsumer FixitApplyConsumer { |
| InputText, |
| getInputFilename(), |
| }; |
| Instance->addDiagnosticConsumer(&FixitApplyConsumer); |
| |
| Instance->performSema(); |
| |
| StringRef ResultText = InputText; |
| unsigned ResultBufferID = InputState->getOutputBufferID(); |
| |
| if (FixitApplyConsumer.getNumFixitsApplied() > 0) { |
| SmallString<4096> Scratch; |
| llvm::raw_svector_ostream OS(Scratch); |
| FixitApplyConsumer.printResult(OS); |
| auto ResultBuffer = llvm::MemoryBuffer::getMemBufferCopy(OS.str()); |
| ResultText = ResultBuffer->getBuffer(); |
| ResultBufferID = SrcMgr.addNewSourceBuffer(std::move(ResultBuffer)); |
| } |
| |
| States.push_back(MigrationState::make(MigrationKind::CompilerFixits, |
| SrcMgr, InputState->getOutputBufferID(), |
| ResultBufferID)); |
| return Instance; |
| } |
| |
| bool Migrator::performSyntacticPasses() { |
| clang::FileSystemOptions ClangFileSystemOptions; |
| clang::FileManager ClangFileManager { ClangFileSystemOptions }; |
| |
| llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> DummyClangDiagIDs { |
| new clang::DiagnosticIDs() |
| }; |
| auto ClangDiags = |
| llvm::make_unique<clang::DiagnosticsEngine>(DummyClangDiagIDs, |
| new clang::DiagnosticOptions, |
| new clang::DiagnosticConsumer(), |
| /*ShouldOwnClient=*/true); |
| |
| clang::SourceManager ClangSourceManager { *ClangDiags, ClangFileManager }; |
| clang::LangOptions ClangLangOpts; |
| clang::edit::EditedSource Edits { ClangSourceManager, ClangLangOpts }; |
| |
| auto InputState = States.back(); |
| auto InputText = InputState->getOutputText(); |
| |
| EditorAdapter Editor { StartInstance->getSourceMgr(), ClangSourceManager }; |
| |
| runAPIDiffMigratorPass(Editor, StartInstance->getPrimarySourceFile(), |
| getMigratorOptions()); |
| runTupleSplatMigratorPass(Editor, StartInstance->getPrimarySourceFile(), |
| getMigratorOptions()); |
| runTypeOfMigratorPass(Editor, StartInstance->getPrimarySourceFile(), |
| getMigratorOptions()); |
| |
| Edits.commit(Editor.getEdits()); |
| |
| RewriteBufferEditsReceiver Rewriter { |
| ClangSourceManager, |
| Editor.getClangFileIDForSwiftBufferID( |
| StartInstance->getPrimarySourceFile()->getBufferID().getValue()), |
| InputState->getOutputText() |
| }; |
| |
| Edits.applyRewrites(Rewriter); |
| |
| SmallString<1024> Scratch; |
| llvm::raw_svector_ostream OS(Scratch); |
| Rewriter.printResult(OS); |
| auto ResultBuffer = this->SrcMgr.addMemBufferCopy(OS.str()); |
| |
| States.push_back( |
| MigrationState::make(MigrationKind::Syntactic, |
| this->SrcMgr, |
| States.back()->getInputBufferID(), |
| ResultBuffer)); |
| return false; |
| } |
| |
| namespace { |
| /// Print a replacement from a diff edit scriptto the given output stream. |
| /// |
| /// \param Filename The filename of the original file |
| /// \param Rep The Replacement to print |
| /// \param OS The output stream |
| void printReplacement(const StringRef Filename, |
| const Replacement &Rep, |
| llvm::raw_ostream &OS) { |
| assert(!Filename.empty()); |
| if (Rep.Remove == 0 && Rep.Text.empty()) { |
| return; |
| } |
| OS << " {\n"; |
| |
| OS << " \"file\": \""; |
| OS.write_escaped(Filename); |
| OS << "\",\n"; |
| |
| OS << " \"offset\": " << Rep.Offset; |
| if (Rep.Remove > 0) { |
| OS << ",\n"; |
| OS << " \"remove\": " << Rep.Remove; |
| } |
| if (!Rep.Text.empty()) { |
| OS << ",\n"; |
| OS << " \"text\": \""; |
| OS.write_escaped(Rep.Text); |
| OS << "\"\n"; |
| } else { |
| OS << "\n"; |
| } |
| OS << " }"; |
| } |
| |
| /// Print a remap file to the given output stream. |
| /// |
| /// \param OriginalFilename The filename of the file that was edited |
| /// not the output file for printing here. |
| /// \param InputText The input text without any changes. |
| /// \param OutputText The result text after any changes. |
| /// \param OS The output stream. |
| void printRemap(const StringRef OriginalFilename, |
| const StringRef InputText, |
| const StringRef OutputText, |
| llvm::raw_ostream &OS) { |
| assert(!OriginalFilename.empty()); |
| |
| diff_match_patch<std::string> DMP; |
| const auto Diffs = DMP.diff_main(InputText, OutputText, /*checkLines=*/false); |
| |
| OS << "["; |
| |
| size_t Offset = 0; |
| |
| llvm::SmallVector<Replacement, 32> Replacements; |
| |
| for (const auto &Diff : Diffs) { |
| size_t OffsetIncrement = 0; |
| switch (Diff.operation) { |
| case decltype(DMP)::EQUAL: |
| OffsetIncrement += Diff.text.size(); |
| break; |
| case decltype(DMP)::INSERT: |
| Replacements.push_back({ Offset, 0, Diff.text }); |
| break; |
| case decltype(DMP)::DELETE: |
| Replacements.push_back({ Offset, Diff.text.size(), "" }); |
| OffsetIncrement = Diff.text.size(); |
| break; |
| } |
| Offset += OffsetIncrement; |
| } |
| |
| assert(Offset == InputText.size()); |
| |
| // Combine removal edits with previous edits that are consecutive. |
| for (unsigned i = 1; i < Replacements.size();) { |
| auto &Previous = Replacements[i-1]; |
| auto &Current = Replacements[i]; |
| assert(Current.Offset >= Previous.Offset + Previous.Remove); |
| unsigned Distance = Current.Offset-(Previous.Offset + Previous.Remove); |
| if (Distance > 0) { |
| ++i; |
| continue; |
| } |
| if (!Current.Text.empty()) { |
| ++i; |
| continue; |
| } |
| Previous.Remove += Current.Remove; |
| Replacements.erase(Replacements.begin() + i); |
| } |
| |
| // Combine removal edits with next edits that are consecutive. |
| for (unsigned i = 0; i + 1 < Replacements.size();) { |
| auto &Current = Replacements[i]; |
| auto &nextRep = Replacements[i + 1]; |
| assert(nextRep.Offset >= Current.Offset + Current.Remove); |
| unsigned Distance = nextRep.Offset - (Current.Offset + Current.Remove); |
| if (Distance > 0) { |
| ++i; |
| continue; |
| } |
| if (!Current.Text.empty()) { |
| ++i; |
| continue; |
| } |
| nextRep.Offset -= Current.Remove; |
| nextRep.Remove += Current.Remove; |
| Replacements.erase(Replacements.begin() + i); |
| } |
| |
| // For remaining removal diffs, include the byte adjacent to the range on the |
| // left. libclang applies the diffs as byte diffs, so it doesn't matter if the |
| // byte is part of a multi-byte UTF8 character. |
| for (unsigned i = 0; i < Replacements.size(); ++i) { |
| auto &Current = Replacements[i]; |
| if (!Current.Text.empty()) |
| continue; |
| if (Current.Offset == 0) |
| continue; |
| Current.Offset -= 1; |
| Current.Remove += 1; |
| Current.Text = InputText.substr(Current.Offset, 1); |
| } |
| |
| for (auto Rep = Replacements.begin(); Rep != Replacements.end(); ++Rep) { |
| if (Rep != Replacements.begin()) { |
| OS << ",\n"; |
| } else { |
| OS << "\n"; |
| } |
| printReplacement(OriginalFilename, *Rep, OS); |
| } |
| |
| OS << "\n]"; |
| } |
| |
| } // end anonymous namespace |
| |
| bool Migrator::emitRemap() const { |
| const auto &RemapPath = getMigratorOptions().EmitRemapFilePath; |
| if (RemapPath.empty()) { |
| return false; |
| } |
| |
| std::error_code Error; |
| llvm::raw_fd_ostream FileOS(RemapPath, |
| Error, llvm::sys::fs::F_Text); |
| if (FileOS.has_error()) { |
| return true; |
| } |
| |
| auto InputText = States.front()->getOutputText(); |
| auto OutputText = States.back()->getOutputText(); |
| printRemap(getInputFilename(), InputText, OutputText, FileOS); |
| |
| FileOS.flush(); |
| return FileOS.has_error(); |
| } |
| |
| bool Migrator::emitMigratedFile() const { |
| const auto &OutFilename = getMigratorOptions().EmitMigratedFilePath; |
| if (OutFilename.empty()) { |
| return false; |
| } |
| |
| std::error_code Error; |
| llvm::raw_fd_ostream FileOS(OutFilename, |
| Error, llvm::sys::fs::F_Text); |
| if (FileOS.has_error()) { |
| return true; |
| } |
| |
| FileOS << States.back()->getOutputText(); |
| |
| FileOS.flush(); |
| |
| return FileOS.has_error(); |
| } |
| |
| bool Migrator::dumpStates() const { |
| const auto &OutDir = getMigratorOptions().DumpMigrationStatesDir; |
| if (OutDir.empty()) { |
| return false; |
| } |
| |
| auto Failed = false; |
| for (size_t i = 0; i < States.size(); ++i) { |
| Failed |= States[i]->print(i, OutDir); |
| } |
| |
| return Failed; |
| } |
| |
| const MigratorOptions &Migrator::getMigratorOptions() const { |
| return StartInvocation.getMigratorOptions(); |
| } |
| |
| const StringRef Migrator::getInputFilename() const { |
| auto &PrimaryInput = StartInvocation.getFrontendOptions() |
| .InputsAndOutputs.getRequiredUniquePrimaryInput(); |
| return PrimaryInput.file(); |
| } |