1 //===--- ARCMT.cpp - Migration to ARC mode --------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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/ADT/Triple.h"
23 #include "llvm/Support/MemoryBuffer.h"
25 using namespace clang;
26 using namespace arcmt;
28 bool CapturedDiagList::clearDiagnostic(ArrayRef<unsigned> IDs,
30 if (range.isInvalid())
34 ListTy::iterator I = List.begin();
35 while (I != List.end()) {
36 FullSourceLoc diagLoc = I->getLocation();
37 if ((IDs.empty() || // empty means clear all diagnostics in the range.
38 llvm::is_contained(IDs, I->getID())) &&
39 !diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) &&
40 (diagLoc == range.getEnd() ||
41 diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) {
43 ListTy::iterator eraseS = I++;
44 if (eraseS->getLevel() != DiagnosticsEngine::Note)
45 while (I != List.end() && I->getLevel() == DiagnosticsEngine::Note)
47 // Clear the diagnostic and any notes following it.
48 I = List.erase(eraseS, I);
58 bool CapturedDiagList::hasDiagnostic(ArrayRef<unsigned> IDs,
59 SourceRange range) const {
60 if (range.isInvalid())
63 ListTy::const_iterator I = List.begin();
64 while (I != List.end()) {
65 FullSourceLoc diagLoc = I->getLocation();
66 if ((IDs.empty() || // empty means any diagnostic in the range.
67 llvm::find(IDs, I->getID()) != IDs.end()) &&
68 !diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) &&
69 (diagLoc == range.getEnd() ||
70 diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) {
80 void CapturedDiagList::reportDiagnostics(DiagnosticsEngine &Diags) const {
81 for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I)
85 bool CapturedDiagList::hasErrors() const {
86 for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I)
87 if (I->getLevel() >= DiagnosticsEngine::Error)
95 class CaptureDiagnosticConsumer : public DiagnosticConsumer {
96 DiagnosticsEngine &Diags;
97 DiagnosticConsumer &DiagClient;
98 CapturedDiagList &CapturedDiags;
99 bool HasBegunSourceFile;
101 CaptureDiagnosticConsumer(DiagnosticsEngine &diags,
102 DiagnosticConsumer &client,
103 CapturedDiagList &capturedDiags)
104 : Diags(diags), DiagClient(client), CapturedDiags(capturedDiags),
105 HasBegunSourceFile(false) { }
107 void BeginSourceFile(const LangOptions &Opts,
108 const Preprocessor *PP) override {
109 // Pass BeginSourceFile message onto DiagClient on first call.
110 // The corresponding EndSourceFile call will be made from an
111 // explicit call to FinishCapture.
112 if (!HasBegunSourceFile) {
113 DiagClient.BeginSourceFile(Opts, PP);
114 HasBegunSourceFile = true;
118 void FinishCapture() {
119 // Call EndSourceFile on DiagClient on completion of capture to
120 // enable VerifyDiagnosticConsumer to check diagnostics *after*
121 // it has received the diagnostic list.
122 if (HasBegunSourceFile) {
123 DiagClient.EndSourceFile();
124 HasBegunSourceFile = false;
128 ~CaptureDiagnosticConsumer() override {
129 assert(!HasBegunSourceFile && "FinishCapture not called!");
132 void HandleDiagnostic(DiagnosticsEngine::Level level,
133 const Diagnostic &Info) override {
134 if (DiagnosticIDs::isARCDiagnostic(Info.getID()) ||
135 level >= DiagnosticsEngine::Error || level == DiagnosticsEngine::Note) {
136 if (Info.getLocation().isValid())
137 CapturedDiags.push_back(StoredDiagnostic(level, Info));
141 // Non-ARC warnings are ignored.
142 Diags.setLastDiagnosticIgnored(true);
146 } // end anonymous namespace
148 static bool HasARCRuntime(CompilerInvocation &origCI) {
149 // This duplicates some functionality from Darwin::AddDeploymentTarget
150 // but this function is well defined, so keep it decoupled from the driver
151 // and avoid unrelated complications.
152 llvm::Triple triple(origCI.getTargetOpts().Triple);
155 return triple.getOSMajorVersion() >= 5;
157 if (triple.isWatchOS())
160 if (triple.getOS() == llvm::Triple::Darwin)
161 return triple.getOSMajorVersion() >= 11;
163 if (triple.getOS() == llvm::Triple::MacOSX) {
164 unsigned Major, Minor, Micro;
165 triple.getOSVersion(Major, Minor, Micro);
166 return Major > 10 || (Major == 10 && Minor >= 7);
172 static CompilerInvocation *
173 createInvocationForMigration(CompilerInvocation &origCI,
174 const PCHContainerReader &PCHContainerRdr) {
175 std::unique_ptr<CompilerInvocation> CInvok;
176 CInvok.reset(new CompilerInvocation(origCI));
177 PreprocessorOptions &PPOpts = CInvok->getPreprocessorOpts();
178 if (!PPOpts.ImplicitPCHInclude.empty()) {
179 // We can't use a PCH because it was likely built in non-ARC mode and we
180 // want to parse in ARC. Include the original header.
181 FileManager FileMgr(origCI.getFileSystemOpts());
182 IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
183 IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
184 new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(),
185 new IgnoringDiagConsumer()));
186 std::string OriginalFile = ASTReader::getOriginalSourceFile(
187 PPOpts.ImplicitPCHInclude, FileMgr, PCHContainerRdr, *Diags);
188 if (!OriginalFile.empty())
189 PPOpts.Includes.insert(PPOpts.Includes.begin(), OriginalFile);
190 PPOpts.ImplicitPCHInclude.clear();
192 std::string define = getARCMTMacroName();
194 CInvok->getPreprocessorOpts().addMacroDef(define);
195 CInvok->getLangOpts()->ObjCAutoRefCount = true;
196 CInvok->getLangOpts()->setGC(LangOptions::NonGC);
197 CInvok->getDiagnosticOpts().ErrorLimit = 0;
198 CInvok->getDiagnosticOpts().PedanticErrors = 0;
200 // Ignore -Werror flags when migrating.
201 std::vector<std::string> WarnOpts;
202 for (std::vector<std::string>::iterator
203 I = CInvok->getDiagnosticOpts().Warnings.begin(),
204 E = CInvok->getDiagnosticOpts().Warnings.end(); I != E; ++I) {
205 if (!StringRef(*I).startswith("error"))
206 WarnOpts.push_back(*I);
208 WarnOpts.push_back("error=arc-unsafe-retained-assign");
209 CInvok->getDiagnosticOpts().Warnings = std::move(WarnOpts);
211 CInvok->getLangOpts()->ObjCWeakRuntime = HasARCRuntime(origCI);
212 CInvok->getLangOpts()->ObjCWeak = CInvok->getLangOpts()->ObjCWeakRuntime;
214 return CInvok.release();
217 static void emitPremigrationErrors(const CapturedDiagList &arcDiags,
218 DiagnosticOptions *diagOpts,
220 TextDiagnosticPrinter printer(llvm::errs(), diagOpts);
221 IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
222 IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
223 new DiagnosticsEngine(DiagID, diagOpts, &printer,
224 /*ShouldOwnClient=*/false));
225 Diags->setSourceManager(&PP.getSourceManager());
227 printer.BeginSourceFile(PP.getLangOpts(), &PP);
228 arcDiags.reportDiagnostics(*Diags);
229 printer.EndSourceFile();
232 //===----------------------------------------------------------------------===//
233 // checkForManualIssues.
234 //===----------------------------------------------------------------------===//
236 bool arcmt::checkForManualIssues(
237 CompilerInvocation &origCI, const FrontendInputFile &Input,
238 std::shared_ptr<PCHContainerOperations> PCHContainerOps,
239 DiagnosticConsumer *DiagClient, bool emitPremigrationARCErrors,
240 StringRef plistOut) {
241 if (!origCI.getLangOpts()->ObjC)
244 LangOptions::GCMode OrigGCMode = origCI.getLangOpts()->getGC();
245 bool NoNSAllocReallocError = origCI.getMigratorOpts().NoNSAllocReallocError;
246 bool NoFinalizeRemoval = origCI.getMigratorOpts().NoFinalizeRemoval;
248 std::vector<TransformFn> transforms = arcmt::getAllTransformations(OrigGCMode,
250 assert(!transforms.empty());
252 std::unique_ptr<CompilerInvocation> CInvok;
254 createInvocationForMigration(origCI, PCHContainerOps->getRawReader()));
255 CInvok->getFrontendOpts().Inputs.clear();
256 CInvok->getFrontendOpts().Inputs.push_back(Input);
258 CapturedDiagList capturedDiags;
261 IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
262 IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
263 new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(),
264 DiagClient, /*ShouldOwnClient=*/false));
266 // Filter of all diagnostics.
267 CaptureDiagnosticConsumer errRec(*Diags, *DiagClient, capturedDiags);
268 Diags->setClient(&errRec, /*ShouldOwnClient=*/false);
270 std::unique_ptr<ASTUnit> Unit(ASTUnit::LoadFromCompilerInvocationAction(
271 std::move(CInvok), PCHContainerOps, Diags));
273 errRec.FinishCapture();
277 // Don't filter diagnostics anymore.
278 Diags->setClient(DiagClient, /*ShouldOwnClient=*/false);
280 ASTContext &Ctx = Unit->getASTContext();
282 if (Diags->hasFatalErrorOccurred()) {
284 DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());
285 capturedDiags.reportDiagnostics(*Diags);
286 DiagClient->EndSourceFile();
287 errRec.FinishCapture();
291 if (emitPremigrationARCErrors)
292 emitPremigrationErrors(capturedDiags, &origCI.getDiagnosticOpts(),
293 Unit->getPreprocessor());
294 if (!plistOut.empty()) {
295 SmallVector<StoredDiagnostic, 8> arcDiags;
296 for (CapturedDiagList::iterator
297 I = capturedDiags.begin(), E = capturedDiags.end(); I != E; ++I)
298 arcDiags.push_back(*I);
299 writeARCDiagsToPlist(plistOut, arcDiags,
300 Ctx.getSourceManager(), Ctx.getLangOpts());
303 // After parsing of source files ended, we want to reuse the
304 // diagnostics objects to emit further diagnostics.
305 // We call BeginSourceFile because DiagnosticConsumer requires that
306 // diagnostics with source range information are emitted only in between
307 // BeginSourceFile() and EndSourceFile().
308 DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());
310 // No macros will be added since we are just checking and we won't modify
312 std::vector<SourceLocation> ARCMTMacroLocs;
314 TransformActions testAct(*Diags, capturedDiags, Ctx, Unit->getPreprocessor());
315 MigrationPass pass(Ctx, OrigGCMode, Unit->getSema(), testAct, capturedDiags,
317 pass.setNoFinalizeRemoval(NoFinalizeRemoval);
318 if (!NoNSAllocReallocError)
319 Diags->setSeverity(diag::warn_arcmt_nsalloc_realloc, diag::Severity::Error,
322 for (unsigned i=0, e = transforms.size(); i != e; ++i)
325 capturedDiags.reportDiagnostics(*Diags);
327 DiagClient->EndSourceFile();
328 errRec.FinishCapture();
330 return capturedDiags.hasErrors() || testAct.hasReportedErrors();
333 //===----------------------------------------------------------------------===//
334 // applyTransformations.
335 //===----------------------------------------------------------------------===//
338 applyTransforms(CompilerInvocation &origCI, const FrontendInputFile &Input,
339 std::shared_ptr<PCHContainerOperations> PCHContainerOps,
340 DiagnosticConsumer *DiagClient, StringRef outputDir,
341 bool emitPremigrationARCErrors, StringRef plistOut) {
342 if (!origCI.getLangOpts()->ObjC)
345 LangOptions::GCMode OrigGCMode = origCI.getLangOpts()->getGC();
347 // Make sure checking is successful first.
348 CompilerInvocation CInvokForCheck(origCI);
349 if (arcmt::checkForManualIssues(CInvokForCheck, Input, PCHContainerOps,
350 DiagClient, emitPremigrationARCErrors,
354 CompilerInvocation CInvok(origCI);
355 CInvok.getFrontendOpts().Inputs.clear();
356 CInvok.getFrontendOpts().Inputs.push_back(Input);
358 MigrationProcess migration(CInvok, PCHContainerOps, DiagClient, outputDir);
359 bool NoFinalizeRemoval = origCI.getMigratorOpts().NoFinalizeRemoval;
361 std::vector<TransformFn> transforms = arcmt::getAllTransformations(OrigGCMode,
363 assert(!transforms.empty());
365 for (unsigned i=0, e = transforms.size(); i != e; ++i) {
366 bool err = migration.applyTransform(transforms[i]);
367 if (err) return true;
370 IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
371 IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
372 new DiagnosticsEngine(DiagID, &origCI.getDiagnosticOpts(),
373 DiagClient, /*ShouldOwnClient=*/false));
375 if (outputDir.empty()) {
376 origCI.getLangOpts()->ObjCAutoRefCount = true;
377 return migration.getRemapper().overwriteOriginal(*Diags);
379 return migration.getRemapper().flushToDisk(outputDir, *Diags);
383 bool arcmt::applyTransformations(
384 CompilerInvocation &origCI, const FrontendInputFile &Input,
385 std::shared_ptr<PCHContainerOperations> PCHContainerOps,
386 DiagnosticConsumer *DiagClient) {
387 return applyTransforms(origCI, Input, PCHContainerOps, DiagClient,
388 StringRef(), false, StringRef());
391 bool arcmt::migrateWithTemporaryFiles(
392 CompilerInvocation &origCI, const FrontendInputFile &Input,
393 std::shared_ptr<PCHContainerOperations> PCHContainerOps,
394 DiagnosticConsumer *DiagClient, StringRef outputDir,
395 bool emitPremigrationARCErrors, StringRef plistOut) {
396 assert(!outputDir.empty() && "Expected output directory path");
397 return applyTransforms(origCI, Input, PCHContainerOps, DiagClient, outputDir,
398 emitPremigrationARCErrors, plistOut);
401 bool arcmt::getFileRemappings(std::vector<std::pair<std::string,std::string> > &
404 DiagnosticConsumer *DiagClient) {
405 assert(!outputDir.empty());
407 IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
408 IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
409 new DiagnosticsEngine(DiagID, new DiagnosticOptions,
410 DiagClient, /*ShouldOwnClient=*/false));
412 FileRemapper remapper;
413 bool err = remapper.initFromDisk(outputDir, *Diags,
414 /*ignoreIfFilesChanged=*/true);
418 PreprocessorOptions PPOpts;
419 remapper.applyMappings(PPOpts);
420 remap = PPOpts.RemappedFiles;
426 //===----------------------------------------------------------------------===//
427 // CollectTransformActions.
428 //===----------------------------------------------------------------------===//
432 class ARCMTMacroTrackerPPCallbacks : public PPCallbacks {
433 std::vector<SourceLocation> &ARCMTMacroLocs;
436 ARCMTMacroTrackerPPCallbacks(std::vector<SourceLocation> &ARCMTMacroLocs)
437 : ARCMTMacroLocs(ARCMTMacroLocs) { }
439 void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
440 SourceRange Range, const MacroArgs *Args) override {
441 if (MacroNameTok.getIdentifierInfo()->getName() == getARCMTMacroName())
442 ARCMTMacroLocs.push_back(MacroNameTok.getLocation());
446 class ARCMTMacroTrackerAction : public ASTFrontendAction {
447 std::vector<SourceLocation> &ARCMTMacroLocs;
450 ARCMTMacroTrackerAction(std::vector<SourceLocation> &ARCMTMacroLocs)
451 : ARCMTMacroLocs(ARCMTMacroLocs) { }
453 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
454 StringRef InFile) override {
455 CI.getPreprocessor().addPPCallbacks(
456 std::make_unique<ARCMTMacroTrackerPPCallbacks>(ARCMTMacroLocs));
457 return std::make_unique<ASTConsumer>();
461 class RewritesApplicator : public TransformActions::RewriteReceiver {
463 MigrationProcess::RewriteListener *Listener;
466 RewritesApplicator(Rewriter &rewriter, ASTContext &ctx,
467 MigrationProcess::RewriteListener *listener)
468 : rewriter(rewriter), Listener(listener) {
470 Listener->start(ctx);
472 ~RewritesApplicator() override {
477 void insert(SourceLocation loc, StringRef text) override {
478 bool err = rewriter.InsertText(loc, text, /*InsertAfter=*/true,
479 /*indentNewLines=*/true);
480 if (!err && Listener)
481 Listener->insert(loc, text);
484 void remove(CharSourceRange range) override {
485 Rewriter::RewriteOptions removeOpts;
486 removeOpts.IncludeInsertsAtBeginOfRange = false;
487 removeOpts.IncludeInsertsAtEndOfRange = false;
488 removeOpts.RemoveLineIfEmpty = true;
490 bool err = rewriter.RemoveText(range, removeOpts);
491 if (!err && Listener)
492 Listener->remove(range);
495 void increaseIndentation(CharSourceRange range,
496 SourceLocation parentIndent) override {
497 rewriter.IncreaseIndentation(range, parentIndent);
501 } // end anonymous namespace.
503 /// Anchor for VTable.
504 MigrationProcess::RewriteListener::~RewriteListener() { }
506 MigrationProcess::MigrationProcess(
507 const CompilerInvocation &CI,
508 std::shared_ptr<PCHContainerOperations> PCHContainerOps,
509 DiagnosticConsumer *diagClient, StringRef outputDir)
510 : OrigCI(CI), PCHContainerOps(std::move(PCHContainerOps)),
511 DiagClient(diagClient), HadARCErrors(false) {
512 if (!outputDir.empty()) {
513 IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
514 IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
515 new DiagnosticsEngine(DiagID, &CI.getDiagnosticOpts(),
516 DiagClient, /*ShouldOwnClient=*/false));
517 Remapper.initFromDisk(outputDir, *Diags, /*ignoreIfFilesChanged=*/true);
521 bool MigrationProcess::applyTransform(TransformFn trans,
522 RewriteListener *listener) {
523 std::unique_ptr<CompilerInvocation> CInvok;
525 createInvocationForMigration(OrigCI, PCHContainerOps->getRawReader()));
526 CInvok->getDiagnosticOpts().IgnoreWarnings = true;
528 Remapper.applyMappings(CInvok->getPreprocessorOpts());
530 CapturedDiagList capturedDiags;
531 std::vector<SourceLocation> ARCMTMacroLocs;
534 IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
535 IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
536 new DiagnosticsEngine(DiagID, new DiagnosticOptions,
537 DiagClient, /*ShouldOwnClient=*/false));
539 // Filter of all diagnostics.
540 CaptureDiagnosticConsumer errRec(*Diags, *DiagClient, capturedDiags);
541 Diags->setClient(&errRec, /*ShouldOwnClient=*/false);
543 std::unique_ptr<ARCMTMacroTrackerAction> ASTAction;
544 ASTAction.reset(new ARCMTMacroTrackerAction(ARCMTMacroLocs));
546 std::unique_ptr<ASTUnit> Unit(ASTUnit::LoadFromCompilerInvocationAction(
547 std::move(CInvok), PCHContainerOps, Diags, ASTAction.get()));
549 errRec.FinishCapture();
552 Unit->setOwnsRemappedFileBuffers(false); // FileRemapper manages that.
554 HadARCErrors = HadARCErrors || capturedDiags.hasErrors();
556 // Don't filter diagnostics anymore.
557 Diags->setClient(DiagClient, /*ShouldOwnClient=*/false);
559 ASTContext &Ctx = Unit->getASTContext();
561 if (Diags->hasFatalErrorOccurred()) {
563 DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());
564 capturedDiags.reportDiagnostics(*Diags);
565 DiagClient->EndSourceFile();
566 errRec.FinishCapture();
570 // After parsing of source files ended, we want to reuse the
571 // diagnostics objects to emit further diagnostics.
572 // We call BeginSourceFile because DiagnosticConsumer requires that
573 // diagnostics with source range information are emitted only in between
574 // BeginSourceFile() and EndSourceFile().
575 DiagClient->BeginSourceFile(Ctx.getLangOpts(), &Unit->getPreprocessor());
577 Rewriter rewriter(Ctx.getSourceManager(), Ctx.getLangOpts());
578 TransformActions TA(*Diags, capturedDiags, Ctx, Unit->getPreprocessor());
579 MigrationPass pass(Ctx, OrigCI.getLangOpts()->getGC(),
580 Unit->getSema(), TA, capturedDiags, ARCMTMacroLocs);
585 RewritesApplicator applicator(rewriter, Ctx, listener);
586 TA.applyRewrites(applicator);
589 DiagClient->EndSourceFile();
590 errRec.FinishCapture();
592 if (DiagClient->getNumErrors())
595 for (Rewriter::buffer_iterator
596 I = rewriter.buffer_begin(), E = rewriter.buffer_end(); I != E; ++I) {
597 FileID FID = I->first;
598 RewriteBuffer &buf = I->second;
599 const FileEntry *file = Ctx.getSourceManager().getFileEntryForID(FID);
601 std::string newFname = file->getName();
602 newFname += "-trans";
603 SmallString<512> newText;
604 llvm::raw_svector_ostream vecOS(newText);
606 std::unique_ptr<llvm::MemoryBuffer> memBuf(
607 llvm::MemoryBuffer::getMemBufferCopy(
608 StringRef(newText.data(), newText.size()), newFname));
609 SmallString<64> filePath(file->getName());
610 Unit->getFileManager().FixupRelativePath(filePath);
611 Remapper.remap(filePath.str(), std::move(memBuf));