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 (DiagnosticIDs::isARCDiagnostic(Info.getID()) || 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->getLangOpts().setGC(LangOptions::NonGC); 196 CInvok->getDiagnosticOpts().ErrorLimit = 0; 197 CInvok->getDiagnosticOpts().Warnings.push_back( 198 "error=arc-unsafe-retained-assign"); 199 CInvok->getLangOpts().ObjCRuntimeHasWeak = HasARCRuntime(origCI); 200 201 return CInvok.take(); 202 } 203 204 static void emitPremigrationErrors(const CapturedDiagList &arcDiags, 205 const DiagnosticOptions &diagOpts, 206 Preprocessor &PP) { 207 TextDiagnosticPrinter printer(llvm::errs(), diagOpts); 208 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 209 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 210 new DiagnosticsEngine(DiagID, &printer, /*ShouldOwnClient=*/false)); 211 Diags->setSourceManager(&PP.getSourceManager()); 212 213 printer.BeginSourceFile(PP.getLangOptions(), &PP); 214 arcDiags.reportDiagnostics(*Diags); 215 printer.EndSourceFile(); 216 } 217 218 //===----------------------------------------------------------------------===// 219 // checkForManualIssues. 220 //===----------------------------------------------------------------------===// 221 222 bool arcmt::checkForManualIssues(CompilerInvocation &origCI, 223 StringRef Filename, InputKind Kind, 224 DiagnosticConsumer *DiagClient, 225 bool emitPremigrationARCErrors, 226 StringRef plistOut) { 227 if (!origCI.getLangOpts().ObjC1) 228 return false; 229 230 LangOptions::GCMode OrigGCMode = origCI.getLangOpts().getGC(); 231 232 std::vector<TransformFn> transforms = arcmt::getAllTransformations(OrigGCMode); 233 assert(!transforms.empty()); 234 235 llvm::OwningPtr<CompilerInvocation> CInvok; 236 CInvok.reset(createInvocationForMigration(origCI)); 237 CInvok->getFrontendOpts().Inputs.clear(); 238 CInvok->getFrontendOpts().Inputs.push_back(std::make_pair(Kind, Filename)); 239 240 CapturedDiagList capturedDiags; 241 242 assert(DiagClient); 243 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 244 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 245 new DiagnosticsEngine(DiagID, DiagClient, /*ShouldOwnClient=*/false)); 246 247 // Filter of all diagnostics. 248 CaptureDiagnosticConsumer errRec(*Diags, capturedDiags); 249 Diags->setClient(&errRec, /*ShouldOwnClient=*/false); 250 251 llvm::OwningPtr<ASTUnit> Unit( 252 ASTUnit::LoadFromCompilerInvocationAction(CInvok.take(), Diags)); 253 if (!Unit) 254 return true; 255 256 // Don't filter diagnostics anymore. 257 Diags->setClient(DiagClient, /*ShouldOwnClient=*/false); 258 259 ASTContext &Ctx = Unit->getASTContext(); 260 261 if (Diags->hasFatalErrorOccurred()) { 262 Diags->Reset(); 263 DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); 264 capturedDiags.reportDiagnostics(*Diags); 265 DiagClient->EndSourceFile(); 266 return true; 267 } 268 269 if (emitPremigrationARCErrors) 270 emitPremigrationErrors(capturedDiags, origCI.getDiagnosticOpts(), 271 Unit->getPreprocessor()); 272 if (!plistOut.empty()) { 273 SmallVector<StoredDiagnostic, 8> arcDiags; 274 for (CapturedDiagList::iterator 275 I = capturedDiags.begin(), E = capturedDiags.end(); I != E; ++I) 276 arcDiags.push_back(*I); 277 writeARCDiagsToPlist(plistOut, arcDiags, 278 Ctx.getSourceManager(), Ctx.getLangOptions()); 279 } 280 281 // After parsing of source files ended, we want to reuse the 282 // diagnostics objects to emit further diagnostics. 283 // We call BeginSourceFile because DiagnosticConsumer requires that 284 // diagnostics with source range information are emitted only in between 285 // BeginSourceFile() and EndSourceFile(). 286 DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); 287 288 // No macros will be added since we are just checking and we won't modify 289 // source code. 290 std::vector<SourceLocation> ARCMTMacroLocs; 291 292 TransformActions testAct(*Diags, capturedDiags, Ctx, Unit->getPreprocessor()); 293 MigrationPass pass(Ctx, OrigGCMode, Unit->getSema(), testAct, ARCMTMacroLocs); 294 295 for (unsigned i=0, e = transforms.size(); i != e; ++i) 296 transforms[i](pass); 297 298 capturedDiags.reportDiagnostics(*Diags); 299 300 DiagClient->EndSourceFile(); 301 302 // If we are migrating code that gets the '-fobjc-arc' flag, make sure 303 // to remove it so that we don't get errors from normal compilation. 304 origCI.getLangOpts().ObjCAutoRefCount = false; 305 306 return capturedDiags.hasErrors() || testAct.hasReportedErrors(); 307 } 308 309 //===----------------------------------------------------------------------===// 310 // applyTransformations. 311 //===----------------------------------------------------------------------===// 312 313 static bool applyTransforms(CompilerInvocation &origCI, 314 StringRef Filename, InputKind Kind, 315 DiagnosticConsumer *DiagClient, 316 StringRef outputDir, 317 bool emitPremigrationARCErrors, 318 StringRef plistOut) { 319 if (!origCI.getLangOpts().ObjC1) 320 return false; 321 322 LangOptions::GCMode OrigGCMode = origCI.getLangOpts().getGC(); 323 324 // Make sure checking is successful first. 325 CompilerInvocation CInvokForCheck(origCI); 326 if (arcmt::checkForManualIssues(CInvokForCheck, Filename, Kind, DiagClient, 327 emitPremigrationARCErrors, plistOut)) 328 return true; 329 330 CompilerInvocation CInvok(origCI); 331 CInvok.getFrontendOpts().Inputs.clear(); 332 CInvok.getFrontendOpts().Inputs.push_back(std::make_pair(Kind, Filename)); 333 334 MigrationProcess migration(CInvok, DiagClient, outputDir); 335 336 std::vector<TransformFn> transforms = arcmt::getAllTransformations(OrigGCMode); 337 assert(!transforms.empty()); 338 339 for (unsigned i=0, e = transforms.size(); i != e; ++i) { 340 bool err = migration.applyTransform(transforms[i]); 341 if (err) return true; 342 } 343 344 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 345 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 346 new DiagnosticsEngine(DiagID, DiagClient, /*ShouldOwnClient=*/false)); 347 348 if (outputDir.empty()) { 349 origCI.getLangOpts().ObjCAutoRefCount = true; 350 return migration.getRemapper().overwriteOriginal(*Diags); 351 } else { 352 // If we are migrating code that gets the '-fobjc-arc' flag, make sure 353 // to remove it so that we don't get errors from normal compilation. 354 origCI.getLangOpts().ObjCAutoRefCount = false; 355 return migration.getRemapper().flushToDisk(outputDir, *Diags); 356 } 357 } 358 359 bool arcmt::applyTransformations(CompilerInvocation &origCI, 360 StringRef Filename, InputKind Kind, 361 DiagnosticConsumer *DiagClient) { 362 return applyTransforms(origCI, Filename, Kind, DiagClient, 363 StringRef(), false, StringRef()); 364 } 365 366 bool arcmt::migrateWithTemporaryFiles(CompilerInvocation &origCI, 367 StringRef Filename, InputKind Kind, 368 DiagnosticConsumer *DiagClient, 369 StringRef outputDir, 370 bool emitPremigrationARCErrors, 371 StringRef plistOut) { 372 assert(!outputDir.empty() && "Expected output directory path"); 373 return applyTransforms(origCI, Filename, Kind, DiagClient, 374 outputDir, emitPremigrationARCErrors, plistOut); 375 } 376 377 bool arcmt::getFileRemappings(std::vector<std::pair<std::string,std::string> > & 378 remap, 379 StringRef outputDir, 380 DiagnosticConsumer *DiagClient) { 381 assert(!outputDir.empty()); 382 383 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 384 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 385 new DiagnosticsEngine(DiagID, DiagClient, /*ShouldOwnClient=*/false)); 386 387 FileRemapper remapper; 388 bool err = remapper.initFromDisk(outputDir, *Diags, 389 /*ignoreIfFilesChanged=*/true); 390 if (err) 391 return true; 392 393 CompilerInvocation CI; 394 remapper.applyMappings(CI); 395 remap = CI.getPreprocessorOpts().RemappedFiles; 396 397 return false; 398 } 399 400 //===----------------------------------------------------------------------===// 401 // CollectTransformActions. 402 //===----------------------------------------------------------------------===// 403 404 namespace { 405 406 class ARCMTMacroTrackerPPCallbacks : public PPCallbacks { 407 std::vector<SourceLocation> &ARCMTMacroLocs; 408 409 public: 410 ARCMTMacroTrackerPPCallbacks(std::vector<SourceLocation> &ARCMTMacroLocs) 411 : ARCMTMacroLocs(ARCMTMacroLocs) { } 412 413 virtual void MacroExpands(const Token &MacroNameTok, const MacroInfo *MI, 414 SourceRange Range) { 415 if (MacroNameTok.getIdentifierInfo()->getName() == getARCMTMacroName()) 416 ARCMTMacroLocs.push_back(MacroNameTok.getLocation()); 417 } 418 }; 419 420 class ARCMTMacroTrackerAction : public ASTFrontendAction { 421 std::vector<SourceLocation> &ARCMTMacroLocs; 422 423 public: 424 ARCMTMacroTrackerAction(std::vector<SourceLocation> &ARCMTMacroLocs) 425 : ARCMTMacroLocs(ARCMTMacroLocs) { } 426 427 virtual ASTConsumer *CreateASTConsumer(CompilerInstance &CI, 428 StringRef InFile) { 429 CI.getPreprocessor().addPPCallbacks( 430 new ARCMTMacroTrackerPPCallbacks(ARCMTMacroLocs)); 431 return new ASTConsumer(); 432 } 433 }; 434 435 class RewritesApplicator : public TransformActions::RewriteReceiver { 436 Rewriter &rewriter; 437 ASTContext &Ctx; 438 MigrationProcess::RewriteListener *Listener; 439 440 public: 441 RewritesApplicator(Rewriter &rewriter, ASTContext &ctx, 442 MigrationProcess::RewriteListener *listener) 443 : rewriter(rewriter), Ctx(ctx), Listener(listener) { 444 if (Listener) 445 Listener->start(ctx); 446 } 447 ~RewritesApplicator() { 448 if (Listener) 449 Listener->finish(); 450 } 451 452 virtual void insert(SourceLocation loc, StringRef text) { 453 bool err = rewriter.InsertText(loc, text, /*InsertAfter=*/true, 454 /*indentNewLines=*/true); 455 if (!err && Listener) 456 Listener->insert(loc, text); 457 } 458 459 virtual void remove(CharSourceRange range) { 460 Rewriter::RewriteOptions removeOpts; 461 removeOpts.IncludeInsertsAtBeginOfRange = false; 462 removeOpts.IncludeInsertsAtEndOfRange = false; 463 removeOpts.RemoveLineIfEmpty = true; 464 465 bool err = rewriter.RemoveText(range, removeOpts); 466 if (!err && Listener) 467 Listener->remove(range); 468 } 469 470 virtual void increaseIndentation(CharSourceRange range, 471 SourceLocation parentIndent) { 472 rewriter.IncreaseIndentation(range, parentIndent); 473 } 474 }; 475 476 } // end anonymous namespace. 477 478 /// \brief Anchor for VTable. 479 MigrationProcess::RewriteListener::~RewriteListener() { } 480 481 MigrationProcess::MigrationProcess(const CompilerInvocation &CI, 482 DiagnosticConsumer *diagClient, 483 StringRef outputDir) 484 : OrigCI(CI), DiagClient(diagClient) { 485 if (!outputDir.empty()) { 486 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 487 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 488 new DiagnosticsEngine(DiagID, DiagClient, /*ShouldOwnClient=*/false)); 489 Remapper.initFromDisk(outputDir, *Diags, /*ignoreIfFilesChanges=*/true); 490 } 491 } 492 493 bool MigrationProcess::applyTransform(TransformFn trans, 494 RewriteListener *listener) { 495 llvm::OwningPtr<CompilerInvocation> CInvok; 496 CInvok.reset(createInvocationForMigration(OrigCI)); 497 CInvok->getDiagnosticOpts().IgnoreWarnings = true; 498 499 Remapper.applyMappings(*CInvok); 500 501 CapturedDiagList capturedDiags; 502 std::vector<SourceLocation> ARCMTMacroLocs; 503 504 assert(DiagClient); 505 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 506 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 507 new DiagnosticsEngine(DiagID, DiagClient, /*ShouldOwnClient=*/false)); 508 509 // Filter of all diagnostics. 510 CaptureDiagnosticConsumer errRec(*Diags, capturedDiags); 511 Diags->setClient(&errRec, /*ShouldOwnClient=*/false); 512 513 llvm::OwningPtr<ARCMTMacroTrackerAction> ASTAction; 514 ASTAction.reset(new ARCMTMacroTrackerAction(ARCMTMacroLocs)); 515 516 llvm::OwningPtr<ASTUnit> Unit( 517 ASTUnit::LoadFromCompilerInvocationAction(CInvok.take(), Diags, 518 ASTAction.get())); 519 if (!Unit) 520 return true; 521 Unit->setOwnsRemappedFileBuffers(false); // FileRemapper manages that. 522 523 // Don't filter diagnostics anymore. 524 Diags->setClient(DiagClient, /*ShouldOwnClient=*/false); 525 526 ASTContext &Ctx = Unit->getASTContext(); 527 528 if (Diags->hasFatalErrorOccurred()) { 529 Diags->Reset(); 530 DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); 531 capturedDiags.reportDiagnostics(*Diags); 532 DiagClient->EndSourceFile(); 533 return true; 534 } 535 536 // After parsing of source files ended, we want to reuse the 537 // diagnostics objects to emit further diagnostics. 538 // We call BeginSourceFile because DiagnosticConsumer requires that 539 // diagnostics with source range information are emitted only in between 540 // BeginSourceFile() and EndSourceFile(). 541 DiagClient->BeginSourceFile(Ctx.getLangOptions(), &Unit->getPreprocessor()); 542 543 Rewriter rewriter(Ctx.getSourceManager(), Ctx.getLangOptions()); 544 TransformActions TA(*Diags, capturedDiags, Ctx, Unit->getPreprocessor()); 545 MigrationPass pass(Ctx, OrigCI.getLangOpts().getGC(), 546 Unit->getSema(), TA, ARCMTMacroLocs); 547 548 trans(pass); 549 550 { 551 RewritesApplicator applicator(rewriter, Ctx, listener); 552 TA.applyRewrites(applicator); 553 } 554 555 DiagClient->EndSourceFile(); 556 557 if (DiagClient->getNumErrors()) 558 return true; 559 560 for (Rewriter::buffer_iterator 561 I = rewriter.buffer_begin(), E = rewriter.buffer_end(); I != E; ++I) { 562 FileID FID = I->first; 563 RewriteBuffer &buf = I->second; 564 const FileEntry *file = Ctx.getSourceManager().getFileEntryForID(FID); 565 assert(file); 566 std::string newFname = file->getName(); 567 newFname += "-trans"; 568 llvm::SmallString<512> newText; 569 llvm::raw_svector_ostream vecOS(newText); 570 buf.write(vecOS); 571 vecOS.flush(); 572 llvm::MemoryBuffer *memBuf = llvm::MemoryBuffer::getMemBufferCopy( 573 StringRef(newText.data(), newText.size()), newFname); 574 llvm::SmallString<64> filePath(file->getName()); 575 Unit->getFileManager().FixupRelativePath(filePath); 576 Remapper.remap(filePath.str(), memBuf); 577 } 578 579 return false; 580 } 581