//===--- ARCMT.cpp - Migration to ARC mode --------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/ARCMigrate/ARCMT.h" #include "Internals.h" #include "clang/AST/ASTConsumer.h" #include "clang/Basic/DiagnosticCategories.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/Utils.h" #include "clang/Lex/Preprocessor.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Sema/SemaDiagnostic.h" #include "clang/Serialization/ASTReader.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/TargetParser/Triple.h" #include using namespace clang; using namespace arcmt; bool CapturedDiagList::clearDiagnostic(ArrayRef IDs, SourceRange range) { if (range.isInvalid()) return false; bool cleared = false; ListTy::iterator I = List.begin(); while (I != List.end()) { FullSourceLoc diagLoc = I->getLocation(); if ((IDs.empty() || // empty means clear all diagnostics in the range. llvm::is_contained(IDs, I->getID())) && !diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) && (diagLoc == range.getEnd() || diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) { cleared = true; ListTy::iterator eraseS = I++; if (eraseS->getLevel() != DiagnosticsEngine::Note) while (I != List.end() && I->getLevel() == DiagnosticsEngine::Note) ++I; // Clear the diagnostic and any notes following it. I = List.erase(eraseS, I); continue; } ++I; } return cleared; } bool CapturedDiagList::hasDiagnostic(ArrayRef IDs, SourceRange range) const { if (range.isInvalid()) return false; ListTy::const_iterator I = List.begin(); while (I != List.end()) { FullSourceLoc diagLoc = I->getLocation(); if ((IDs.empty() || // empty means any diagnostic in the range. llvm::is_contained(IDs, I->getID())) && !diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) && (diagLoc == range.getEnd() || diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) { return true; } ++I; } return false; } void CapturedDiagList::reportDiagnostics(DiagnosticsEngine &Diags) const { for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I) Diags.Report(*I); } bool CapturedDiagList::hasErrors() const { for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I) if (I->getLevel() >= DiagnosticsEngine::Error) return true; return false; } namespace { class CaptureDiagnosticConsumer : public DiagnosticConsumer { DiagnosticsEngine &Diags; DiagnosticConsumer &DiagClient; CapturedDiagList &CapturedDiags; bool HasBegunSourceFile; public: CaptureDiagnosticConsumer(DiagnosticsEngine &diags, DiagnosticConsumer &client, CapturedDiagList &capturedDiags) : Diags(diags), DiagClient(client), CapturedDiags(capturedDiags), HasBegunSourceFile(false) { } void BeginSourceFile(const LangOptions &Opts, const Preprocessor *PP) override { // Pass BeginSourceFile message onto DiagClient on first call. // The corresponding EndSourceFile call will be made from an // explicit call to FinishCapture. if (!HasBegunSourceFile) { DiagClient.BeginSourceFile(Opts, PP); HasBegunSourceFile = true; } } void FinishCapture() { // Call EndSourceFile on DiagClient on completion of capture to // enable VerifyDiagnosticConsumer to check diagnostics *after* // it has received the diagnostic list. if (HasBegunSourceFile) { DiagClient.EndSourceFile(); HasBegunSourceFile = false; } } ~CaptureDiagnosticConsumer() override { assert(!HasBegunSourceFile && "FinishCapture not called!"); } void HandleDiagnostic(DiagnosticsEngine::Level level, const Diagnostic &Info) override { if (DiagnosticIDs::isARCDiagnostic(Info.getID()) || level >= DiagnosticsEngine::Error || level == DiagnosticsEngine::Note) { if (Info.getLocation().isValid()) CapturedDiags.push_back(StoredDiagnostic(level, Info)); return; } // Non-ARC warnings are ignored. Diags.setLastDiagnosticIgnored(true); } }; } // end anonymous namespace static bool HasARCRuntime(CompilerInvocation &origCI) { // This duplicates some functionality from Darwin::AddDeploymentTarget // but this function is well defined, so keep it decoupled from the driver // and avoid unrelated complications. llvm::Triple triple(origCI.getTargetOpts().Triple); if (triple.isiOS()) return triple.getOSMajorVersion() >= 5; if (triple.isWatchOS()) return true; if (triple.getOS() == llvm::Triple::Darwin) return triple.getOSMajorVersion() >= 11; if (triple.getOS() == llvm::Triple::MacOSX) { return triple.getOSVersion() >= VersionTuple(10, 7); } return false; } static CompilerInvocation * createInvocationForMigration(CompilerInvocation &origCI, const PCHContainerReader &PCHContainerRdr) { std::unique_ptr CInvok; CInvok.reset(new CompilerInvocation(origCI)); PreprocessorOptions &PPOpts = CInvok->getPreprocessorOpts(); if (!PPOpts.ImplicitPCHInclude.empty()) { // We can't use a PCH because it was likely built in non-ARC mode and we // want to parse in ARC. Include the original header. FileManager FileMgr(origCI.getFileSystemOpts()); IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); IntrusiveRefCntPtr Diags( new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(), new IgnoringDiagConsumer())); std::string OriginalFile = ASTReader::getOriginalSourceFile( PPOpts.ImplicitPCHInclude, FileMgr, PCHContainerRdr, *Diags); if (!OriginalFile.empty()) PPOpts.Includes.insert(PPOpts.Includes.begin(), OriginalFile); PPOpts.ImplicitPCHInclude.clear(); } std::string define = std::string(getARCMTMacroName()); define += '='; CInvok->getPreprocessorOpts().addMacroDef(define); CInvok->getLangOpts().ObjCAutoRefCount = true; CInvok->getLangOpts().setGC(LangOptions::NonGC); CInvok->getDiagnosticOpts().ErrorLimit = 0; CInvok->getDiagnosticOpts().PedanticErrors = 0; // Ignore -Werror flags when migrating. std::vector WarnOpts; for (std::vector::iterator I = CInvok->getDiagnosticOpts().Warnings.begin(), E = CInvok->getDiagnosticOpts().Warnings.end(); I != E; ++I) { if (!StringRef(*I).starts_with("error")) WarnOpts.push_back(*I); } WarnOpts.push_back("error=arc-unsafe-retained-assign"); CInvok->getDiagnosticOpts().Warnings = std::move(WarnOpts); CInvok->getLangOpts().ObjCWeakRuntime = HasARCRuntime(origCI); CInvok->getLangOpts().ObjCWeak = CInvok->getLangOpts().ObjCWeakRuntime; return CInvok.release(); } static void emitPremigrationErrors(const CapturedDiagList &arcDiags, DiagnosticOptions *diagOpts, Preprocessor &PP) { TextDiagnosticPrinter printer(llvm::errs(), diagOpts); IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); IntrusiveRefCntPtr Diags( new DiagnosticsEngine(DiagID, diagOpts, &printer, /*ShouldOwnClient=*/false)); Diags->setSourceManager(&PP.getSourceManager()); printer.BeginSourceFile(PP.getLangOpts(), &PP); arcDiags.reportDiagnostics(*Diags); printer.EndSourceFile(); } //===----------------------------------------------------------------------===// // checkForManualIssues. //===----------------------------------------------------------------------===// bool arcmt::checkForManualIssues( CompilerInvocation &origCI, const FrontendInputFile &Input, std::shared_ptr PCHContainerOps, DiagnosticConsumer *DiagClient, bool emitPremigrationARCErrors, StringRef plistOut) { if (!origCI.getLangOpts().ObjC) return false; LangOptions::GCMode OrigGCMode = origCI.getLangOpts().getGC(); bool NoNSAllocReallocError = origCI.getMigratorOpts().NoNSAllocReallocError; bool NoFinalizeRemoval = origCI.getMigratorOpts().NoFinalizeRemoval; std::vector transforms = arcmt::getAllTransformations(OrigGCMode, NoFinalizeRemoval); assert(!transforms.empty()); std::unique_ptr CInvok; CInvok.reset( createInvocationForMigration(origCI, PCHContainerOps->getRawReader())); CInvok->getFrontendOpts().Inputs.clear(); CInvok->getFrontendOpts().Inputs.push_back(Input); CapturedDiagList capturedDiags; assert(DiagClient); IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); IntrusiveRefCntPtr Diags( new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(), DiagClient, /*ShouldOwnClient=*/false)); // Filter of all diagnostics. CaptureDiagnosticConsumer errRec(*Diags, *DiagClient, capturedDiags); Diags->setClient(&errRec, /*ShouldOwnClient=*/false); std::unique_ptr Unit(ASTUnit::LoadFromCompilerInvocationAction( std::move(CInvok), PCHContainerOps, Diags)); if (!Unit) { errRec.FinishCapture(); return true; } // Don't filter diagnostics anymore. Diags->setClient(DiagClient, /*ShouldOwnClient=*/false); ASTContext &Ctx = Unit->getASTContext(); if (Diags->hasFatalErrorOccurred()) { Diags->Reset(); DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor()); capturedDiags.reportDiagnostics(*Diags); DiagClient->EndSourceFile(); errRec.FinishCapture(); return true; } if (emitPremigrationARCErrors) emitPremigrationErrors(capturedDiags, &origCI.getDiagnosticOpts(), Unit->getPreprocessor()); if (!plistOut.empty()) { SmallVector arcDiags; for (CapturedDiagList::iterator I = capturedDiags.begin(), E = capturedDiags.end(); I != E; ++I) arcDiags.push_back(*I); writeARCDiagsToPlist(std::string(plistOut), arcDiags, Ctx.getSourceManager(), Ctx.getLangOpts()); } // After parsing of source files ended, we want to reuse the // diagnostics objects to emit further diagnostics. // We call BeginSourceFile because DiagnosticConsumer requires that // diagnostics with source range information are emitted only in between // BeginSourceFile() and EndSourceFile(). DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor()); // No macros will be added since we are just checking and we won't modify // source code. std::vector ARCMTMacroLocs; TransformActions testAct(*Diags, capturedDiags, Ctx, Unit->getPreprocessor()); MigrationPass pass(Ctx, OrigGCMode, Unit->getSema(), testAct, capturedDiags, ARCMTMacroLocs); pass.setNoFinalizeRemoval(NoFinalizeRemoval); if (!NoNSAllocReallocError) Diags->setSeverity(diag::warn_arcmt_nsalloc_realloc, diag::Severity::Error, SourceLocation()); for (unsigned i=0, e = transforms.size(); i != e; ++i) transforms[i](pass); capturedDiags.reportDiagnostics(*Diags); DiagClient->EndSourceFile(); errRec.FinishCapture(); return capturedDiags.hasErrors() || testAct.hasReportedErrors(); } //===----------------------------------------------------------------------===// // applyTransformations. //===----------------------------------------------------------------------===// static bool applyTransforms(CompilerInvocation &origCI, const FrontendInputFile &Input, std::shared_ptr PCHContainerOps, DiagnosticConsumer *DiagClient, StringRef outputDir, bool emitPremigrationARCErrors, StringRef plistOut) { if (!origCI.getLangOpts().ObjC) return false; LangOptions::GCMode OrigGCMode = origCI.getLangOpts().getGC(); // Make sure checking is successful first. CompilerInvocation CInvokForCheck(origCI); if (arcmt::checkForManualIssues(CInvokForCheck, Input, PCHContainerOps, DiagClient, emitPremigrationARCErrors, plistOut)) return true; CompilerInvocation CInvok(origCI); CInvok.getFrontendOpts().Inputs.clear(); CInvok.getFrontendOpts().Inputs.push_back(Input); MigrationProcess migration(CInvok, PCHContainerOps, DiagClient, outputDir); bool NoFinalizeRemoval = origCI.getMigratorOpts().NoFinalizeRemoval; std::vector transforms = arcmt::getAllTransformations(OrigGCMode, NoFinalizeRemoval); assert(!transforms.empty()); for (unsigned i=0, e = transforms.size(); i != e; ++i) { bool err = migration.applyTransform(transforms[i]); if (err) return true; } IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); IntrusiveRefCntPtr Diags( new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(), DiagClient, /*ShouldOwnClient=*/false)); if (outputDir.empty()) { origCI.getLangOpts().ObjCAutoRefCount = true; return migration.getRemapper().overwriteOriginal(*Diags); } else { return migration.getRemapper().flushToDisk(outputDir, *Diags); } } bool arcmt::applyTransformations( CompilerInvocation &origCI, const FrontendInputFile &Input, std::shared_ptr PCHContainerOps, DiagnosticConsumer *DiagClient) { return applyTransforms(origCI, Input, PCHContainerOps, DiagClient, StringRef(), false, StringRef()); } bool arcmt::migrateWithTemporaryFiles( CompilerInvocation &origCI, const FrontendInputFile &Input, std::shared_ptr PCHContainerOps, DiagnosticConsumer *DiagClient, StringRef outputDir, bool emitPremigrationARCErrors, StringRef plistOut) { assert(!outputDir.empty() && "Expected output directory path"); return applyTransforms(origCI, Input, PCHContainerOps, DiagClient, outputDir, emitPremigrationARCErrors, plistOut); } bool arcmt::getFileRemappings(std::vector > & remap, StringRef outputDir, DiagnosticConsumer *DiagClient) { assert(!outputDir.empty()); IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); IntrusiveRefCntPtr Diags( new DiagnosticsEngine(DiagID, new DiagnosticOptions, DiagClient, /*ShouldOwnClient=*/false)); FileRemapper remapper; bool err = remapper.initFromDisk(outputDir, *Diags, /*ignoreIfFilesChanged=*/true); if (err) return true; remapper.forEachMapping( [&](StringRef From, StringRef To) { remap.push_back(std::make_pair(From.str(), To.str())); }, [](StringRef, const llvm::MemoryBufferRef &) {}); return false; } //===----------------------------------------------------------------------===// // CollectTransformActions. //===----------------------------------------------------------------------===// namespace { class ARCMTMacroTrackerPPCallbacks : public PPCallbacks { std::vector &ARCMTMacroLocs; public: ARCMTMacroTrackerPPCallbacks(std::vector &ARCMTMacroLocs) : ARCMTMacroLocs(ARCMTMacroLocs) { } void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, SourceRange Range, const MacroArgs *Args) override { if (MacroNameTok.getIdentifierInfo()->getName() == getARCMTMacroName()) ARCMTMacroLocs.push_back(MacroNameTok.getLocation()); } }; class ARCMTMacroTrackerAction : public ASTFrontendAction { std::vector &ARCMTMacroLocs; public: ARCMTMacroTrackerAction(std::vector &ARCMTMacroLocs) : ARCMTMacroLocs(ARCMTMacroLocs) { } std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { CI.getPreprocessor().addPPCallbacks( std::make_unique(ARCMTMacroLocs)); return std::make_unique(); } }; class RewritesApplicator : public TransformActions::RewriteReceiver { Rewriter &rewriter; MigrationProcess::RewriteListener *Listener; public: RewritesApplicator(Rewriter &rewriter, ASTContext &ctx, MigrationProcess::RewriteListener *listener) : rewriter(rewriter), Listener(listener) { if (Listener) Listener->start(ctx); } ~RewritesApplicator() override { if (Listener) Listener->finish(); } void insert(SourceLocation loc, StringRef text) override { bool err = rewriter.InsertText(loc, text, /*InsertAfter=*/true, /*indentNewLines=*/true); if (!err && Listener) Listener->insert(loc, text); } void remove(CharSourceRange range) override { Rewriter::RewriteOptions removeOpts; removeOpts.IncludeInsertsAtBeginOfRange = false; removeOpts.IncludeInsertsAtEndOfRange = false; removeOpts.RemoveLineIfEmpty = true; bool err = rewriter.RemoveText(range, removeOpts); if (!err && Listener) Listener->remove(range); } void increaseIndentation(CharSourceRange range, SourceLocation parentIndent) override { rewriter.IncreaseIndentation(range, parentIndent); } }; } // end anonymous namespace. /// Anchor for VTable. MigrationProcess::RewriteListener::~RewriteListener() { } MigrationProcess::MigrationProcess( CompilerInvocation &CI, std::shared_ptr PCHContainerOps, DiagnosticConsumer *diagClient, StringRef outputDir) : OrigCI(CI), PCHContainerOps(std::move(PCHContainerOps)), DiagClient(diagClient), HadARCErrors(false) { if (!outputDir.empty()) { IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); IntrusiveRefCntPtr Diags( new DiagnosticsEngine(DiagID, &CI.getDiagnosticOpts(), DiagClient, /*ShouldOwnClient=*/false)); Remapper.initFromDisk(outputDir, *Diags, /*ignoreIfFilesChanged=*/true); } } bool MigrationProcess::applyTransform(TransformFn trans, RewriteListener *listener) { std::unique_ptr CInvok; CInvok.reset( createInvocationForMigration(OrigCI, PCHContainerOps->getRawReader())); CInvok->getDiagnosticOpts().IgnoreWarnings = true; Remapper.applyMappings(CInvok->getPreprocessorOpts()); CapturedDiagList capturedDiags; std::vector ARCMTMacroLocs; assert(DiagClient); IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); IntrusiveRefCntPtr Diags( new DiagnosticsEngine(DiagID, new DiagnosticOptions, DiagClient, /*ShouldOwnClient=*/false)); // Filter of all diagnostics. CaptureDiagnosticConsumer errRec(*Diags, *DiagClient, capturedDiags); Diags->setClient(&errRec, /*ShouldOwnClient=*/false); std::unique_ptr ASTAction; ASTAction.reset(new ARCMTMacroTrackerAction(ARCMTMacroLocs)); std::unique_ptr Unit(ASTUnit::LoadFromCompilerInvocationAction( std::move(CInvok), PCHContainerOps, Diags, ASTAction.get())); if (!Unit) { errRec.FinishCapture(); return true; } Unit->setOwnsRemappedFileBuffers(false); // FileRemapper manages that. HadARCErrors = HadARCErrors || capturedDiags.hasErrors(); // Don't filter diagnostics anymore. Diags->setClient(DiagClient, /*ShouldOwnClient=*/false); ASTContext &Ctx = Unit->getASTContext(); if (Diags->hasFatalErrorOccurred()) { Diags->Reset(); DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor()); capturedDiags.reportDiagnostics(*Diags); DiagClient->EndSourceFile(); errRec.FinishCapture(); return true; } // After parsing of source files ended, we want to reuse the // diagnostics objects to emit further diagnostics. // We call BeginSourceFile because DiagnosticConsumer requires that // diagnostics with source range information are emitted only in between // BeginSourceFile() and EndSourceFile(). DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor()); Rewriter rewriter(Ctx.getSourceManager(), Ctx.getLangOpts()); TransformActions TA(*Diags, capturedDiags, Ctx, Unit->getPreprocessor()); MigrationPass pass(Ctx, OrigCI.getLangOpts().getGC(), Unit->getSema(), TA, capturedDiags, ARCMTMacroLocs); trans(pass); { RewritesApplicator applicator(rewriter, Ctx, listener); TA.applyRewrites(applicator); } DiagClient->EndSourceFile(); errRec.FinishCapture(); if (DiagClient->getNumErrors()) return true; for (Rewriter::buffer_iterator I = rewriter.buffer_begin(), E = rewriter.buffer_end(); I != E; ++I) { FileID FID = I->first; RewriteBuffer &buf = I->second; OptionalFileEntryRef file = Ctx.getSourceManager().getFileEntryRefForID(FID); assert(file); std::string newFname = std::string(file->getName()); newFname += "-trans"; SmallString<512> newText; llvm::raw_svector_ostream vecOS(newText); buf.write(vecOS); std::unique_ptr memBuf( llvm::MemoryBuffer::getMemBufferCopy(newText.str(), newFname)); SmallString<64> filePath(file->getName()); Unit->getFileManager().FixupRelativePath(filePath); Remapper.remap(filePath.str(), std::move(memBuf)); } return false; }