llvm-project
616 строк · 21.9 Кб
1//===--- ARCMT.cpp - Migration to ARC mode --------------------------------===//
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-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "clang/ARCMigrate/ARCMT.h"10#include "Internals.h"11#include "clang/AST/ASTConsumer.h"12#include "clang/Basic/DiagnosticCategories.h"13#include "clang/Frontend/ASTUnit.h"14#include "clang/Frontend/CompilerInstance.h"15#include "clang/Frontend/FrontendAction.h"16#include "clang/Frontend/TextDiagnosticPrinter.h"17#include "clang/Frontend/Utils.h"18#include "clang/Lex/Preprocessor.h"19#include "clang/Lex/PreprocessorOptions.h"20#include "clang/Rewrite/Core/Rewriter.h"21#include "clang/Sema/SemaDiagnostic.h"22#include "clang/Serialization/ASTReader.h"23#include "llvm/Support/MemoryBuffer.h"24#include "llvm/TargetParser/Triple.h"25#include <utility>26using namespace clang;27using namespace arcmt;28
29bool CapturedDiagList::clearDiagnostic(ArrayRef<unsigned> IDs,30SourceRange range) {31if (range.isInvalid())32return false;33
34bool cleared = false;35ListTy::iterator I = List.begin();36while (I != List.end()) {37FullSourceLoc diagLoc = I->getLocation();38if ((IDs.empty() || // empty means clear all diagnostics in the range.39llvm::is_contained(IDs, I->getID())) &&40!diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) &&41(diagLoc == range.getEnd() ||42diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) {43cleared = true;44ListTy::iterator eraseS = I++;45if (eraseS->getLevel() != DiagnosticsEngine::Note)46while (I != List.end() && I->getLevel() == DiagnosticsEngine::Note)47++I;48// Clear the diagnostic and any notes following it.49I = List.erase(eraseS, I);50continue;51}52
53++I;54}55
56return cleared;57}
58
59bool CapturedDiagList::hasDiagnostic(ArrayRef<unsigned> IDs,60SourceRange range) const {61if (range.isInvalid())62return false;63
64ListTy::const_iterator I = List.begin();65while (I != List.end()) {66FullSourceLoc diagLoc = I->getLocation();67if ((IDs.empty() || // empty means any diagnostic in the range.68llvm::is_contained(IDs, I->getID())) &&69!diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) &&70(diagLoc == range.getEnd() ||71diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) {72return true;73}74
75++I;76}77
78return false;79}
80
81void CapturedDiagList::reportDiagnostics(DiagnosticsEngine &Diags) const {82for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I)83Diags.Report(*I);84}
85
86bool CapturedDiagList::hasErrors() const {87for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I)88if (I->getLevel() >= DiagnosticsEngine::Error)89return true;90
91return false;92}
93
94namespace {95
96class CaptureDiagnosticConsumer : public DiagnosticConsumer {97DiagnosticsEngine &Diags;98DiagnosticConsumer &DiagClient;99CapturedDiagList &CapturedDiags;100bool HasBegunSourceFile;101public:102CaptureDiagnosticConsumer(DiagnosticsEngine &diags,103DiagnosticConsumer &client,104CapturedDiagList &capturedDiags)105: Diags(diags), DiagClient(client), CapturedDiags(capturedDiags),106HasBegunSourceFile(false) { }107
108void BeginSourceFile(const LangOptions &Opts,109const Preprocessor *PP) override {110// Pass BeginSourceFile message onto DiagClient on first call.111// The corresponding EndSourceFile call will be made from an112// explicit call to FinishCapture.113if (!HasBegunSourceFile) {114DiagClient.BeginSourceFile(Opts, PP);115HasBegunSourceFile = true;116}117}118
119void FinishCapture() {120// Call EndSourceFile on DiagClient on completion of capture to121// enable VerifyDiagnosticConsumer to check diagnostics *after*122// it has received the diagnostic list.123if (HasBegunSourceFile) {124DiagClient.EndSourceFile();125HasBegunSourceFile = false;126}127}128
129~CaptureDiagnosticConsumer() override {130assert(!HasBegunSourceFile && "FinishCapture not called!");131}132
133void HandleDiagnostic(DiagnosticsEngine::Level level,134const Diagnostic &Info) override {135if (DiagnosticIDs::isARCDiagnostic(Info.getID()) ||136level >= DiagnosticsEngine::Error || level == DiagnosticsEngine::Note) {137if (Info.getLocation().isValid())138CapturedDiags.push_back(StoredDiagnostic(level, Info));139return;140}141
142// Non-ARC warnings are ignored.143Diags.setLastDiagnosticIgnored(true);144}145};146
147} // end anonymous namespace148
149static bool HasARCRuntime(CompilerInvocation &origCI) {150// This duplicates some functionality from Darwin::AddDeploymentTarget151// but this function is well defined, so keep it decoupled from the driver152// and avoid unrelated complications.153llvm::Triple triple(origCI.getTargetOpts().Triple);154
155if (triple.isiOS())156return triple.getOSMajorVersion() >= 5;157
158if (triple.isWatchOS())159return true;160
161if (triple.getOS() == llvm::Triple::Darwin)162return triple.getOSMajorVersion() >= 11;163
164if (triple.getOS() == llvm::Triple::MacOSX) {165return triple.getOSVersion() >= VersionTuple(10, 7);166}167
168return false;169}
170
171static CompilerInvocation *172createInvocationForMigration(CompilerInvocation &origCI,173const PCHContainerReader &PCHContainerRdr) {174std::unique_ptr<CompilerInvocation> CInvok;175CInvok.reset(new CompilerInvocation(origCI));176PreprocessorOptions &PPOpts = CInvok->getPreprocessorOpts();177if (!PPOpts.ImplicitPCHInclude.empty()) {178// We can't use a PCH because it was likely built in non-ARC mode and we179// want to parse in ARC. Include the original header.180FileManager FileMgr(origCI.getFileSystemOpts());181IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());182IntrusiveRefCntPtr<DiagnosticsEngine> Diags(183new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(),184new IgnoringDiagConsumer()));185std::string OriginalFile = ASTReader::getOriginalSourceFile(186PPOpts.ImplicitPCHInclude, FileMgr, PCHContainerRdr, *Diags);187if (!OriginalFile.empty())188PPOpts.Includes.insert(PPOpts.Includes.begin(), OriginalFile);189PPOpts.ImplicitPCHInclude.clear();190}191std::string define = std::string(getARCMTMacroName());192define += '=';193CInvok->getPreprocessorOpts().addMacroDef(define);194CInvok->getLangOpts().ObjCAutoRefCount = true;195CInvok->getLangOpts().setGC(LangOptions::NonGC);196CInvok->getDiagnosticOpts().ErrorLimit = 0;197CInvok->getDiagnosticOpts().PedanticErrors = 0;198
199// Ignore -Werror flags when migrating.200std::vector<std::string> WarnOpts;201for (std::vector<std::string>::iterator202I = CInvok->getDiagnosticOpts().Warnings.begin(),203E = CInvok->getDiagnosticOpts().Warnings.end(); I != E; ++I) {204if (!StringRef(*I).starts_with("error"))205WarnOpts.push_back(*I);206}207WarnOpts.push_back("error=arc-unsafe-retained-assign");208CInvok->getDiagnosticOpts().Warnings = std::move(WarnOpts);209
210CInvok->getLangOpts().ObjCWeakRuntime = HasARCRuntime(origCI);211CInvok->getLangOpts().ObjCWeak = CInvok->getLangOpts().ObjCWeakRuntime;212
213return CInvok.release();214}
215
216static void emitPremigrationErrors(const CapturedDiagList &arcDiags,217DiagnosticOptions *diagOpts,218Preprocessor &PP) {219TextDiagnosticPrinter printer(llvm::errs(), diagOpts);220IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());221IntrusiveRefCntPtr<DiagnosticsEngine> Diags(222new DiagnosticsEngine(DiagID, diagOpts, &printer,223/*ShouldOwnClient=*/false));224Diags->setSourceManager(&PP.getSourceManager());225
226printer.BeginSourceFile(PP.getLangOpts(), &PP);227arcDiags.reportDiagnostics(*Diags);228printer.EndSourceFile();229}
230
231//===----------------------------------------------------------------------===//
232// checkForManualIssues.
233//===----------------------------------------------------------------------===//
234
235bool arcmt::checkForManualIssues(236CompilerInvocation &origCI, const FrontendInputFile &Input,237std::shared_ptr<PCHContainerOperations> PCHContainerOps,238DiagnosticConsumer *DiagClient, bool emitPremigrationARCErrors,239StringRef plistOut) {240if (!origCI.getLangOpts().ObjC)241return false;242
243LangOptions::GCMode OrigGCMode = origCI.getLangOpts().getGC();244bool NoNSAllocReallocError = origCI.getMigratorOpts().NoNSAllocReallocError;245bool NoFinalizeRemoval = origCI.getMigratorOpts().NoFinalizeRemoval;246
247std::vector<TransformFn> transforms = arcmt::getAllTransformations(OrigGCMode,248NoFinalizeRemoval);249assert(!transforms.empty());250
251std::unique_ptr<CompilerInvocation> CInvok;252CInvok.reset(253createInvocationForMigration(origCI, PCHContainerOps->getRawReader()));254CInvok->getFrontendOpts().Inputs.clear();255CInvok->getFrontendOpts().Inputs.push_back(Input);256
257CapturedDiagList capturedDiags;258
259assert(DiagClient);260IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());261IntrusiveRefCntPtr<DiagnosticsEngine> Diags(262new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(),263DiagClient, /*ShouldOwnClient=*/false));264
265// Filter of all diagnostics.266CaptureDiagnosticConsumer errRec(*Diags, *DiagClient, capturedDiags);267Diags->setClient(&errRec, /*ShouldOwnClient=*/false);268
269std::unique_ptr<ASTUnit> Unit(ASTUnit::LoadFromCompilerInvocationAction(270std::move(CInvok), PCHContainerOps, Diags));271if (!Unit) {272errRec.FinishCapture();273return true;274}275
276// Don't filter diagnostics anymore.277Diags->setClient(DiagClient, /*ShouldOwnClient=*/false);278
279ASTContext &Ctx = Unit->getASTContext();280
281if (Diags->hasFatalErrorOccurred()) {282Diags->Reset();283DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());284capturedDiags.reportDiagnostics(*Diags);285DiagClient->EndSourceFile();286errRec.FinishCapture();287return true;288}289
290if (emitPremigrationARCErrors)291emitPremigrationErrors(capturedDiags, &origCI.getDiagnosticOpts(),292Unit->getPreprocessor());293if (!plistOut.empty()) {294SmallVector<StoredDiagnostic, 8> arcDiags;295for (CapturedDiagList::iterator296I = capturedDiags.begin(), E = capturedDiags.end(); I != E; ++I)297arcDiags.push_back(*I);298writeARCDiagsToPlist(std::string(plistOut), arcDiags,299Ctx.getSourceManager(), Ctx.getLangOpts());300}301
302// After parsing of source files ended, we want to reuse the303// diagnostics objects to emit further diagnostics.304// We call BeginSourceFile because DiagnosticConsumer requires that305// diagnostics with source range information are emitted only in between306// BeginSourceFile() and EndSourceFile().307DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());308
309// No macros will be added since we are just checking and we won't modify310// source code.311std::vector<SourceLocation> ARCMTMacroLocs;312
313TransformActions testAct(*Diags, capturedDiags, Ctx, Unit->getPreprocessor());314MigrationPass pass(Ctx, OrigGCMode, Unit->getSema(), testAct, capturedDiags,315ARCMTMacroLocs);316pass.setNoFinalizeRemoval(NoFinalizeRemoval);317if (!NoNSAllocReallocError)318Diags->setSeverity(diag::warn_arcmt_nsalloc_realloc, diag::Severity::Error,319SourceLocation());320
321for (unsigned i=0, e = transforms.size(); i != e; ++i)322transforms[i](pass);323
324capturedDiags.reportDiagnostics(*Diags);325
326DiagClient->EndSourceFile();327errRec.FinishCapture();328
329return capturedDiags.hasErrors() || testAct.hasReportedErrors();330}
331
332//===----------------------------------------------------------------------===//
333// applyTransformations.
334//===----------------------------------------------------------------------===//
335
336static bool337applyTransforms(CompilerInvocation &origCI, const FrontendInputFile &Input,338std::shared_ptr<PCHContainerOperations> PCHContainerOps,339DiagnosticConsumer *DiagClient, StringRef outputDir,340bool emitPremigrationARCErrors, StringRef plistOut) {341if (!origCI.getLangOpts().ObjC)342return false;343
344LangOptions::GCMode OrigGCMode = origCI.getLangOpts().getGC();345
346// Make sure checking is successful first.347CompilerInvocation CInvokForCheck(origCI);348if (arcmt::checkForManualIssues(CInvokForCheck, Input, PCHContainerOps,349DiagClient, emitPremigrationARCErrors,350plistOut))351return true;352
353CompilerInvocation CInvok(origCI);354CInvok.getFrontendOpts().Inputs.clear();355CInvok.getFrontendOpts().Inputs.push_back(Input);356
357MigrationProcess migration(CInvok, PCHContainerOps, DiagClient, outputDir);358bool NoFinalizeRemoval = origCI.getMigratorOpts().NoFinalizeRemoval;359
360std::vector<TransformFn> transforms = arcmt::getAllTransformations(OrigGCMode,361NoFinalizeRemoval);362assert(!transforms.empty());363
364for (unsigned i=0, e = transforms.size(); i != e; ++i) {365bool err = migration.applyTransform(transforms[i]);366if (err) return true;367}368
369IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());370IntrusiveRefCntPtr<DiagnosticsEngine> Diags(371new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(),372DiagClient, /*ShouldOwnClient=*/false));373
374if (outputDir.empty()) {375origCI.getLangOpts().ObjCAutoRefCount = true;376return migration.getRemapper().overwriteOriginal(*Diags);377} else {378return migration.getRemapper().flushToDisk(outputDir, *Diags);379}380}
381
382bool arcmt::applyTransformations(383CompilerInvocation &origCI, const FrontendInputFile &Input,384std::shared_ptr<PCHContainerOperations> PCHContainerOps,385DiagnosticConsumer *DiagClient) {386return applyTransforms(origCI, Input, PCHContainerOps, DiagClient,387StringRef(), false, StringRef());388}
389
390bool arcmt::migrateWithTemporaryFiles(391CompilerInvocation &origCI, const FrontendInputFile &Input,392std::shared_ptr<PCHContainerOperations> PCHContainerOps,393DiagnosticConsumer *DiagClient, StringRef outputDir,394bool emitPremigrationARCErrors, StringRef plistOut) {395assert(!outputDir.empty() && "Expected output directory path");396return applyTransforms(origCI, Input, PCHContainerOps, DiagClient, outputDir,397emitPremigrationARCErrors, plistOut);398}
399
400bool arcmt::getFileRemappings(std::vector<std::pair<std::string,std::string> > &401remap,402StringRef outputDir,403DiagnosticConsumer *DiagClient) {404assert(!outputDir.empty());405
406IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());407IntrusiveRefCntPtr<DiagnosticsEngine> Diags(408new DiagnosticsEngine(DiagID, new DiagnosticOptions,409DiagClient, /*ShouldOwnClient=*/false));410
411FileRemapper remapper;412bool err = remapper.initFromDisk(outputDir, *Diags,413/*ignoreIfFilesChanged=*/true);414if (err)415return true;416
417remapper.forEachMapping(418[&](StringRef From, StringRef To) {419remap.push_back(std::make_pair(From.str(), To.str()));420},421[](StringRef, const llvm::MemoryBufferRef &) {});422
423return false;424}
425
426
427//===----------------------------------------------------------------------===//
428// CollectTransformActions.
429//===----------------------------------------------------------------------===//
430
431namespace {432
433class ARCMTMacroTrackerPPCallbacks : public PPCallbacks {434std::vector<SourceLocation> &ARCMTMacroLocs;435
436public:437ARCMTMacroTrackerPPCallbacks(std::vector<SourceLocation> &ARCMTMacroLocs)438: ARCMTMacroLocs(ARCMTMacroLocs) { }439
440void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,441SourceRange Range, const MacroArgs *Args) override {442if (MacroNameTok.getIdentifierInfo()->getName() == getARCMTMacroName())443ARCMTMacroLocs.push_back(MacroNameTok.getLocation());444}445};446
447class ARCMTMacroTrackerAction : public ASTFrontendAction {448std::vector<SourceLocation> &ARCMTMacroLocs;449
450public:451ARCMTMacroTrackerAction(std::vector<SourceLocation> &ARCMTMacroLocs)452: ARCMTMacroLocs(ARCMTMacroLocs) { }453
454std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,455StringRef InFile) override {456CI.getPreprocessor().addPPCallbacks(457std::make_unique<ARCMTMacroTrackerPPCallbacks>(ARCMTMacroLocs));458return std::make_unique<ASTConsumer>();459}460};461
462class RewritesApplicator : public TransformActions::RewriteReceiver {463Rewriter &rewriter;464MigrationProcess::RewriteListener *Listener;465
466public:467RewritesApplicator(Rewriter &rewriter, ASTContext &ctx,468MigrationProcess::RewriteListener *listener)469: rewriter(rewriter), Listener(listener) {470if (Listener)471Listener->start(ctx);472}473~RewritesApplicator() override {474if (Listener)475Listener->finish();476}477
478void insert(SourceLocation loc, StringRef text) override {479bool err = rewriter.InsertText(loc, text, /*InsertAfter=*/true,480/*indentNewLines=*/true);481if (!err && Listener)482Listener->insert(loc, text);483}484
485void remove(CharSourceRange range) override {486Rewriter::RewriteOptions removeOpts;487removeOpts.IncludeInsertsAtBeginOfRange = false;488removeOpts.IncludeInsertsAtEndOfRange = false;489removeOpts.RemoveLineIfEmpty = true;490
491bool err = rewriter.RemoveText(range, removeOpts);492if (!err && Listener)493Listener->remove(range);494}495
496void increaseIndentation(CharSourceRange range,497SourceLocation parentIndent) override {498rewriter.IncreaseIndentation(range, parentIndent);499}500};501
502} // end anonymous namespace.503
504/// Anchor for VTable.
505MigrationProcess::RewriteListener::~RewriteListener() { }506
507MigrationProcess::MigrationProcess(508CompilerInvocation &CI,509std::shared_ptr<PCHContainerOperations> PCHContainerOps,510DiagnosticConsumer *diagClient, StringRef outputDir)511: OrigCI(CI), PCHContainerOps(std::move(PCHContainerOps)),512DiagClient(diagClient), HadARCErrors(false) {513if (!outputDir.empty()) {514IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());515IntrusiveRefCntPtr<DiagnosticsEngine> Diags(516new DiagnosticsEngine(DiagID, &CI.getDiagnosticOpts(),517DiagClient, /*ShouldOwnClient=*/false));518Remapper.initFromDisk(outputDir, *Diags, /*ignoreIfFilesChanged=*/true);519}520}
521
522bool MigrationProcess::applyTransform(TransformFn trans,523RewriteListener *listener) {524std::unique_ptr<CompilerInvocation> CInvok;525CInvok.reset(526createInvocationForMigration(OrigCI, PCHContainerOps->getRawReader()));527CInvok->getDiagnosticOpts().IgnoreWarnings = true;528
529Remapper.applyMappings(CInvok->getPreprocessorOpts());530
531CapturedDiagList capturedDiags;532std::vector<SourceLocation> ARCMTMacroLocs;533
534assert(DiagClient);535IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());536IntrusiveRefCntPtr<DiagnosticsEngine> Diags(537new DiagnosticsEngine(DiagID, new DiagnosticOptions,538DiagClient, /*ShouldOwnClient=*/false));539
540// Filter of all diagnostics.541CaptureDiagnosticConsumer errRec(*Diags, *DiagClient, capturedDiags);542Diags->setClient(&errRec, /*ShouldOwnClient=*/false);543
544std::unique_ptr<ARCMTMacroTrackerAction> ASTAction;545ASTAction.reset(new ARCMTMacroTrackerAction(ARCMTMacroLocs));546
547std::unique_ptr<ASTUnit> Unit(ASTUnit::LoadFromCompilerInvocationAction(548std::move(CInvok), PCHContainerOps, Diags, ASTAction.get()));549if (!Unit) {550errRec.FinishCapture();551return true;552}553Unit->setOwnsRemappedFileBuffers(false); // FileRemapper manages that.554
555HadARCErrors = HadARCErrors || capturedDiags.hasErrors();556
557// Don't filter diagnostics anymore.558Diags->setClient(DiagClient, /*ShouldOwnClient=*/false);559
560ASTContext &Ctx = Unit->getASTContext();561
562if (Diags->hasFatalErrorOccurred()) {563Diags->Reset();564DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());565capturedDiags.reportDiagnostics(*Diags);566DiagClient->EndSourceFile();567errRec.FinishCapture();568return true;569}570
571// After parsing of source files ended, we want to reuse the572// diagnostics objects to emit further diagnostics.573// We call BeginSourceFile because DiagnosticConsumer requires that574// diagnostics with source range information are emitted only in between575// BeginSourceFile() and EndSourceFile().576DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());577
578Rewriter rewriter(Ctx.getSourceManager(), Ctx.getLangOpts());579TransformActions TA(*Diags, capturedDiags, Ctx, Unit->getPreprocessor());580MigrationPass pass(Ctx, OrigCI.getLangOpts().getGC(),581Unit->getSema(), TA, capturedDiags, ARCMTMacroLocs);582
583trans(pass);584
585{586RewritesApplicator applicator(rewriter, Ctx, listener);587TA.applyRewrites(applicator);588}589
590DiagClient->EndSourceFile();591errRec.FinishCapture();592
593if (DiagClient->getNumErrors())594return true;595
596for (Rewriter::buffer_iterator597I = rewriter.buffer_begin(), E = rewriter.buffer_end(); I != E; ++I) {598FileID FID = I->first;599RewriteBuffer &buf = I->second;600OptionalFileEntryRef file =601Ctx.getSourceManager().getFileEntryRefForID(FID);602assert(file);603std::string newFname = std::string(file->getName());604newFname += "-trans";605SmallString<512> newText;606llvm::raw_svector_ostream vecOS(newText);607buf.write(vecOS);608std::unique_ptr<llvm::MemoryBuffer> memBuf(609llvm::MemoryBuffer::getMemBufferCopy(newText.str(), newFname));610SmallString<64> filePath(file->getName());611Unit->getFileManager().FixupRelativePath(filePath);612Remapper.remap(filePath.str(), std::move(memBuf));613}614
615return false;616}
617