1 //===--- CompilerInstance.cpp ---------------------------------------------===// 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 "clang/Frontend/CompilerInstance.h" 11 #include "clang/Sema/Sema.h" 12 #include "clang/AST/ASTConsumer.h" 13 #include "clang/AST/ASTContext.h" 14 #include "clang/Basic/Diagnostic.h" 15 #include "clang/Basic/FileManager.h" 16 #include "clang/Basic/SourceManager.h" 17 #include "clang/Basic/TargetInfo.h" 18 #include "clang/Basic/Version.h" 19 #include "clang/Lex/HeaderSearch.h" 20 #include "clang/Lex/Preprocessor.h" 21 #include "clang/Lex/PTHManager.h" 22 #include "clang/Frontend/ChainedDiagnosticClient.h" 23 #include "clang/Frontend/FrontendAction.h" 24 #include "clang/Frontend/FrontendDiagnostic.h" 25 #include "clang/Frontend/LogDiagnosticPrinter.h" 26 #include "clang/Frontend/TextDiagnosticPrinter.h" 27 #include "clang/Frontend/VerifyDiagnosticsClient.h" 28 #include "clang/Frontend/Utils.h" 29 #include "clang/Serialization/ASTReader.h" 30 #include "clang/Sema/CodeCompleteConsumer.h" 31 #include "llvm/Support/FileSystem.h" 32 #include "llvm/Support/MemoryBuffer.h" 33 #include "llvm/Support/raw_ostream.h" 34 #include "llvm/ADT/Statistic.h" 35 #include "llvm/Support/Timer.h" 36 #include "llvm/Support/Host.h" 37 #include "llvm/Support/Path.h" 38 #include "llvm/Support/Program.h" 39 #include "llvm/Support/Signals.h" 40 #include "llvm/Support/system_error.h" 41 #include "llvm/Config/config.h" 42 using namespace clang; 43 44 CompilerInstance::CompilerInstance() 45 : Invocation(new CompilerInvocation()), ModuleManager(0) { 46 } 47 48 CompilerInstance::~CompilerInstance() { 49 } 50 51 void CompilerInstance::setInvocation(CompilerInvocation *Value) { 52 Invocation = Value; 53 } 54 55 void CompilerInstance::setDiagnostics(Diagnostic *Value) { 56 Diagnostics = Value; 57 } 58 59 void CompilerInstance::setTarget(TargetInfo *Value) { 60 Target = Value; 61 } 62 63 void CompilerInstance::setFileManager(FileManager *Value) { 64 FileMgr = Value; 65 } 66 67 void CompilerInstance::setSourceManager(SourceManager *Value) { 68 SourceMgr = Value; 69 } 70 71 void CompilerInstance::setPreprocessor(Preprocessor *Value) { PP = Value; } 72 73 void CompilerInstance::setASTContext(ASTContext *Value) { Context = Value; } 74 75 void CompilerInstance::setSema(Sema *S) { 76 TheSema.reset(S); 77 } 78 79 void CompilerInstance::setASTConsumer(ASTConsumer *Value) { 80 Consumer.reset(Value); 81 } 82 83 void CompilerInstance::setCodeCompletionConsumer(CodeCompleteConsumer *Value) { 84 CompletionConsumer.reset(Value); 85 } 86 87 // Diagnostics 88 static void SetUpBuildDumpLog(const DiagnosticOptions &DiagOpts, 89 unsigned argc, const char* const *argv, 90 Diagnostic &Diags) { 91 std::string ErrorInfo; 92 llvm::OwningPtr<llvm::raw_ostream> OS( 93 new llvm::raw_fd_ostream(DiagOpts.DumpBuildInformation.c_str(), ErrorInfo)); 94 if (!ErrorInfo.empty()) { 95 Diags.Report(diag::err_fe_unable_to_open_logfile) 96 << DiagOpts.DumpBuildInformation << ErrorInfo; 97 return; 98 } 99 100 (*OS) << "clang -cc1 command line arguments: "; 101 for (unsigned i = 0; i != argc; ++i) 102 (*OS) << argv[i] << ' '; 103 (*OS) << '\n'; 104 105 // Chain in a diagnostic client which will log the diagnostics. 106 DiagnosticClient *Logger = 107 new TextDiagnosticPrinter(*OS.take(), DiagOpts, /*OwnsOutputStream=*/true); 108 Diags.setClient(new ChainedDiagnosticClient(Diags.takeClient(), Logger)); 109 } 110 111 static void SetUpDiagnosticLog(const DiagnosticOptions &DiagOpts, 112 const CodeGenOptions *CodeGenOpts, 113 Diagnostic &Diags) { 114 std::string ErrorInfo; 115 bool OwnsStream = false; 116 llvm::raw_ostream *OS = &llvm::errs(); 117 if (DiagOpts.DiagnosticLogFile != "-") { 118 // Create the output stream. 119 llvm::raw_fd_ostream *FileOS( 120 new llvm::raw_fd_ostream(DiagOpts.DiagnosticLogFile.c_str(), 121 ErrorInfo, llvm::raw_fd_ostream::F_Append)); 122 if (!ErrorInfo.empty()) { 123 Diags.Report(diag::warn_fe_cc_log_diagnostics_failure) 124 << DiagOpts.DumpBuildInformation << ErrorInfo; 125 } else { 126 FileOS->SetUnbuffered(); 127 FileOS->SetUseAtomicWrites(true); 128 OS = FileOS; 129 OwnsStream = true; 130 } 131 } 132 133 // Chain in the diagnostic client which will log the diagnostics. 134 LogDiagnosticPrinter *Logger = new LogDiagnosticPrinter(*OS, DiagOpts, 135 OwnsStream); 136 if (CodeGenOpts) 137 Logger->setDwarfDebugFlags(CodeGenOpts->DwarfDebugFlags); 138 Diags.setClient(new ChainedDiagnosticClient(Diags.takeClient(), Logger)); 139 } 140 141 void CompilerInstance::createDiagnostics(int Argc, const char* const *Argv, 142 DiagnosticClient *Client) { 143 Diagnostics = createDiagnostics(getDiagnosticOpts(), Argc, Argv, Client, 144 &getCodeGenOpts()); 145 } 146 147 llvm::IntrusiveRefCntPtr<Diagnostic> 148 CompilerInstance::createDiagnostics(const DiagnosticOptions &Opts, 149 int Argc, const char* const *Argv, 150 DiagnosticClient *Client, 151 const CodeGenOptions *CodeGenOpts) { 152 llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 153 llvm::IntrusiveRefCntPtr<Diagnostic> Diags(new Diagnostic(DiagID)); 154 155 // Create the diagnostic client for reporting errors or for 156 // implementing -verify. 157 if (Client) 158 Diags->setClient(Client); 159 else 160 Diags->setClient(new TextDiagnosticPrinter(llvm::errs(), Opts)); 161 162 // Chain in -verify checker, if requested. 163 if (Opts.VerifyDiagnostics) 164 Diags->setClient(new VerifyDiagnosticsClient(*Diags, Diags->takeClient())); 165 166 // Chain in -diagnostic-log-file dumper, if requested. 167 if (!Opts.DiagnosticLogFile.empty()) 168 SetUpDiagnosticLog(Opts, CodeGenOpts, *Diags); 169 170 if (!Opts.DumpBuildInformation.empty()) 171 SetUpBuildDumpLog(Opts, Argc, Argv, *Diags); 172 173 // Configure our handling of diagnostics. 174 ProcessWarningOptions(*Diags, Opts); 175 176 return Diags; 177 } 178 179 // File Manager 180 181 void CompilerInstance::createFileManager() { 182 FileMgr = new FileManager(getFileSystemOpts()); 183 } 184 185 // Source Manager 186 187 void CompilerInstance::createSourceManager(FileManager &FileMgr) { 188 SourceMgr = new SourceManager(getDiagnostics(), FileMgr); 189 } 190 191 // Preprocessor 192 193 void CompilerInstance::createPreprocessor() { 194 PP = createPreprocessor(getDiagnostics(), getLangOpts(), 195 getPreprocessorOpts(), getHeaderSearchOpts(), 196 getDependencyOutputOpts(), getTarget(), 197 getFrontendOpts(), getSourceManager(), 198 getFileManager()); 199 } 200 201 Preprocessor * 202 CompilerInstance::createPreprocessor(Diagnostic &Diags, 203 const LangOptions &LangInfo, 204 const PreprocessorOptions &PPOpts, 205 const HeaderSearchOptions &HSOpts, 206 const DependencyOutputOptions &DepOpts, 207 const TargetInfo &Target, 208 const FrontendOptions &FEOpts, 209 SourceManager &SourceMgr, 210 FileManager &FileMgr) { 211 // Create a PTH manager if we are using some form of a token cache. 212 PTHManager *PTHMgr = 0; 213 if (!PPOpts.TokenCache.empty()) 214 PTHMgr = PTHManager::Create(PPOpts.TokenCache, Diags); 215 216 // Create the Preprocessor. 217 HeaderSearch *HeaderInfo = new HeaderSearch(FileMgr); 218 Preprocessor *PP = new Preprocessor(Diags, LangInfo, Target, 219 SourceMgr, *HeaderInfo, PTHMgr, 220 /*OwnsHeaderSearch=*/true); 221 222 // Note that this is different then passing PTHMgr to Preprocessor's ctor. 223 // That argument is used as the IdentifierInfoLookup argument to 224 // IdentifierTable's ctor. 225 if (PTHMgr) { 226 PTHMgr->setPreprocessor(PP); 227 PP->setPTHManager(PTHMgr); 228 } 229 230 if (PPOpts.DetailedRecord) 231 PP->createPreprocessingRecord( 232 PPOpts.DetailedRecordIncludesNestedMacroExpansions); 233 234 InitializePreprocessor(*PP, PPOpts, HSOpts, FEOpts); 235 236 // Handle generating dependencies, if requested. 237 if (!DepOpts.OutputFile.empty()) 238 AttachDependencyFileGen(*PP, DepOpts); 239 240 // Handle generating header include information, if requested. 241 if (DepOpts.ShowHeaderIncludes) 242 AttachHeaderIncludeGen(*PP); 243 if (!DepOpts.HeaderIncludeOutputFile.empty()) { 244 llvm::StringRef OutputPath = DepOpts.HeaderIncludeOutputFile; 245 if (OutputPath == "-") 246 OutputPath = ""; 247 AttachHeaderIncludeGen(*PP, /*ShowAllHeaders=*/true, OutputPath, 248 /*ShowDepth=*/false); 249 } 250 251 return PP; 252 } 253 254 // ASTContext 255 256 void CompilerInstance::createASTContext() { 257 Preprocessor &PP = getPreprocessor(); 258 Context = new ASTContext(getLangOpts(), PP.getSourceManager(), 259 getTarget(), PP.getIdentifierTable(), 260 PP.getSelectorTable(), PP.getBuiltinInfo(), 261 /*size_reserve=*/ 0); 262 } 263 264 // ExternalASTSource 265 266 void CompilerInstance::createPCHExternalASTSource(llvm::StringRef Path, 267 bool DisablePCHValidation, 268 bool DisableStatCache, 269 void *DeserializationListener){ 270 llvm::OwningPtr<ExternalASTSource> Source; 271 bool Preamble = getPreprocessorOpts().PrecompiledPreambleBytes.first != 0; 272 Source.reset(createPCHExternalASTSource(Path, getHeaderSearchOpts().Sysroot, 273 DisablePCHValidation, 274 DisableStatCache, 275 getPreprocessor(), getASTContext(), 276 DeserializationListener, 277 Preamble)); 278 ModuleManager = static_cast<ASTReader*>(Source.get()); 279 getASTContext().setExternalSource(Source); 280 } 281 282 ExternalASTSource * 283 CompilerInstance::createPCHExternalASTSource(llvm::StringRef Path, 284 const std::string &Sysroot, 285 bool DisablePCHValidation, 286 bool DisableStatCache, 287 Preprocessor &PP, 288 ASTContext &Context, 289 void *DeserializationListener, 290 bool Preamble) { 291 llvm::OwningPtr<ASTReader> Reader; 292 Reader.reset(new ASTReader(PP, &Context, 293 Sysroot.empty() ? 0 : Sysroot.c_str(), 294 DisablePCHValidation, DisableStatCache)); 295 296 Reader->setDeserializationListener( 297 static_cast<ASTDeserializationListener *>(DeserializationListener)); 298 switch (Reader->ReadAST(Path, 299 Preamble ? ASTReader::Preamble : ASTReader::PCH)) { 300 case ASTReader::Success: 301 // Set the predefines buffer as suggested by the PCH reader. Typically, the 302 // predefines buffer will be empty. 303 PP.setPredefines(Reader->getSuggestedPredefines()); 304 return Reader.take(); 305 306 case ASTReader::Failure: 307 // Unrecoverable failure: don't even try to process the input file. 308 break; 309 310 case ASTReader::IgnorePCH: 311 // No suitable PCH file could be found. Return an error. 312 break; 313 } 314 315 return 0; 316 } 317 318 // Code Completion 319 320 static bool EnableCodeCompletion(Preprocessor &PP, 321 const std::string &Filename, 322 unsigned Line, 323 unsigned Column) { 324 // Tell the source manager to chop off the given file at a specific 325 // line and column. 326 const FileEntry *Entry = PP.getFileManager().getFile(Filename); 327 if (!Entry) { 328 PP.getDiagnostics().Report(diag::err_fe_invalid_code_complete_file) 329 << Filename; 330 return true; 331 } 332 333 // Truncate the named file at the given line/column. 334 PP.SetCodeCompletionPoint(Entry, Line, Column); 335 return false; 336 } 337 338 void CompilerInstance::createCodeCompletionConsumer() { 339 const ParsedSourceLocation &Loc = getFrontendOpts().CodeCompletionAt; 340 if (!CompletionConsumer) { 341 CompletionConsumer.reset( 342 createCodeCompletionConsumer(getPreprocessor(), 343 Loc.FileName, Loc.Line, Loc.Column, 344 getFrontendOpts().ShowMacrosInCodeCompletion, 345 getFrontendOpts().ShowCodePatternsInCodeCompletion, 346 getFrontendOpts().ShowGlobalSymbolsInCodeCompletion, 347 llvm::outs())); 348 if (!CompletionConsumer) 349 return; 350 } else if (EnableCodeCompletion(getPreprocessor(), Loc.FileName, 351 Loc.Line, Loc.Column)) { 352 CompletionConsumer.reset(); 353 return; 354 } 355 356 if (CompletionConsumer->isOutputBinary() && 357 llvm::sys::Program::ChangeStdoutToBinary()) { 358 getPreprocessor().getDiagnostics().Report(diag::err_fe_stdout_binary); 359 CompletionConsumer.reset(); 360 } 361 } 362 363 void CompilerInstance::createFrontendTimer() { 364 FrontendTimer.reset(new llvm::Timer("Clang front-end timer")); 365 } 366 367 CodeCompleteConsumer * 368 CompilerInstance::createCodeCompletionConsumer(Preprocessor &PP, 369 const std::string &Filename, 370 unsigned Line, 371 unsigned Column, 372 bool ShowMacros, 373 bool ShowCodePatterns, 374 bool ShowGlobals, 375 llvm::raw_ostream &OS) { 376 if (EnableCodeCompletion(PP, Filename, Line, Column)) 377 return 0; 378 379 // Set up the creation routine for code-completion. 380 return new PrintingCodeCompleteConsumer(ShowMacros, ShowCodePatterns, 381 ShowGlobals, OS); 382 } 383 384 void CompilerInstance::createSema(bool CompleteTranslationUnit, 385 CodeCompleteConsumer *CompletionConsumer) { 386 TheSema.reset(new Sema(getPreprocessor(), getASTContext(), getASTConsumer(), 387 CompleteTranslationUnit, CompletionConsumer)); 388 } 389 390 // Output Files 391 392 void CompilerInstance::addOutputFile(const OutputFile &OutFile) { 393 assert(OutFile.OS && "Attempt to add empty stream to output list!"); 394 OutputFiles.push_back(OutFile); 395 } 396 397 void CompilerInstance::clearOutputFiles(bool EraseFiles) { 398 for (std::list<OutputFile>::iterator 399 it = OutputFiles.begin(), ie = OutputFiles.end(); it != ie; ++it) { 400 delete it->OS; 401 if (!it->TempFilename.empty()) { 402 if (EraseFiles) { 403 bool existed; 404 llvm::sys::fs::remove(it->TempFilename, existed); 405 } else { 406 llvm::SmallString<128> NewOutFile(it->Filename); 407 408 // If '-working-directory' was passed, the output filename should be 409 // relative to that. 410 FileMgr->FixupRelativePath(NewOutFile); 411 if (llvm::error_code ec = llvm::sys::fs::rename(it->TempFilename, 412 NewOutFile.str())) { 413 getDiagnostics().Report(diag::err_fe_unable_to_rename_temp) 414 << it->TempFilename << it->Filename << ec.message(); 415 416 bool existed; 417 llvm::sys::fs::remove(it->TempFilename, existed); 418 } 419 } 420 } else if (!it->Filename.empty() && EraseFiles) 421 llvm::sys::Path(it->Filename).eraseFromDisk(); 422 423 } 424 OutputFiles.clear(); 425 } 426 427 llvm::raw_fd_ostream * 428 CompilerInstance::createDefaultOutputFile(bool Binary, 429 llvm::StringRef InFile, 430 llvm::StringRef Extension) { 431 return createOutputFile(getFrontendOpts().OutputFile, Binary, 432 /*RemoveFileOnSignal=*/true, InFile, Extension); 433 } 434 435 llvm::raw_fd_ostream * 436 CompilerInstance::createOutputFile(llvm::StringRef OutputPath, 437 bool Binary, bool RemoveFileOnSignal, 438 llvm::StringRef InFile, 439 llvm::StringRef Extension) { 440 std::string Error, OutputPathName, TempPathName; 441 llvm::raw_fd_ostream *OS = createOutputFile(OutputPath, Error, Binary, 442 RemoveFileOnSignal, 443 InFile, Extension, 444 &OutputPathName, 445 &TempPathName); 446 if (!OS) { 447 getDiagnostics().Report(diag::err_fe_unable_to_open_output) 448 << OutputPath << Error; 449 return 0; 450 } 451 452 // Add the output file -- but don't try to remove "-", since this means we are 453 // using stdin. 454 addOutputFile(OutputFile((OutputPathName != "-") ? OutputPathName : "", 455 TempPathName, OS)); 456 457 return OS; 458 } 459 460 llvm::raw_fd_ostream * 461 CompilerInstance::createOutputFile(llvm::StringRef OutputPath, 462 std::string &Error, 463 bool Binary, 464 bool RemoveFileOnSignal, 465 llvm::StringRef InFile, 466 llvm::StringRef Extension, 467 std::string *ResultPathName, 468 std::string *TempPathName) { 469 std::string OutFile, TempFile; 470 if (!OutputPath.empty()) { 471 OutFile = OutputPath; 472 } else if (InFile == "-") { 473 OutFile = "-"; 474 } else if (!Extension.empty()) { 475 llvm::sys::Path Path(InFile); 476 Path.eraseSuffix(); 477 Path.appendSuffix(Extension); 478 OutFile = Path.str(); 479 } else { 480 OutFile = "-"; 481 } 482 483 if (OutFile != "-") { 484 llvm::sys::Path OutPath(OutFile); 485 // Only create the temporary if we can actually write to OutPath, otherwise 486 // we want to fail early. 487 bool Exists; 488 if ((llvm::sys::fs::exists(OutPath.str(), Exists) || !Exists) || 489 (OutPath.isRegularFile() && OutPath.canWrite())) { 490 // Create a temporary file. 491 llvm::sys::Path TempPath(OutFile); 492 if (!TempPath.createTemporaryFileOnDisk()) 493 TempFile = TempPath.str(); 494 } 495 } 496 497 std::string OSFile = OutFile; 498 if (!TempFile.empty()) 499 OSFile = TempFile; 500 501 llvm::OwningPtr<llvm::raw_fd_ostream> OS( 502 new llvm::raw_fd_ostream(OSFile.c_str(), Error, 503 (Binary ? llvm::raw_fd_ostream::F_Binary : 0))); 504 if (!Error.empty()) 505 return 0; 506 507 // Make sure the out stream file gets removed if we crash. 508 if (RemoveFileOnSignal) 509 llvm::sys::RemoveFileOnSignal(llvm::sys::Path(OSFile)); 510 511 if (ResultPathName) 512 *ResultPathName = OutFile; 513 if (TempPathName) 514 *TempPathName = TempFile; 515 516 return OS.take(); 517 } 518 519 // Initialization Utilities 520 521 bool CompilerInstance::InitializeSourceManager(llvm::StringRef InputFile) { 522 return InitializeSourceManager(InputFile, getDiagnostics(), getFileManager(), 523 getSourceManager(), getFrontendOpts()); 524 } 525 526 bool CompilerInstance::InitializeSourceManager(llvm::StringRef InputFile, 527 Diagnostic &Diags, 528 FileManager &FileMgr, 529 SourceManager &SourceMgr, 530 const FrontendOptions &Opts) { 531 // Figure out where to get and map in the main file, unless it's already 532 // been created (e.g., by a precompiled preamble). 533 if (!SourceMgr.getMainFileID().isInvalid()) { 534 // Do nothing: the main file has already been set. 535 } else if (InputFile != "-") { 536 const FileEntry *File = FileMgr.getFile(InputFile); 537 if (!File) { 538 Diags.Report(diag::err_fe_error_reading) << InputFile; 539 return false; 540 } 541 SourceMgr.createMainFileID(File); 542 } else { 543 llvm::OwningPtr<llvm::MemoryBuffer> SB; 544 if (llvm::MemoryBuffer::getSTDIN(SB)) { 545 // FIXME: Give ec.message() in this diag. 546 Diags.Report(diag::err_fe_error_reading_stdin); 547 return false; 548 } 549 const FileEntry *File = FileMgr.getVirtualFile(SB->getBufferIdentifier(), 550 SB->getBufferSize(), 0); 551 SourceMgr.createMainFileID(File); 552 SourceMgr.overrideFileContents(File, SB.take()); 553 } 554 555 assert(!SourceMgr.getMainFileID().isInvalid() && 556 "Couldn't establish MainFileID!"); 557 return true; 558 } 559 560 // High-Level Operations 561 562 bool CompilerInstance::ExecuteAction(FrontendAction &Act) { 563 assert(hasDiagnostics() && "Diagnostics engine is not initialized!"); 564 assert(!getFrontendOpts().ShowHelp && "Client must handle '-help'!"); 565 assert(!getFrontendOpts().ShowVersion && "Client must handle '-version'!"); 566 567 // FIXME: Take this as an argument, once all the APIs we used have moved to 568 // taking it as an input instead of hard-coding llvm::errs. 569 llvm::raw_ostream &OS = llvm::errs(); 570 571 // Create the target instance. 572 setTarget(TargetInfo::CreateTargetInfo(getDiagnostics(), getTargetOpts())); 573 if (!hasTarget()) 574 return false; 575 576 // Inform the target of the language options. 577 // 578 // FIXME: We shouldn't need to do this, the target should be immutable once 579 // created. This complexity should be lifted elsewhere. 580 getTarget().setForcedLangOptions(getLangOpts()); 581 582 // Validate/process some options. 583 if (getHeaderSearchOpts().Verbose) 584 OS << "clang -cc1 version " CLANG_VERSION_STRING 585 << " based upon " << PACKAGE_STRING 586 << " hosted on " << llvm::sys::getHostTriple() << "\n"; 587 588 if (getFrontendOpts().ShowTimers) 589 createFrontendTimer(); 590 591 if (getFrontendOpts().ShowStats) 592 llvm::EnableStatistics(); 593 594 for (unsigned i = 0, e = getFrontendOpts().Inputs.size(); i != e; ++i) { 595 const std::string &InFile = getFrontendOpts().Inputs[i].second; 596 597 // Reset the ID tables if we are reusing the SourceManager. 598 if (hasSourceManager()) 599 getSourceManager().clearIDTables(); 600 601 if (Act.BeginSourceFile(*this, InFile, getFrontendOpts().Inputs[i].first)) { 602 Act.Execute(); 603 Act.EndSourceFile(); 604 } 605 } 606 607 if (getDiagnosticOpts().ShowCarets) { 608 // We can have multiple diagnostics sharing one diagnostic client. 609 // Get the total number of warnings/errors from the client. 610 unsigned NumWarnings = getDiagnostics().getClient()->getNumWarnings(); 611 unsigned NumErrors = getDiagnostics().getClient()->getNumErrors(); 612 613 if (NumWarnings) 614 OS << NumWarnings << " warning" << (NumWarnings == 1 ? "" : "s"); 615 if (NumWarnings && NumErrors) 616 OS << " and "; 617 if (NumErrors) 618 OS << NumErrors << " error" << (NumErrors == 1 ? "" : "s"); 619 if (NumWarnings || NumErrors) 620 OS << " generated.\n"; 621 } 622 623 if (getFrontendOpts().ShowStats && hasFileManager()) { 624 getFileManager().PrintStats(); 625 OS << "\n"; 626 } 627 628 return !getDiagnostics().getClient()->getNumErrors(); 629 } 630 631 632