Index: include/clang/Basic/Diagnostic.h =================================================================== --- include/clang/Basic/Diagnostic.h +++ include/clang/Basic/Diagnostic.h @@ -752,6 +752,15 @@ } bool hasFatalErrorOccurred() const { return FatalErrorOccurred; } + /// Determine whether the client should because a fatal error was issued, so + /// clients can cut off extra processing that might be wasteful in this case. + bool shouldRecoverAfterFatalErrors() const { + // todo: a separate flag might be required for a client that doesn't want to + // show any errors after the fatal, but still wants to collect as much + // information from the source code as possible. + return SuppressAfterFatalError; + } + /// Determine whether any kind of unrecoverable error has occurred. bool hasUnrecoverableErrorOccurred() const { return FatalErrorOccurred || UnrecoverableErrorOccurred; Index: lib/Lex/PPDirectives.cpp =================================================================== --- lib/Lex/PPDirectives.cpp +++ lib/Lex/PPDirectives.cpp @@ -1899,7 +1899,8 @@ // Any diagnostics after the fatal error will not be visible. As the // compilation failed already and errors in subsequently included files won't // be visible, avoid preprocessing those files. - if (ShouldEnter && Diags->hasFatalErrorOccurred()) + if (ShouldEnter && Diags->hasFatalErrorOccurred() && + !Diags->shouldRecoverAfterFatalErrors()) ShouldEnter = false; // Determine whether we should try to import the module for this #include, if Index: lib/Sema/SemaTemplateInstantiate.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiate.cpp +++ lib/Sema/SemaTemplateInstantiate.cpp @@ -219,6 +219,7 @@ // error have occurred. Any diagnostics we might have raised will not be // visible, and we do not need to construct a correct AST. if (SemaRef.Diags.hasFatalErrorOccurred() && + !SemaRef.Diags.shouldRecoverAfterFatalErrors() && SemaRef.Diags.hasUncompilableErrorOccurred()) { Invalid = true; return; Index: unittests/Frontend/PCHPreambleTest.cpp =================================================================== --- unittests/Frontend/PCHPreambleTest.cpp +++ unittests/Frontend/PCHPreambleTest.cpp @@ -77,7 +77,8 @@ RemappedFiles[Filename] = Contents; } - std::unique_ptr ParseAST(const std::string &EntryFile) { + std::unique_ptr ParseAST(const std::string &EntryFile, + bool SuppressAfterFatalError = true) { PCHContainerOpts = std::make_shared(); std::shared_ptr CI(new CompilerInvocation); CI->getFrontendOpts().Inputs.push_back( @@ -88,11 +89,15 @@ CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles(); + CI->getLangOpts()->CPlusPlus = true; + CI->getLangOpts()->CPlusPlus11 = true; + PreprocessorOptions &PPOpts = CI->getPreprocessorOpts(); PPOpts.RemappedFilesKeepOriginalName = true; IntrusiveRefCntPtr Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions, new DiagnosticConsumer)); + Diags->setSuppressAfterFatalError(SuppressAfterFatalError); FileManager *FileMgr = new FileManager(FSOpts, VFS); @@ -197,4 +202,34 @@ ASSERT_LE(HeaderReadCount, GetFileReadCount(Header)); } +TEST_F(PCHPreambleTest, MissingHeader) { + std::string Header1 = "//./header1.h"; + AddFile(Header1, "template class C;\n" + "template class C{};\n"); + + std::string Header2 = "//./header2.h"; + AddFile(Header2, "using Alias = C;\n"); + + std::string Main = "//./main.cpp"; + AddFile(Main, "#include \"nonexistent1\"\n" + "#include \"//./header1.h\"\n" + "#include \"nonexistent2\"\n" + "#include \"//./header2.h\"\n" + "Alias Var;"); + + std::unique_ptr AST( + ParseAST(Main, /*SuppressAfterFatalError=*/false)); + ASSERT_TRUE(AST.get()); + + // only "file not found" errors should be emitted, + // "Alias" should be visible for lookup. + auto ExpectedErrorsCount = 2u; + + ASSERT_EQ(AST->getDiagnostics().getClient()->getNumErrors(), + ExpectedErrorsCount); + ASSERT_TRUE(ReparseAST(AST)); + ASSERT_EQ(AST->getDiagnostics().getClient()->getNumErrors(), + ExpectedErrorsCount); +} + } // anonymous namespace