Index: include/clang/Frontend/ASTUnit.h =================================================================== --- include/clang/Frontend/ASTUnit.h +++ include/clang/Frontend/ASTUnit.h @@ -205,7 +205,10 @@ /// we'll attempt to rebuild the precompiled header. This way, if /// building the precompiled preamble fails, we won't try again for /// some number of calls. - unsigned PreambleRebuildCounter = 0; + unsigned PreambleRebuildCountdown = 0; + + /// Counter indicating how often the preamble was build in total. + unsigned PreambleCounter = 0; /// Cache pairs "filename - source location" /// @@ -574,6 +577,8 @@ mapLocationToPreamble(R.getEnd())); } + unsigned getPreambleCounterForTests() const { return PreambleCounter; } + // Retrieve the diagnostics associated with this AST using stored_diag_iterator = StoredDiagnostic *; using stored_diag_const_iterator = const StoredDiagnostic *; Index: lib/Frontend/ASTUnit.cpp =================================================================== --- lib/Frontend/ASTUnit.cpp +++ lib/Frontend/ASTUnit.cpp @@ -1307,22 +1307,22 @@ PreambleInvocationIn.getDiagnosticOpts()); getDiagnostics().setNumWarnings(NumWarningsInPreamble); - PreambleRebuildCounter = 1; + PreambleRebuildCountdown = 1; return MainFileBuffer; } else { Preamble.reset(); PreambleDiagnostics.clear(); TopLevelDeclsInPreamble.clear(); PreambleSrcLocCache.clear(); - PreambleRebuildCounter = 1; + PreambleRebuildCountdown = 1; } } // If the preamble rebuild counter > 1, it's because we previously // failed to build a preamble and we're not yet ready to try // again. Decrement the counter and return a failure. - if (PreambleRebuildCounter > 1) { - --PreambleRebuildCounter; + if (PreambleRebuildCountdown > 1) { + --PreambleRebuildCountdown; return nullptr; } @@ -1332,6 +1332,8 @@ if (!AllowRebuild) return nullptr; + ++PreambleCounter; + SmallVector NewPreambleDiagsStandalone; SmallVector NewPreambleDiags; ASTUnitPreambleCallbacks Callbacks; @@ -1359,18 +1361,18 @@ if (NewPreamble) { Preamble = std::move(*NewPreamble); - PreambleRebuildCounter = 1; + PreambleRebuildCountdown = 1; } else { switch (static_cast(NewPreamble.getError().value())) { case BuildPreambleError::CouldntCreateTempFile: // Try again next time. - PreambleRebuildCounter = 1; + PreambleRebuildCountdown = 1; return nullptr; case BuildPreambleError::CouldntCreateTargetInfo: case BuildPreambleError::BeginSourceFileFailed: case BuildPreambleError::CouldntEmitPCH: // These erros are more likely to repeat, retry after some period. - PreambleRebuildCounter = DefaultPreambleRebuildInterval; + PreambleRebuildCountdown = DefaultPreambleRebuildInterval; return nullptr; } llvm_unreachable("unexpected BuildPreambleError"); @@ -1510,7 +1512,7 @@ AST->OnlyLocalDecls = OnlyLocalDecls; AST->CaptureDiagnostics = CaptureDiagnostics; if (PrecompilePreambleAfterNParses > 0) - AST->PreambleRebuildCounter = PrecompilePreambleAfterNParses; + AST->PreambleRebuildCountdown = PrecompilePreambleAfterNParses; AST->TUKind = Action ? Action->getTranslationUnitKind() : TU_Complete; AST->ShouldCacheCodeCompletionResults = CacheCodeCompletionResults; AST->IncludeBriefCommentsInCodeCompletion @@ -1644,7 +1646,7 @@ std::unique_ptr OverrideMainBuffer; if (PrecompilePreambleAfterNParses > 0) { - PreambleRebuildCounter = PrecompilePreambleAfterNParses; + PreambleRebuildCountdown = PrecompilePreambleAfterNParses; OverrideMainBuffer = getMainBufferWithPrecompiledPreamble(PCHContainerOps, *Invocation, VFS); getDiagnostics().Reset(); @@ -1822,7 +1824,7 @@ // If we have a preamble file lying around, or if we might try to // build a precompiled preamble, do so now. std::unique_ptr OverrideMainBuffer; - if (Preamble || PreambleRebuildCounter > 0) + if (Preamble || PreambleRebuildCountdown > 0) OverrideMainBuffer = getMainBufferWithPrecompiledPreamble(PCHContainerOps, *Invocation, VFS); Index: lib/Frontend/PrecompiledPreamble.cpp =================================================================== --- lib/Frontend/PrecompiledPreamble.cpp +++ lib/Frontend/PrecompiledPreamble.cpp @@ -28,6 +28,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/Mutex.h" #include "llvm/Support/MutexGuard.h" +#include "llvm/Support/Path.h" #include "llvm/Support/Process.h" #include "llvm/Support/VirtualFileSystem.h" #include @@ -451,20 +452,32 @@ Status.getSize(), llvm::sys::toTimeT(Status.getLastModificationTime())); } + llvm::StringMap OverridenFileBuffers; for (const auto &RB : PreprocessorOpts.RemappedFileBuffers) { - llvm::vfs::Status Status; - if (!moveOnNoError(VFS->status(RB.first), Status)) - return false; - - OverriddenFiles[Status.getUniqueID()] = + const PrecompiledPreamble::PreambleFileHash PreambleHash = PreambleFileHash::createForMemoryBuffer(RB.second); + llvm::vfs::Status Status; + if (moveOnNoError(VFS->status(RB.first), Status)) + OverriddenFiles[Status.getUniqueID()] = PreambleHash; + else + OverridenFileBuffers[RB.first] = PreambleHash; } // Check whether anything has changed. for (const auto &F : FilesInPreamble) { + auto OverridenFileBuffer = OverridenFileBuffers.find(F.first()); + if (OverridenFileBuffer != OverridenFileBuffers.end()) { + // The file's buffer was remapped; check whether it matches up + // with the previous mapping. + if (OverridenFileBuffer->second != F.second) + return false; + continue; + } + llvm::vfs::Status Status; if (!moveOnNoError(VFS->status(F.first()), Status)) { - // If we can't stat the file, assume that something horrible happened. + // If the file's buffer is not remapped and we can't stat it, + // assume that something horrible happened. return false; } @@ -478,9 +491,10 @@ continue; } - // The file was not remapped; check whether it has changed on disk. + // Neither the file's buffer nor the file itself was remapped; + // check whether it has changed on disk. if (Status.getSize() != uint64_t(F.second.Size) || - llvm::sys::toTimeT(Status.getLastModificationTime()) != + llvm::sys::toTimeT(Status.getLastModificationTime()) != F.second.ModTime) return false; } Index: unittests/Frontend/PCHPreambleTest.cpp =================================================================== --- unittests/Frontend/PCHPreambleTest.cpp +++ unittests/Frontend/PCHPreambleTest.cpp @@ -52,7 +52,10 @@ FileSystemOptions FSOpts; public: - void SetUp() override { + void SetUp() override { ResetVFS(); } + void TearDown() override {} + + void ResetVFS() { VFS = new ReadCountingInMemoryFileSystem(); // We need the working directory to be set to something absolute, // otherwise it ends up being inadvertently set to the current @@ -63,9 +66,6 @@ VFS->setCurrentWorkingDirectory("//./"); } - void TearDown() override { - } - void AddFile(const std::string &Filename, const std::string &Contents) { ::time_t now; ::time(&now); @@ -123,6 +123,72 @@ } }; +TEST_F(PCHPreambleTest, ReparseReusesPreambleWithUnsavedFileNotExistingOnDisk) { + std::string Header1 = "//./header1.h"; + std::string MainName = "//./main.cpp"; + AddFile(MainName, R"cpp( +#include "//./header1.h" +int main() { return ZERO; } +)cpp"); + RemapFile(Header1, "#define ZERO 0\n"); + + // Parse with header file provided as unsaved file, which does not exist on + // disk. + std::unique_ptr AST(ParseAST(MainName)); + ASSERT_TRUE(AST.get()); + ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + // Reparse and check that the preamble was reused. + ASSERT_TRUE(ReparseAST(AST)); + ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); +} + +TEST_F(PCHPreambleTest, ReparseReusesPreambleAfterUnsavedFileWasCreatedOnDisk) { + std::string Header1 = "//./header1.h"; + std::string MainName = "//./main.cpp"; + AddFile(MainName, R"cpp( +#include "//./header1.h" +int main() { return ZERO; } +)cpp"); + RemapFile(Header1, "#define ZERO 0\n"); + + // Parse with header file provided as unsaved file, which does not exist on + // disk. + std::unique_ptr AST(ParseAST(MainName)); + ASSERT_TRUE(AST.get()); + ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + // Create the unsaved file also on disk and check that preamble was reused. + AddFile(Header1, "#define ZERO 0\n"); + ASSERT_TRUE(ReparseAST(AST)); + ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); +} + +TEST_F(PCHPreambleTest, + ReparseReusesPreambleAfterUnsavedFileWasRemovedFromDisk) { + std::string Header1 = "//./foo/header1.h"; + std::string MainName = "//./main.cpp"; + std::string MainFileContent = R"cpp( +#include "//./header1.h" +int main() { return ZERO; } +)cpp"; + AddFile(MainName, MainFileContent); + AddFile(Header1, "#define ZERO 0\n"); + RemapFile(Header1, "#define ZERO 0\n"); + + // Parse with header file provided as unsaved file, which exists on disk. + std::unique_ptr AST(ParseAST(MainName)); + ASSERT_TRUE(AST.get()); + ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); + + // Remove the unsaved file from disk and check that the preamble was reused. + ResetVFS(); + AddFile(MainName, MainFileContent); + ASSERT_TRUE(ReparseAST(AST)); + ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); +} + TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) { std::string Header1 = "//./header1.h"; std::string Header2 = "//./header2.h";