Path: blob/main/contrib/llvm-project/clang/lib/ARCMigrate/ARCMT.cpp
35236 views
//===--- ARCMT.cpp - Migration to ARC mode --------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/ARCMigrate/ARCMT.h"9#include "Internals.h"10#include "clang/AST/ASTConsumer.h"11#include "clang/Basic/DiagnosticCategories.h"12#include "clang/Frontend/ASTUnit.h"13#include "clang/Frontend/CompilerInstance.h"14#include "clang/Frontend/FrontendAction.h"15#include "clang/Frontend/TextDiagnosticPrinter.h"16#include "clang/Frontend/Utils.h"17#include "clang/Lex/Preprocessor.h"18#include "clang/Lex/PreprocessorOptions.h"19#include "clang/Rewrite/Core/Rewriter.h"20#include "clang/Sema/SemaDiagnostic.h"21#include "clang/Serialization/ASTReader.h"22#include "llvm/Support/MemoryBuffer.h"23#include "llvm/TargetParser/Triple.h"24#include <utility>25using namespace clang;26using namespace arcmt;2728bool CapturedDiagList::clearDiagnostic(ArrayRef<unsigned> IDs,29SourceRange range) {30if (range.isInvalid())31return false;3233bool cleared = false;34ListTy::iterator I = List.begin();35while (I != List.end()) {36FullSourceLoc diagLoc = I->getLocation();37if ((IDs.empty() || // empty means clear all diagnostics in the range.38llvm::is_contained(IDs, I->getID())) &&39!diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) &&40(diagLoc == range.getEnd() ||41diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) {42cleared = true;43ListTy::iterator eraseS = I++;44if (eraseS->getLevel() != DiagnosticsEngine::Note)45while (I != List.end() && I->getLevel() == DiagnosticsEngine::Note)46++I;47// Clear the diagnostic and any notes following it.48I = List.erase(eraseS, I);49continue;50}5152++I;53}5455return cleared;56}5758bool CapturedDiagList::hasDiagnostic(ArrayRef<unsigned> IDs,59SourceRange range) const {60if (range.isInvalid())61return false;6263ListTy::const_iterator I = List.begin();64while (I != List.end()) {65FullSourceLoc diagLoc = I->getLocation();66if ((IDs.empty() || // empty means any diagnostic in the range.67llvm::is_contained(IDs, I->getID())) &&68!diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) &&69(diagLoc == range.getEnd() ||70diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) {71return true;72}7374++I;75}7677return false;78}7980void CapturedDiagList::reportDiagnostics(DiagnosticsEngine &Diags) const {81for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I)82Diags.Report(*I);83}8485bool CapturedDiagList::hasErrors() const {86for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I)87if (I->getLevel() >= DiagnosticsEngine::Error)88return true;8990return false;91}9293namespace {9495class CaptureDiagnosticConsumer : public DiagnosticConsumer {96DiagnosticsEngine &Diags;97DiagnosticConsumer &DiagClient;98CapturedDiagList &CapturedDiags;99bool HasBegunSourceFile;100public:101CaptureDiagnosticConsumer(DiagnosticsEngine &diags,102DiagnosticConsumer &client,103CapturedDiagList &capturedDiags)104: Diags(diags), DiagClient(client), CapturedDiags(capturedDiags),105HasBegunSourceFile(false) { }106107void BeginSourceFile(const LangOptions &Opts,108const Preprocessor *PP) override {109// Pass BeginSourceFile message onto DiagClient on first call.110// The corresponding EndSourceFile call will be made from an111// explicit call to FinishCapture.112if (!HasBegunSourceFile) {113DiagClient.BeginSourceFile(Opts, PP);114HasBegunSourceFile = true;115}116}117118void FinishCapture() {119// Call EndSourceFile on DiagClient on completion of capture to120// enable VerifyDiagnosticConsumer to check diagnostics *after*121// it has received the diagnostic list.122if (HasBegunSourceFile) {123DiagClient.EndSourceFile();124HasBegunSourceFile = false;125}126}127128~CaptureDiagnosticConsumer() override {129assert(!HasBegunSourceFile && "FinishCapture not called!");130}131132void HandleDiagnostic(DiagnosticsEngine::Level level,133const Diagnostic &Info) override {134if (DiagnosticIDs::isARCDiagnostic(Info.getID()) ||135level >= DiagnosticsEngine::Error || level == DiagnosticsEngine::Note) {136if (Info.getLocation().isValid())137CapturedDiags.push_back(StoredDiagnostic(level, Info));138return;139}140141// Non-ARC warnings are ignored.142Diags.setLastDiagnosticIgnored(true);143}144};145146} // end anonymous namespace147148static bool HasARCRuntime(CompilerInvocation &origCI) {149// This duplicates some functionality from Darwin::AddDeploymentTarget150// but this function is well defined, so keep it decoupled from the driver151// and avoid unrelated complications.152llvm::Triple triple(origCI.getTargetOpts().Triple);153154if (triple.isiOS())155return triple.getOSMajorVersion() >= 5;156157if (triple.isWatchOS())158return true;159160if (triple.getOS() == llvm::Triple::Darwin)161return triple.getOSMajorVersion() >= 11;162163if (triple.getOS() == llvm::Triple::MacOSX) {164return triple.getOSVersion() >= VersionTuple(10, 7);165}166167return false;168}169170static CompilerInvocation *171createInvocationForMigration(CompilerInvocation &origCI,172const PCHContainerReader &PCHContainerRdr) {173std::unique_ptr<CompilerInvocation> CInvok;174CInvok.reset(new CompilerInvocation(origCI));175PreprocessorOptions &PPOpts = CInvok->getPreprocessorOpts();176if (!PPOpts.ImplicitPCHInclude.empty()) {177// We can't use a PCH because it was likely built in non-ARC mode and we178// want to parse in ARC. Include the original header.179FileManager FileMgr(origCI.getFileSystemOpts());180IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());181IntrusiveRefCntPtr<DiagnosticsEngine> Diags(182new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(),183new IgnoringDiagConsumer()));184std::string OriginalFile = ASTReader::getOriginalSourceFile(185PPOpts.ImplicitPCHInclude, FileMgr, PCHContainerRdr, *Diags);186if (!OriginalFile.empty())187PPOpts.Includes.insert(PPOpts.Includes.begin(), OriginalFile);188PPOpts.ImplicitPCHInclude.clear();189}190std::string define = std::string(getARCMTMacroName());191define += '=';192CInvok->getPreprocessorOpts().addMacroDef(define);193CInvok->getLangOpts().ObjCAutoRefCount = true;194CInvok->getLangOpts().setGC(LangOptions::NonGC);195CInvok->getDiagnosticOpts().ErrorLimit = 0;196CInvok->getDiagnosticOpts().PedanticErrors = 0;197198// Ignore -Werror flags when migrating.199std::vector<std::string> WarnOpts;200for (std::vector<std::string>::iterator201I = CInvok->getDiagnosticOpts().Warnings.begin(),202E = CInvok->getDiagnosticOpts().Warnings.end(); I != E; ++I) {203if (!StringRef(*I).starts_with("error"))204WarnOpts.push_back(*I);205}206WarnOpts.push_back("error=arc-unsafe-retained-assign");207CInvok->getDiagnosticOpts().Warnings = std::move(WarnOpts);208209CInvok->getLangOpts().ObjCWeakRuntime = HasARCRuntime(origCI);210CInvok->getLangOpts().ObjCWeak = CInvok->getLangOpts().ObjCWeakRuntime;211212return CInvok.release();213}214215static void emitPremigrationErrors(const CapturedDiagList &arcDiags,216DiagnosticOptions *diagOpts,217Preprocessor &PP) {218TextDiagnosticPrinter printer(llvm::errs(), diagOpts);219IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());220IntrusiveRefCntPtr<DiagnosticsEngine> Diags(221new DiagnosticsEngine(DiagID, diagOpts, &printer,222/*ShouldOwnClient=*/false));223Diags->setSourceManager(&PP.getSourceManager());224225printer.BeginSourceFile(PP.getLangOpts(), &PP);226arcDiags.reportDiagnostics(*Diags);227printer.EndSourceFile();228}229230//===----------------------------------------------------------------------===//231// checkForManualIssues.232//===----------------------------------------------------------------------===//233234bool arcmt::checkForManualIssues(235CompilerInvocation &origCI, const FrontendInputFile &Input,236std::shared_ptr<PCHContainerOperations> PCHContainerOps,237DiagnosticConsumer *DiagClient, bool emitPremigrationARCErrors,238StringRef plistOut) {239if (!origCI.getLangOpts().ObjC)240return false;241242LangOptions::GCMode OrigGCMode = origCI.getLangOpts().getGC();243bool NoNSAllocReallocError = origCI.getMigratorOpts().NoNSAllocReallocError;244bool NoFinalizeRemoval = origCI.getMigratorOpts().NoFinalizeRemoval;245246std::vector<TransformFn> transforms = arcmt::getAllTransformations(OrigGCMode,247NoFinalizeRemoval);248assert(!transforms.empty());249250std::unique_ptr<CompilerInvocation> CInvok;251CInvok.reset(252createInvocationForMigration(origCI, PCHContainerOps->getRawReader()));253CInvok->getFrontendOpts().Inputs.clear();254CInvok->getFrontendOpts().Inputs.push_back(Input);255256CapturedDiagList capturedDiags;257258assert(DiagClient);259IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());260IntrusiveRefCntPtr<DiagnosticsEngine> Diags(261new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(),262DiagClient, /*ShouldOwnClient=*/false));263264// Filter of all diagnostics.265CaptureDiagnosticConsumer errRec(*Diags, *DiagClient, capturedDiags);266Diags->setClient(&errRec, /*ShouldOwnClient=*/false);267268std::unique_ptr<ASTUnit> Unit(ASTUnit::LoadFromCompilerInvocationAction(269std::move(CInvok), PCHContainerOps, Diags));270if (!Unit) {271errRec.FinishCapture();272return true;273}274275// Don't filter diagnostics anymore.276Diags->setClient(DiagClient, /*ShouldOwnClient=*/false);277278ASTContext &Ctx = Unit->getASTContext();279280if (Diags->hasFatalErrorOccurred()) {281Diags->Reset();282DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());283capturedDiags.reportDiagnostics(*Diags);284DiagClient->EndSourceFile();285errRec.FinishCapture();286return true;287}288289if (emitPremigrationARCErrors)290emitPremigrationErrors(capturedDiags, &origCI.getDiagnosticOpts(),291Unit->getPreprocessor());292if (!plistOut.empty()) {293SmallVector<StoredDiagnostic, 8> arcDiags;294for (CapturedDiagList::iterator295I = capturedDiags.begin(), E = capturedDiags.end(); I != E; ++I)296arcDiags.push_back(*I);297writeARCDiagsToPlist(std::string(plistOut), arcDiags,298Ctx.getSourceManager(), Ctx.getLangOpts());299}300301// After parsing of source files ended, we want to reuse the302// diagnostics objects to emit further diagnostics.303// We call BeginSourceFile because DiagnosticConsumer requires that304// diagnostics with source range information are emitted only in between305// BeginSourceFile() and EndSourceFile().306DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());307308// No macros will be added since we are just checking and we won't modify309// source code.310std::vector<SourceLocation> ARCMTMacroLocs;311312TransformActions testAct(*Diags, capturedDiags, Ctx, Unit->getPreprocessor());313MigrationPass pass(Ctx, OrigGCMode, Unit->getSema(), testAct, capturedDiags,314ARCMTMacroLocs);315pass.setNoFinalizeRemoval(NoFinalizeRemoval);316if (!NoNSAllocReallocError)317Diags->setSeverity(diag::warn_arcmt_nsalloc_realloc, diag::Severity::Error,318SourceLocation());319320for (unsigned i=0, e = transforms.size(); i != e; ++i)321transforms[i](pass);322323capturedDiags.reportDiagnostics(*Diags);324325DiagClient->EndSourceFile();326errRec.FinishCapture();327328return capturedDiags.hasErrors() || testAct.hasReportedErrors();329}330331//===----------------------------------------------------------------------===//332// applyTransformations.333//===----------------------------------------------------------------------===//334335static bool336applyTransforms(CompilerInvocation &origCI, const FrontendInputFile &Input,337std::shared_ptr<PCHContainerOperations> PCHContainerOps,338DiagnosticConsumer *DiagClient, StringRef outputDir,339bool emitPremigrationARCErrors, StringRef plistOut) {340if (!origCI.getLangOpts().ObjC)341return false;342343LangOptions::GCMode OrigGCMode = origCI.getLangOpts().getGC();344345// Make sure checking is successful first.346CompilerInvocation CInvokForCheck(origCI);347if (arcmt::checkForManualIssues(CInvokForCheck, Input, PCHContainerOps,348DiagClient, emitPremigrationARCErrors,349plistOut))350return true;351352CompilerInvocation CInvok(origCI);353CInvok.getFrontendOpts().Inputs.clear();354CInvok.getFrontendOpts().Inputs.push_back(Input);355356MigrationProcess migration(CInvok, PCHContainerOps, DiagClient, outputDir);357bool NoFinalizeRemoval = origCI.getMigratorOpts().NoFinalizeRemoval;358359std::vector<TransformFn> transforms = arcmt::getAllTransformations(OrigGCMode,360NoFinalizeRemoval);361assert(!transforms.empty());362363for (unsigned i=0, e = transforms.size(); i != e; ++i) {364bool err = migration.applyTransform(transforms[i]);365if (err) return true;366}367368IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());369IntrusiveRefCntPtr<DiagnosticsEngine> Diags(370new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(),371DiagClient, /*ShouldOwnClient=*/false));372373if (outputDir.empty()) {374origCI.getLangOpts().ObjCAutoRefCount = true;375return migration.getRemapper().overwriteOriginal(*Diags);376} else {377return migration.getRemapper().flushToDisk(outputDir, *Diags);378}379}380381bool arcmt::applyTransformations(382CompilerInvocation &origCI, const FrontendInputFile &Input,383std::shared_ptr<PCHContainerOperations> PCHContainerOps,384DiagnosticConsumer *DiagClient) {385return applyTransforms(origCI, Input, PCHContainerOps, DiagClient,386StringRef(), false, StringRef());387}388389bool arcmt::migrateWithTemporaryFiles(390CompilerInvocation &origCI, const FrontendInputFile &Input,391std::shared_ptr<PCHContainerOperations> PCHContainerOps,392DiagnosticConsumer *DiagClient, StringRef outputDir,393bool emitPremigrationARCErrors, StringRef plistOut) {394assert(!outputDir.empty() && "Expected output directory path");395return applyTransforms(origCI, Input, PCHContainerOps, DiagClient, outputDir,396emitPremigrationARCErrors, plistOut);397}398399bool arcmt::getFileRemappings(std::vector<std::pair<std::string,std::string> > &400remap,401StringRef outputDir,402DiagnosticConsumer *DiagClient) {403assert(!outputDir.empty());404405IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());406IntrusiveRefCntPtr<DiagnosticsEngine> Diags(407new DiagnosticsEngine(DiagID, new DiagnosticOptions,408DiagClient, /*ShouldOwnClient=*/false));409410FileRemapper remapper;411bool err = remapper.initFromDisk(outputDir, *Diags,412/*ignoreIfFilesChanged=*/true);413if (err)414return true;415416remapper.forEachMapping(417[&](StringRef From, StringRef To) {418remap.push_back(std::make_pair(From.str(), To.str()));419},420[](StringRef, const llvm::MemoryBufferRef &) {});421422return false;423}424425426//===----------------------------------------------------------------------===//427// CollectTransformActions.428//===----------------------------------------------------------------------===//429430namespace {431432class ARCMTMacroTrackerPPCallbacks : public PPCallbacks {433std::vector<SourceLocation> &ARCMTMacroLocs;434435public:436ARCMTMacroTrackerPPCallbacks(std::vector<SourceLocation> &ARCMTMacroLocs)437: ARCMTMacroLocs(ARCMTMacroLocs) { }438439void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,440SourceRange Range, const MacroArgs *Args) override {441if (MacroNameTok.getIdentifierInfo()->getName() == getARCMTMacroName())442ARCMTMacroLocs.push_back(MacroNameTok.getLocation());443}444};445446class ARCMTMacroTrackerAction : public ASTFrontendAction {447std::vector<SourceLocation> &ARCMTMacroLocs;448449public:450ARCMTMacroTrackerAction(std::vector<SourceLocation> &ARCMTMacroLocs)451: ARCMTMacroLocs(ARCMTMacroLocs) { }452453std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,454StringRef InFile) override {455CI.getPreprocessor().addPPCallbacks(456std::make_unique<ARCMTMacroTrackerPPCallbacks>(ARCMTMacroLocs));457return std::make_unique<ASTConsumer>();458}459};460461class RewritesApplicator : public TransformActions::RewriteReceiver {462Rewriter &rewriter;463MigrationProcess::RewriteListener *Listener;464465public:466RewritesApplicator(Rewriter &rewriter, ASTContext &ctx,467MigrationProcess::RewriteListener *listener)468: rewriter(rewriter), Listener(listener) {469if (Listener)470Listener->start(ctx);471}472~RewritesApplicator() override {473if (Listener)474Listener->finish();475}476477void insert(SourceLocation loc, StringRef text) override {478bool err = rewriter.InsertText(loc, text, /*InsertAfter=*/true,479/*indentNewLines=*/true);480if (!err && Listener)481Listener->insert(loc, text);482}483484void remove(CharSourceRange range) override {485Rewriter::RewriteOptions removeOpts;486removeOpts.IncludeInsertsAtBeginOfRange = false;487removeOpts.IncludeInsertsAtEndOfRange = false;488removeOpts.RemoveLineIfEmpty = true;489490bool err = rewriter.RemoveText(range, removeOpts);491if (!err && Listener)492Listener->remove(range);493}494495void increaseIndentation(CharSourceRange range,496SourceLocation parentIndent) override {497rewriter.IncreaseIndentation(range, parentIndent);498}499};500501} // end anonymous namespace.502503/// Anchor for VTable.504MigrationProcess::RewriteListener::~RewriteListener() { }505506MigrationProcess::MigrationProcess(507CompilerInvocation &CI,508std::shared_ptr<PCHContainerOperations> PCHContainerOps,509DiagnosticConsumer *diagClient, StringRef outputDir)510: OrigCI(CI), PCHContainerOps(std::move(PCHContainerOps)),511DiagClient(diagClient), HadARCErrors(false) {512if (!outputDir.empty()) {513IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());514IntrusiveRefCntPtr<DiagnosticsEngine> Diags(515new DiagnosticsEngine(DiagID, &CI.getDiagnosticOpts(),516DiagClient, /*ShouldOwnClient=*/false));517Remapper.initFromDisk(outputDir, *Diags, /*ignoreIfFilesChanged=*/true);518}519}520521bool MigrationProcess::applyTransform(TransformFn trans,522RewriteListener *listener) {523std::unique_ptr<CompilerInvocation> CInvok;524CInvok.reset(525createInvocationForMigration(OrigCI, PCHContainerOps->getRawReader()));526CInvok->getDiagnosticOpts().IgnoreWarnings = true;527528Remapper.applyMappings(CInvok->getPreprocessorOpts());529530CapturedDiagList capturedDiags;531std::vector<SourceLocation> ARCMTMacroLocs;532533assert(DiagClient);534IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());535IntrusiveRefCntPtr<DiagnosticsEngine> Diags(536new DiagnosticsEngine(DiagID, new DiagnosticOptions,537DiagClient, /*ShouldOwnClient=*/false));538539// Filter of all diagnostics.540CaptureDiagnosticConsumer errRec(*Diags, *DiagClient, capturedDiags);541Diags->setClient(&errRec, /*ShouldOwnClient=*/false);542543std::unique_ptr<ARCMTMacroTrackerAction> ASTAction;544ASTAction.reset(new ARCMTMacroTrackerAction(ARCMTMacroLocs));545546std::unique_ptr<ASTUnit> Unit(ASTUnit::LoadFromCompilerInvocationAction(547std::move(CInvok), PCHContainerOps, Diags, ASTAction.get()));548if (!Unit) {549errRec.FinishCapture();550return true;551}552Unit->setOwnsRemappedFileBuffers(false); // FileRemapper manages that.553554HadARCErrors = HadARCErrors || capturedDiags.hasErrors();555556// Don't filter diagnostics anymore.557Diags->setClient(DiagClient, /*ShouldOwnClient=*/false);558559ASTContext &Ctx = Unit->getASTContext();560561if (Diags->hasFatalErrorOccurred()) {562Diags->Reset();563DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());564capturedDiags.reportDiagnostics(*Diags);565DiagClient->EndSourceFile();566errRec.FinishCapture();567return true;568}569570// After parsing of source files ended, we want to reuse the571// diagnostics objects to emit further diagnostics.572// We call BeginSourceFile because DiagnosticConsumer requires that573// diagnostics with source range information are emitted only in between574// BeginSourceFile() and EndSourceFile().575DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());576577Rewriter rewriter(Ctx.getSourceManager(), Ctx.getLangOpts());578TransformActions TA(*Diags, capturedDiags, Ctx, Unit->getPreprocessor());579MigrationPass pass(Ctx, OrigCI.getLangOpts().getGC(),580Unit->getSema(), TA, capturedDiags, ARCMTMacroLocs);581582trans(pass);583584{585RewritesApplicator applicator(rewriter, Ctx, listener);586TA.applyRewrites(applicator);587}588589DiagClient->EndSourceFile();590errRec.FinishCapture();591592if (DiagClient->getNumErrors())593return true;594595for (Rewriter::buffer_iterator596I = rewriter.buffer_begin(), E = rewriter.buffer_end(); I != E; ++I) {597FileID FID = I->first;598RewriteBuffer &buf = I->second;599OptionalFileEntryRef file =600Ctx.getSourceManager().getFileEntryRefForID(FID);601assert(file);602std::string newFname = std::string(file->getName());603newFname += "-trans";604SmallString<512> newText;605llvm::raw_svector_ostream vecOS(newText);606buf.write(vecOS);607std::unique_ptr<llvm::MemoryBuffer> memBuf(608llvm::MemoryBuffer::getMemBufferCopy(newText.str(), newFname));609SmallString<64> filePath(file->getName());610Unit->getFileManager().FixupRelativePath(filePath);611Remapper.remap(filePath.str(), std::move(memBuf));612}613614return false;615}616617618