266 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			266 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C++
		
	
	
	
| //====-- 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(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
 |