1 //===--- ARCMT.cpp - Migration to ARC mode --------------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "Internals.h" 11 #include "clang/Frontend/ASTUnit.h" 12 #include "clang/Frontend/CompilerInstance.h" 13 #include "clang/Frontend/TextDiagnosticPrinter.h" 14 #include "clang/Frontend/Utils.h" 15 #include "clang/AST/ASTConsumer.h" 16 #include "clang/Rewrite/Rewriter.h" 17 #include "clang/Sema/SemaDiagnostic.h" 18 #include "clang/Basic/DiagnosticCategories.h" 19 #include "clang/Lex/Preprocessor.h" 20 #include "llvm/Support/MemoryBuffer.h" 21 #include "llvm/ADT/Triple.h" 22 using namespace clang; 23 using namespace arcmt; 24 25 bool CapturedDiagList::clearDiagnostic(ArrayRef<unsigned> IDs, 26 SourceRange range) { 27 if (range.isInvalid()) 28 return false; 29 30 bool cleared = false; 31 ListTy::iterator I = List.begin(); 32 while (I != List.end()) { 33 FullSourceLoc diagLoc = I->getLocation(); 34 if ((IDs.empty() || // empty means clear all diagnostics in the range. 35 std::find(IDs.begin(), IDs.end(), I->getID()) != IDs.end()) && 36 !diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) && 37 (diagLoc == range.getEnd() || 38 diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) { 39 cleared = true; 40 ListTy::iterator eraseS = I++; 41 while (I != List.end() && I->getLevel() == DiagnosticsEngine::Note) 42 ++I; 43 // Clear the diagnostic and any notes following it. 44 List.erase(eraseS, I); 45 continue; 46 } 47 48 ++I; 49 } 50 51 return cleared; 52 } 53 54 bool CapturedDiagList::hasDiagnostic(ArrayRef<unsigned> IDs, 55 SourceRange range) const { 56 if (range.isInvalid()) 57 return false; 58 59 ListTy::const_iterator I = List.begin(); 60 while (I != List.end()) { 61 FullSourceLoc diagLoc = I->getLocation(); 62 if ((IDs.empty() || // empty means any diagnostic in the range. 63 std::find(IDs.begin(), IDs.end(), I->getID()) != IDs.end()) && 64 !diagLoc.isBeforeInTranslationUnitThan(range.getBegin()) && 65 (diagLoc == range.getEnd() || 66 diagLoc.isBeforeInTranslationUnitThan(range.getEnd()))) { 67 return true; 68 } 69 70 ++I; 71 } 72 73 return false; 74 } 75 76 void CapturedDiagList::reportDiagnostics(DiagnosticsEngine &Diags) const { 77 for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I) 78 Diags.Report(*I); 79 } 80 81 bool CapturedDiagList::hasErrors() const { 82 for (ListTy::const_iterator I = List.begin(), E = List.end(); I != E; ++I) 83 if (I->getLevel() >= DiagnosticsEngine::Error) 84 return true; 85 86 return false; 87 } 88 89 namespace { 90 91 class CaptureDiagnosticConsumer : public DiagnosticConsumer { 92 DiagnosticsEngine &Diags; 93 CapturedDiagList &CapturedDiags; 94 public: 95 CaptureDiagnosticConsumer(DiagnosticsEngine &diags, 96 CapturedDiagList &capturedDiags) 97 : Diags(diags), CapturedDiags(capturedDiags) { } 98 99 virtual void HandleDiagnostic(DiagnosticsEngine::Level level, 100 const Diagnostic &Info) { 101 if (arcmt::isARCDiagnostic(Info.getID(), Diags) || 102 level >= DiagnosticsEngine::Error || level == DiagnosticsEngine::Note) { 103 CapturedDiags.push_back(StoredDiagnostic(level, Info)); 104 return; 105 } 106 107 // Non-ARC warnings are ignored. 108 Diags.setLastDiagnosticIgnored(); 109 } 110 111 DiagnosticConsumer *clone(DiagnosticsEngine &Diags) const { 112 // Just drop any diagnostics that come from cloned consumers; they'll 113 // have different source managers anyway. 114 return new IgnoringDiagConsumer(); 115 } 116 }; 117 118 } // end anonymous namespace 119 120 static inline StringRef SimulatorVersionDefineName() { 121 return "__IPHONE_OS_VERSION_MIN_REQUIRED="; 122 } 123 124 /// \brief Parse the simulator version define: 125 /// __IPHONE_OS_VERSION_MIN_REQUIRED=([0-9])([0-9][0-9])([0-9][0-9]) 126 // and return the grouped values as integers, e.g: 127 // __IPHONE_OS_VERSION_MIN_REQUIRED=40201 128 // will return Major=4, Minor=2, Micro=1. 129 static bool GetVersionFromSimulatorDefine(StringRef define, 130 unsigned &Major, unsigned &Minor, 131 unsigned &Micro) { 132 assert(define.startswith(SimulatorVersionDefineName())); 133 StringRef name, version; 134 llvm::tie(name, version) = define.split('='); 135 if (version.empty()) 136 return false; 137 std::string verstr = version.str(); 138 char *end; 139 unsigned num = (unsigned) strtol(verstr.c_str(), &end, 10); 140 if (*end != '\0') 141 return false; 142 Major = num / 10000; 143 num = num % 10000; 144 Minor = num / 100; 145 Micro = num % 100; 146 return true; 147 } 148 149 static bool HasARCRuntime(CompilerInvocation &origCI) { 150 // This duplicates some functionality from Darwin::AddDeploymentTarget 151 // but this function is well defined, so keep it decoupled from the driver 152 // and avoid unrelated complications. 153 154 for (unsigned i = 0, e = origCI.getPreprocessorOpts().Macros.size(); 155 i != e; ++i) { 156 StringRef define = origCI.getPreprocessorOpts().Macros[i].first; 157 bool isUndef = origCI.getPreprocessorOpts().Macros[i].second; 158 if (isUndef) 159 continue; 160 if (!define.startswith(SimulatorVersionDefineName())) 161 continue; 162 unsigned Major = 0, Minor = 0, Micro = 0; 163 if (GetVersionFromSimulatorDefine(define, Major, Minor, Micro) && 164 Major < 10 && Minor < 100 && Micro < 100) 165 return Major >= 5; 166 } 167 168 llvm::Triple triple(origCI.getTargetOpts().Triple); 169 170 if (triple.getOS() == llvm::Triple::IOS) 171 return triple.getOSMajorVersion() >= 5; 172 173 if (triple.getOS() == llvm::Triple::Darwin) 174 return triple.getOSMajorVersion() >= 11; 175 176 if (triple.getOS() == llvm::Triple::MacOSX) { 177 unsigned Major, Minor, Micro; 178 triple.getOSVersion(Major, Minor, Micro); 179 return Major > 10 || (Major == 10 && Minor >= 7); 180 } 181 182 return false; 183 } 184 185 static CompilerInvocation * 186 createInvocationForMigration(CompilerInvocation &origCI) { 187 llvm::OwningPtr<CompilerInvocation> CInvok; 188 CInvok.reset(new CompilerInvocation(origCI)); 189 CInvok->getPreprocessorOpts().ImplicitPCHInclude = std::string(); 190 CInvok->getPreprocessorOpts().ImplicitPTHInclude = std::string(); 191 std::string define = getARCMTMacroName(); 192 define += '='; 193 CInvok->getPreprocessorOpts().addMacroDef(define); 194 CInvok->getLangOpts().ObjCAutoRefCount = true; 195 CInvok->getDiagnosticOpts().ErrorLimit = 0; 196 CInvok->getDiagnosticOpts().Warnings.push_back( 197 "error=arc-unsafe-retained-assign"); 198 CInvok->getLangOpts().ObjCRuntimeHasWeak = HasARCRuntime(origCI); 199 200 return CInvok.take(); 201 } 202 203 static void emitPremigrationErrors(const CapturedDiagList &arcDiags, 204 const DiagnosticOptions &diagOpts, 205 Preprocessor &PP) { 206 TextDiagnosticPrinter printer(llvm::errs(), diagOpts); 207 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 208 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 209 new DiagnosticsEngine(DiagID, &printer, /*ShouldOwnClient=*/false)); 210 Diags->setSourceManager(&PP.getSourceManager()); 211 212 printer.BeginSourceFile(PP.getLangOptions(), &PP); 213 arcDiags.reportDiagnostics(*Diags); 214 printer.EndSourceFile(); 215 } 216 217 //===----------------------------------------------------------------------===// 218 // checkForManualIssues. 219 //===----------------------------------------------------------------------===// 220 221 bool arcmt::checkForManualIssues(CompilerInvocation &origCI, 222 StringRef Filename, InputKind Kind, 223 DiagnosticConsumer *DiagClient, 224 bool emitPremigrationARCErrors, 225 StringRef plistOut) { 226 if (!origCI.getLangOpts().ObjC1) 227 return false; 228 229 std::vector<TransformFn> transforms = arcmt::getAllTransformations(); 230 assert(!transforms.empty()); 231 232 llvm::OwningPtr<CompilerInvocation> CInvok; 233 CInvok.reset(createInvocationForMigration(origCI)); 234 CInvok->getFrontendOpts().Inputs.clear(); 235 CInvok->getFrontendOpts().Inputs.push_back(std::make_pair(Kind, Filename)); 236 237 CapturedDiagList capturedDiags; 238 239 assert(DiagClient); 240 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 241 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 242 new DiagnosticsEngine(DiagID, DiagClient, /*ShouldOwnClient=*/false)); 243 244 // Filter of all diagnostics. 245 CaptureDiagnosticConsumer errRec(*Diags, capturedDiags); 246 Diags->setClient(&errRec, /*ShouldOwnClient=*/false); 247 248 llvm::OwningPtr<ASTUnit> Unit( 249 ASTUnit::LoadFromCompilerInvocationAction(CInvok.take(), Diags)); 250 if (!Unit) 251 return true; 252 253 // Don't filter diagnostics anymore. 254 Diags->setClient(DiagClient, /*ShouldOwnClient=*/false); 255 256 ASTContext &Ctx = Unit->getASTContext(); 257 258 if (Diags->hasFatalErrorOccurred()) { 259 Diags->Reset(); 260 DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); 261 capturedDiags.reportDiagnostics(*Diags); 262 DiagClient->EndSourceFile(); 263 return true; 264 } 265 266 if (emitPremigrationARCErrors) 267 emitPremigrationErrors(capturedDiags, origCI.getDiagnosticOpts(), 268 Unit->getPreprocessor()); 269 if (!plistOut.empty()) { 270 SmallVector<StoredDiagnostic, 8> arcDiags; 271 for (CapturedDiagList::iterator 272 I = capturedDiags.begin(), E = capturedDiags.end(); I != E; ++I) 273 arcDiags.push_back(*I); 274 writeARCDiagsToPlist(plistOut, arcDiags, 275 Ctx.getSourceManager(), Ctx.getLangOptions()); 276 } 277 278 // After parsing of source files ended, we want to reuse the 279 // diagnostics objects to emit further diagnostics. 280 // We call BeginSourceFile because DiagnosticConsumer requires that 281 // diagnostics with source range information are emitted only in between 282 // BeginSourceFile() and EndSourceFile(). 283 DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); 284 285 // No macros will be added since we are just checking and we won't modify 286 // source code. 287 std::vector<SourceLocation> ARCMTMacroLocs; 288 289 TransformActions testAct(*Diags, capturedDiags, Ctx, Unit->getPreprocessor()); 290 MigrationPass pass(Ctx, Unit->getSema(), testAct, ARCMTMacroLocs); 291 292 for (unsigned i=0, e = transforms.size(); i != e; ++i) 293 transforms[i](pass); 294 295 capturedDiags.reportDiagnostics(*Diags); 296 297 DiagClient->EndSourceFile(); 298 299 // If we are migrating code that gets the '-fobjc-arc' flag, make sure 300 // to remove it so that we don't get errors from normal compilation. 301 origCI.getLangOpts().ObjCAutoRefCount = false; 302 303 return capturedDiags.hasErrors() || testAct.hasReportedErrors(); 304 } 305 306 //===----------------------------------------------------------------------===// 307 // applyTransformations. 308 //===----------------------------------------------------------------------===// 309 310 static bool applyTransforms(CompilerInvocation &origCI, 311 StringRef Filename, InputKind Kind, 312 DiagnosticConsumer *DiagClient, 313 StringRef outputDir, 314 bool emitPremigrationARCErrors, 315 StringRef plistOut) { 316 if (!origCI.getLangOpts().ObjC1) 317 return false; 318 319 // Make sure checking is successful first. 320 CompilerInvocation CInvokForCheck(origCI); 321 if (arcmt::checkForManualIssues(CInvokForCheck, Filename, Kind, DiagClient, 322 emitPremigrationARCErrors, plistOut)) 323 return true; 324 325 CompilerInvocation CInvok(origCI); 326 CInvok.getFrontendOpts().Inputs.clear(); 327 CInvok.getFrontendOpts().Inputs.push_back(std::make_pair(Kind, Filename)); 328 329 MigrationProcess migration(CInvok, DiagClient, outputDir); 330 331 std::vector<TransformFn> transforms = arcmt::getAllTransformations(); 332 assert(!transforms.empty()); 333 334 for (unsigned i=0, e = transforms.size(); i != e; ++i) { 335 bool err = migration.applyTransform(transforms[i]); 336 if (err) return true; 337 } 338 339 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 340 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 341 new DiagnosticsEngine(DiagID, DiagClient, /*ShouldOwnClient=*/false)); 342 343 if (outputDir.empty()) { 344 origCI.getLangOpts().ObjCAutoRefCount = true; 345 return migration.getRemapper().overwriteOriginal(*Diags); 346 } else { 347 // If we are migrating code that gets the '-fobjc-arc' flag, make sure 348 // to remove it so that we don't get errors from normal compilation. 349 origCI.getLangOpts().ObjCAutoRefCount = false; 350 return migration.getRemapper().flushToDisk(outputDir, *Diags); 351 } 352 } 353 354 bool arcmt::applyTransformations(CompilerInvocation &origCI, 355 StringRef Filename, InputKind Kind, 356 DiagnosticConsumer *DiagClient) { 357 return applyTransforms(origCI, Filename, Kind, DiagClient, 358 StringRef(), false, StringRef()); 359 } 360 361 bool arcmt::migrateWithTemporaryFiles(CompilerInvocation &origCI, 362 StringRef Filename, InputKind Kind, 363 DiagnosticConsumer *DiagClient, 364 StringRef outputDir, 365 bool emitPremigrationARCErrors, 366 StringRef plistOut) { 367 assert(!outputDir.empty() && "Expected output directory path"); 368 return applyTransforms(origCI, Filename, Kind, DiagClient, 369 outputDir, emitPremigrationARCErrors, plistOut); 370 } 371 372 bool arcmt::getFileRemappings(std::vector<std::pair<std::string,std::string> > & 373 remap, 374 StringRef outputDir, 375 DiagnosticConsumer *DiagClient) { 376 assert(!outputDir.empty()); 377 378 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 379 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 380 new DiagnosticsEngine(DiagID, DiagClient, /*ShouldOwnClient=*/false)); 381 382 FileRemapper remapper; 383 bool err = remapper.initFromDisk(outputDir, *Diags, 384 /*ignoreIfFilesChanged=*/true); 385 if (err) 386 return true; 387 388 CompilerInvocation CI; 389 remapper.applyMappings(CI); 390 remap = CI.getPreprocessorOpts().RemappedFiles; 391 392 return false; 393 } 394 395 //===----------------------------------------------------------------------===// 396 // CollectTransformActions. 397 //===----------------------------------------------------------------------===// 398 399 namespace { 400 401 class ARCMTMacroTrackerPPCallbacks : public PPCallbacks { 402 std::vector<SourceLocation> &ARCMTMacroLocs; 403 404 public: 405 ARCMTMacroTrackerPPCallbacks(std::vector<SourceLocation> &ARCMTMacroLocs) 406 : ARCMTMacroLocs(ARCMTMacroLocs) { } 407 408 virtual void MacroExpands(const Token &MacroNameTok, const MacroInfo *MI, 409 SourceRange Range) { 410 if (MacroNameTok.getIdentifierInfo()->getName() == getARCMTMacroName()) 411 ARCMTMacroLocs.push_back(MacroNameTok.getLocation()); 412 } 413 }; 414 415 class ARCMTMacroTrackerAction : public ASTFrontendAction { 416 std::vector<SourceLocation> &ARCMTMacroLocs; 417 418 public: 419 ARCMTMacroTrackerAction(std::vector<SourceLocation> &ARCMTMacroLocs) 420 : ARCMTMacroLocs(ARCMTMacroLocs) { } 421 422 virtual ASTConsumer *CreateASTConsumer(CompilerInstance &CI, 423 StringRef InFile) { 424 CI.getPreprocessor().addPPCallbacks( 425 new ARCMTMacroTrackerPPCallbacks(ARCMTMacroLocs)); 426 return new ASTConsumer(); 427 } 428 }; 429 430 class RewritesApplicator : public TransformActions::RewriteReceiver { 431 Rewriter &rewriter; 432 ASTContext &Ctx; 433 MigrationProcess::RewriteListener *Listener; 434 435 public: 436 RewritesApplicator(Rewriter &rewriter, ASTContext &ctx, 437 MigrationProcess::RewriteListener *listener) 438 : rewriter(rewriter), Ctx(ctx), Listener(listener) { 439 if (Listener) 440 Listener->start(ctx); 441 } 442 ~RewritesApplicator() { 443 if (Listener) 444 Listener->finish(); 445 } 446 447 virtual void insert(SourceLocation loc, StringRef text) { 448 bool err = rewriter.InsertText(loc, text, /*InsertAfter=*/true, 449 /*indentNewLines=*/true); 450 if (!err && Listener) 451 Listener->insert(loc, text); 452 } 453 454 virtual void remove(CharSourceRange range) { 455 Rewriter::RewriteOptions removeOpts; 456 removeOpts.IncludeInsertsAtBeginOfRange = false; 457 removeOpts.IncludeInsertsAtEndOfRange = false; 458 removeOpts.RemoveLineIfEmpty = true; 459 460 bool err = rewriter.RemoveText(range, removeOpts); 461 if (!err && Listener) 462 Listener->remove(range); 463 } 464 465 virtual void increaseIndentation(CharSourceRange range, 466 SourceLocation parentIndent) { 467 rewriter.IncreaseIndentation(range, parentIndent); 468 } 469 }; 470 471 } // end anonymous namespace. 472 473 /// \brief Anchor for VTable. 474 MigrationProcess::RewriteListener::~RewriteListener() { } 475 476 MigrationProcess::MigrationProcess(const CompilerInvocation &CI, 477 DiagnosticConsumer *diagClient, 478 StringRef outputDir) 479 : OrigCI(CI), DiagClient(diagClient) { 480 if (!outputDir.empty()) { 481 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 482 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 483 new DiagnosticsEngine(DiagID, DiagClient, /*ShouldOwnClient=*/false)); 484 Remapper.initFromDisk(outputDir, *Diags, /*ignoreIfFilesChanges=*/true); 485 } 486 } 487 488 bool MigrationProcess::applyTransform(TransformFn trans, 489 RewriteListener *listener) { 490 llvm::OwningPtr<CompilerInvocation> CInvok; 491 CInvok.reset(createInvocationForMigration(OrigCI)); 492 CInvok->getDiagnosticOpts().IgnoreWarnings = true; 493 494 Remapper.applyMappings(*CInvok); 495 496 CapturedDiagList capturedDiags; 497 std::vector<SourceLocation> ARCMTMacroLocs; 498 499 assert(DiagClient); 500 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 501 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 502 new DiagnosticsEngine(DiagID, DiagClient, /*ShouldOwnClient=*/false)); 503 504 // Filter of all diagnostics. 505 CaptureDiagnosticConsumer errRec(*Diags, capturedDiags); 506 Diags->setClient(&errRec, /*ShouldOwnClient=*/false); 507 508 llvm::OwningPtr<ARCMTMacroTrackerAction> ASTAction; 509 ASTAction.reset(new ARCMTMacroTrackerAction(ARCMTMacroLocs)); 510 511 llvm::OwningPtr<ASTUnit> Unit( 512 ASTUnit::LoadFromCompilerInvocationAction(CInvok.take(), Diags, 513 ASTAction.get())); 514 if (!Unit) 515 return true; 516 Unit->setOwnsRemappedFileBuffers(false); // FileRemapper manages that. 517 518 // Don't filter diagnostics anymore. 519 Diags->setClient(DiagClient, /*ShouldOwnClient=*/false); 520 521 ASTContext &Ctx = Unit->getASTContext(); 522 523 if (Diags->hasFatalErrorOccurred()) { 524 Diags->Reset(); 525 DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); 526 capturedDiags.reportDiagnostics(*Diags); 527 DiagClient->EndSourceFile(); 528 return true; 529 } 530 531 // After parsing of source files ended, we want to reuse the 532 // diagnostics objects to emit further diagnostics. 533 // We call BeginSourceFile because DiagnosticConsumer requires that 534 // diagnostics with source range information are emitted only in between 535 // BeginSourceFile() and EndSourceFile(). 536 DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); 537 538 Rewriter rewriter(Ctx.getSourceManager(), Ctx.getLangOptions()); 539 TransformActions TA(*Diags, capturedDiags, Ctx, Unit->getPreprocessor()); 540 MigrationPass pass(Ctx, Unit->getSema(), TA, ARCMTMacroLocs); 541 542 trans(pass); 543 544 { 545 RewritesApplicator applicator(rewriter, Ctx, listener); 546 TA.applyRewrites(applicator); 547 } 548 549 DiagClient->EndSourceFile(); 550 551 if (DiagClient->getNumErrors()) 552 return true; 553 554 for (Rewriter::buffer_iterator 555 I = rewriter.buffer_begin(), E = rewriter.buffer_end(); I != E; ++I) { 556 FileID FID = I->first; 557 RewriteBuffer &buf = I->second; 558 const FileEntry *file = Ctx.getSourceManager().getFileEntryForID(FID); 559 assert(file); 560 std::string newFname = file->getName(); 561 newFname += "-trans"; 562 llvm::SmallString<512> newText; 563 llvm::raw_svector_ostream vecOS(newText); 564 buf.write(vecOS); 565 vecOS.flush(); 566 llvm::MemoryBuffer *memBuf = llvm::MemoryBuffer::getMemBufferCopy( 567 StringRef(newText.data(), newText.size()), newFname); 568 llvm::SmallString<64> filePath(file->getName()); 569 Unit->getFileManager().FixupRelativePath(filePath); 570 Remapper.remap(filePath.str(), memBuf); 571 } 572 573 return false; 574 } 575 576 //===----------------------------------------------------------------------===// 577 // isARCDiagnostic. 578 //===----------------------------------------------------------------------===// 579 580 bool arcmt::isARCDiagnostic(unsigned diagID, DiagnosticsEngine &Diag) { 581 return Diag.getDiagnosticIDs()->getCategoryNumberForDiag(diagID) == 582 diag::DiagCat_Automatic_Reference_Counting_Issue; 583 } 584