123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- //====-- 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 {
- class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem
- {
- std::map<std::string, unsigned> ReadCounts;
- public:
- ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override
- {
- SmallVector<char, 128> PathVec;
- Path.toVector(PathVec);
- llvm::sys::path::remove_dots(PathVec, true);
- ++ReadCounts[std::string(PathVec.begin(), PathVec.end())];
- return InMemoryFileSystem::openFileForRead(Path);
- }
- unsigned GetReadCount(const Twine &Path) const
- {
- auto it = ReadCounts.find(Path.str());
- return it == ReadCounts.end() ? 0 : it->second;
- }
- };
- class PCHPreambleTest : public ::testing::Test {
- IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS;
- StringMap<std::string> RemappedFiles;
- std::shared_ptr<PCHContainerOperations> PCHContainerOpts;
- FileSystemOptions FSOpts;
- public:
- 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
- // 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(new DiagnosticOptions, new DiagnosticConsumer));
- FileManager *FileMgr = new FileManager(FSOpts, VFS);
- std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
- CI, PCHContainerOpts, 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(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
|