|  | //====-- unittests/Frontend/PCHPreambleTest.cpp - FrontendAction tests ---====// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "clang/Frontend/ASTUnit.h" | 
|  | #include "clang/Frontend/CompilerInvocation.h" | 
|  | #include "clang/Frontend/CompilerInstance.h" | 
|  | #include "clang/Frontend/FrontendActions.h" | 
|  | #include "clang/Frontend/FrontendOptions.h" | 
|  | #include "clang/Lex/PreprocessorOptions.h" | 
|  | #include "clang/Basic/Diagnostic.h" | 
|  | #include "clang/Basic/FileManager.h" | 
|  | #include "llvm/Support/FileSystem.h" | 
|  | #include "llvm/Support/MemoryBuffer.h" | 
|  | #include "llvm/Support/Path.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | using namespace llvm; | 
|  | using namespace clang; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | std::string Canonicalize(const Twine &Path) { | 
|  | SmallVector<char, 128> PathVec; | 
|  | Path.toVector(PathVec); | 
|  | llvm::sys::path::remove_dots(PathVec, true); | 
|  | return std::string(PathVec.begin(), PathVec.end()); | 
|  | } | 
|  |  | 
|  | class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem | 
|  | { | 
|  | std::map<std::string, unsigned> ReadCounts; | 
|  |  | 
|  | public: | 
|  | ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override | 
|  | { | 
|  | ++ReadCounts[Canonicalize(Path)]; | 
|  | return InMemoryFileSystem::openFileForRead(Path); | 
|  | } | 
|  |  | 
|  | unsigned GetReadCount(const Twine &Path) const | 
|  | { | 
|  | auto it = ReadCounts.find(Canonicalize(Path)); | 
|  | return it == ReadCounts.end() ? 0 : it->second; | 
|  | } | 
|  | }; | 
|  |  | 
|  | class PCHPreambleTest : public ::testing::Test { | 
|  | IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS; | 
|  | StringMap<std::string> RemappedFiles; | 
|  | std::shared_ptr<PCHContainerOperations> PCHContainerOpts; | 
|  | std::shared_ptr<DiagnosticOptions> DiagOpts = | 
|  | std::make_shared<DiagnosticOptions>(); | 
|  | FileSystemOptions FSOpts; | 
|  |  | 
|  | public: | 
|  | void SetUp() override { ResetVFS(); } | 
|  | void TearDown() override {} | 
|  |  | 
|  | void ResetVFS() { | 
|  | VFS = llvm::makeIntrusiveRefCnt<ReadCountingInMemoryFileSystem>(); | 
|  | // We need the working directory to be set to something absolute, | 
|  | // otherwise it ends up being inadvertently set to the current | 
|  | // working directory in the real file system due to a series of | 
|  | // unfortunate conditions interacting badly. | 
|  | // What's more, this path *must* be absolute on all (real) | 
|  | // filesystems, so just '/' won't work (e.g. on Win32). | 
|  | VFS->setCurrentWorkingDirectory("//./"); | 
|  | } | 
|  |  | 
|  | void AddFile(const std::string &Filename, const std::string &Contents) { | 
|  | ::time_t now; | 
|  | ::time(&now); | 
|  | VFS->addFile(Filename, now, MemoryBuffer::getMemBufferCopy(Contents, Filename)); | 
|  | } | 
|  |  | 
|  | void RemapFile(const std::string &Filename, const std::string &Contents) { | 
|  | RemappedFiles[Filename] = Contents; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<ASTUnit> ParseAST(const std::string &EntryFile) { | 
|  | PCHContainerOpts = std::make_shared<PCHContainerOperations>(); | 
|  | std::shared_ptr<CompilerInvocation> CI(new CompilerInvocation); | 
|  | CI->getFrontendOpts().Inputs.push_back( | 
|  | FrontendInputFile(EntryFile, FrontendOptions::getInputKindForExtension( | 
|  | llvm::sys::path::extension(EntryFile).substr(1)))); | 
|  |  | 
|  | CI->getTargetOpts().Triple = "i386-unknown-linux-gnu"; | 
|  |  | 
|  | CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles(); | 
|  |  | 
|  | PreprocessorOptions &PPOpts = CI->getPreprocessorOpts(); | 
|  | PPOpts.RemappedFilesKeepOriginalName = true; | 
|  |  | 
|  | IntrusiveRefCntPtr<DiagnosticsEngine> Diags( | 
|  | CompilerInstance::createDiagnostics(*VFS, *DiagOpts, | 
|  | new DiagnosticConsumer)); | 
|  |  | 
|  | auto FileMgr = llvm::makeIntrusiveRefCnt<FileManager>(FSOpts, VFS); | 
|  |  | 
|  | std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation( | 
|  | CI, PCHContainerOpts, DiagOpts, Diags, FileMgr, false, | 
|  | CaptureDiagsKind::None, | 
|  | /*PrecompilePreambleAfterNParses=*/1); | 
|  | return AST; | 
|  | } | 
|  |  | 
|  | bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) { | 
|  | bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS); | 
|  | return !reparseFailed; | 
|  | } | 
|  |  | 
|  | unsigned GetFileReadCount(const std::string &Filename) const { | 
|  | return VFS->GetReadCount(Filename); | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::vector<std::pair<std::string, llvm::MemoryBuffer *>> | 
|  | GetRemappedFiles() const { | 
|  | std::vector<std::pair<std::string, llvm::MemoryBuffer *>> Remapped; | 
|  | for (const auto &RemappedFile : RemappedFiles) { | 
|  | std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy( | 
|  | RemappedFile.second, RemappedFile.first()); | 
|  | Remapped.emplace_back(std::string(RemappedFile.first()), buf.release()); | 
|  | } | 
|  | return Remapped; | 
|  | } | 
|  | }; | 
|  |  | 
|  | 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<ASTUnit> 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<ASTUnit> 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 "//./foo/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<ASTUnit> 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"; | 
|  | std::string MainName = "//./main.cpp"; | 
|  | AddFile(Header1, ""); | 
|  | AddFile(Header2, "#pragma once"); | 
|  | AddFile(MainName, | 
|  | "#include \"//./foo/../header1.h\"\n" | 
|  | "#include \"//./foo/../header2.h\"\n" | 
|  | "int main() { return ZERO; }"); | 
|  | RemapFile(Header1, "static const int ZERO = 0;\n"); | 
|  |  | 
|  | std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); | 
|  | ASSERT_TRUE(AST.get()); | 
|  | ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); | 
|  |  | 
|  | unsigned initialCounts[] = { | 
|  | GetFileReadCount(MainName), | 
|  | GetFileReadCount(Header1), | 
|  | GetFileReadCount(Header2) | 
|  | }; | 
|  |  | 
|  | ASSERT_TRUE(ReparseAST(AST)); | 
|  |  | 
|  | ASSERT_NE(initialCounts[0], GetFileReadCount(MainName)); | 
|  | ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1)); | 
|  | ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2)); | 
|  | } | 
|  |  | 
|  | TEST_F(PCHPreambleTest, ParseWithBom) { | 
|  | std::string Header = "//./header.h"; | 
|  | std::string Main = "//./main.cpp"; | 
|  | AddFile(Header, "int random() { return 4; }"); | 
|  | AddFile(Main, | 
|  | "\xef\xbb\xbf" | 
|  | "#include \"//./header.h\"\n" | 
|  | "int main() { return random() -2; }"); | 
|  |  | 
|  | std::unique_ptr<ASTUnit> AST(ParseAST(Main)); | 
|  | ASSERT_TRUE(AST.get()); | 
|  | ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); | 
|  |  | 
|  | unsigned HeaderReadCount = GetFileReadCount(Header); | 
|  |  | 
|  | ASSERT_TRUE(ReparseAST(AST)); | 
|  | ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); | 
|  |  | 
|  | // Check preamble PCH was really reused | 
|  | ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header)); | 
|  |  | 
|  | // Remove BOM | 
|  | RemapFile(Main, | 
|  | "#include \"//./header.h\"\n" | 
|  | "int main() { return random() -2; }"); | 
|  |  | 
|  | ASSERT_TRUE(ReparseAST(AST)); | 
|  | ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); | 
|  |  | 
|  | ASSERT_LE(HeaderReadCount, GetFileReadCount(Header)); | 
|  | HeaderReadCount = GetFileReadCount(Header); | 
|  |  | 
|  | // Add BOM back | 
|  | RemapFile(Main, | 
|  | "\xef\xbb\xbf" | 
|  | "#include \"//./header.h\"\n" | 
|  | "int main() { return random() -2; }"); | 
|  |  | 
|  | ASSERT_TRUE(ReparseAST(AST)); | 
|  | ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); | 
|  |  | 
|  | ASSERT_LE(HeaderReadCount, GetFileReadCount(Header)); | 
|  | } | 
|  |  | 
|  | } // anonymous namespace |