1b5b48db1SCameron Desrochers //====-- unittests/Frontend/PCHPreambleTest.cpp - FrontendAction tests ---====// 2b5b48db1SCameron Desrochers // 32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information. 52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6b5b48db1SCameron Desrochers // 7b5b48db1SCameron Desrochers //===----------------------------------------------------------------------===// 8b5b48db1SCameron Desrochers 9b5b48db1SCameron Desrochers #include "clang/Frontend/ASTUnit.h" 10b5b48db1SCameron Desrochers #include "clang/Frontend/CompilerInvocation.h" 11b5b48db1SCameron Desrochers #include "clang/Frontend/CompilerInstance.h" 12b5b48db1SCameron Desrochers #include "clang/Frontend/FrontendActions.h" 13b5b48db1SCameron Desrochers #include "clang/Frontend/FrontendOptions.h" 14b5b48db1SCameron Desrochers #include "clang/Lex/PreprocessorOptions.h" 15b5b48db1SCameron Desrochers #include "clang/Basic/Diagnostic.h" 16b5b48db1SCameron Desrochers #include "clang/Basic/FileManager.h" 17b5b48db1SCameron Desrochers #include "llvm/Support/FileSystem.h" 18b5b48db1SCameron Desrochers #include "llvm/Support/MemoryBuffer.h" 19b5b48db1SCameron Desrochers #include "llvm/Support/Path.h" 20b5b48db1SCameron Desrochers #include "gtest/gtest.h" 21b5b48db1SCameron Desrochers 22b5b48db1SCameron Desrochers using namespace llvm; 23b5b48db1SCameron Desrochers using namespace clang; 24b5b48db1SCameron Desrochers 25b5b48db1SCameron Desrochers namespace { 26b5b48db1SCameron Desrochers 2735ab2a11SPaul Pluzhnikov std::string Canonicalize(const Twine &Path) { 2835ab2a11SPaul Pluzhnikov SmallVector<char, 128> PathVec; 2935ab2a11SPaul Pluzhnikov Path.toVector(PathVec); 3035ab2a11SPaul Pluzhnikov llvm::sys::path::remove_dots(PathVec, true); 3135ab2a11SPaul Pluzhnikov return std::string(PathVec.begin(), PathVec.end()); 3235ab2a11SPaul Pluzhnikov } 3335ab2a11SPaul Pluzhnikov 34b5b48db1SCameron Desrochers class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem 35b5b48db1SCameron Desrochers { 36b5b48db1SCameron Desrochers std::map<std::string, unsigned> ReadCounts; 37b5b48db1SCameron Desrochers 38b5b48db1SCameron Desrochers public: 39efdb3ae2SAbhina Sreeskantharajan ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override 40efdb3ae2SAbhina Sreeskantharajan { 4135ab2a11SPaul Pluzhnikov ++ReadCounts[Canonicalize(Path)]; 42efdb3ae2SAbhina Sreeskantharajan return InMemoryFileSystem::openFileForRead(Path); 43b5b48db1SCameron Desrochers } 44b5b48db1SCameron Desrochers 45b5b48db1SCameron Desrochers unsigned GetReadCount(const Twine &Path) const 46b5b48db1SCameron Desrochers { 4735ab2a11SPaul Pluzhnikov auto it = ReadCounts.find(Canonicalize(Path)); 48b5b48db1SCameron Desrochers return it == ReadCounts.end() ? 0 : it->second; 49b5b48db1SCameron Desrochers } 50b5b48db1SCameron Desrochers }; 51b5b48db1SCameron Desrochers 52b5b48db1SCameron Desrochers class PCHPreambleTest : public ::testing::Test { 53b5b48db1SCameron Desrochers IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS; 54b5b48db1SCameron Desrochers StringMap<std::string> RemappedFiles; 55b5b48db1SCameron Desrochers std::shared_ptr<PCHContainerOperations> PCHContainerOpts; 56b5b48db1SCameron Desrochers FileSystemOptions FSOpts; 57b5b48db1SCameron Desrochers 58b5b48db1SCameron Desrochers public: 59295c19e9SNikolai Kosjar void SetUp() override { ResetVFS(); } 60295c19e9SNikolai Kosjar void TearDown() override {} 61295c19e9SNikolai Kosjar 62295c19e9SNikolai Kosjar void ResetVFS() { 63b5b48db1SCameron Desrochers VFS = new ReadCountingInMemoryFileSystem(); 64b5b48db1SCameron Desrochers // We need the working directory to be set to something absolute, 65b5b48db1SCameron Desrochers // otherwise it ends up being inadvertently set to the current 66b5b48db1SCameron Desrochers // working directory in the real file system due to a series of 67b5b48db1SCameron Desrochers // unfortunate conditions interacting badly. 68b5b48db1SCameron Desrochers // What's more, this path *must* be absolute on all (real) 69b5b48db1SCameron Desrochers // filesystems, so just '/' won't work (e.g. on Win32). 70b5b48db1SCameron Desrochers VFS->setCurrentWorkingDirectory("//./"); 71b5b48db1SCameron Desrochers } 72b5b48db1SCameron Desrochers 73b5b48db1SCameron Desrochers void AddFile(const std::string &Filename, const std::string &Contents) { 74b5b48db1SCameron Desrochers ::time_t now; 75b5b48db1SCameron Desrochers ::time(&now); 76b5b48db1SCameron Desrochers VFS->addFile(Filename, now, MemoryBuffer::getMemBufferCopy(Contents, Filename)); 77b5b48db1SCameron Desrochers } 78b5b48db1SCameron Desrochers 79b5b48db1SCameron Desrochers void RemapFile(const std::string &Filename, const std::string &Contents) { 80b5b48db1SCameron Desrochers RemappedFiles[Filename] = Contents; 81b5b48db1SCameron Desrochers } 82b5b48db1SCameron Desrochers 83b5b48db1SCameron Desrochers std::unique_ptr<ASTUnit> ParseAST(const std::string &EntryFile) { 84b5b48db1SCameron Desrochers PCHContainerOpts = std::make_shared<PCHContainerOperations>(); 85b5b48db1SCameron Desrochers std::shared_ptr<CompilerInvocation> CI(new CompilerInvocation); 86b5b48db1SCameron Desrochers CI->getFrontendOpts().Inputs.push_back( 87b5b48db1SCameron Desrochers FrontendInputFile(EntryFile, FrontendOptions::getInputKindForExtension( 88b5b48db1SCameron Desrochers llvm::sys::path::extension(EntryFile).substr(1)))); 89b5b48db1SCameron Desrochers 90b5b48db1SCameron Desrochers CI->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 91b5b48db1SCameron Desrochers 92b5b48db1SCameron Desrochers CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles(); 93b5b48db1SCameron Desrochers 94b5b48db1SCameron Desrochers PreprocessorOptions &PPOpts = CI->getPreprocessorOpts(); 95b5b48db1SCameron Desrochers PPOpts.RemappedFilesKeepOriginalName = true; 96b5b48db1SCameron Desrochers 97*df9a14d7SKadir Cetinkaya IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 98*df9a14d7SKadir Cetinkaya CompilerInstance::createDiagnostics(*VFS, new DiagnosticOptions, 99*df9a14d7SKadir Cetinkaya new DiagnosticConsumer)); 100b5b48db1SCameron Desrochers 101b5b48db1SCameron Desrochers FileManager *FileMgr = new FileManager(FSOpts, VFS); 102b5b48db1SCameron Desrochers 103b5b48db1SCameron Desrochers std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation( 1048edd8da4SNikolai Kosjar CI, PCHContainerOpts, Diags, FileMgr, false, CaptureDiagsKind::None, 105b5b48db1SCameron Desrochers /*PrecompilePreambleAfterNParses=*/1); 106b5b48db1SCameron Desrochers return AST; 107b5b48db1SCameron Desrochers } 108b5b48db1SCameron Desrochers 109b5b48db1SCameron Desrochers bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) { 110b5b48db1SCameron Desrochers bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS); 111b5b48db1SCameron Desrochers return !reparseFailed; 112b5b48db1SCameron Desrochers } 113b5b48db1SCameron Desrochers 114b5b48db1SCameron Desrochers unsigned GetFileReadCount(const std::string &Filename) const { 115b5b48db1SCameron Desrochers return VFS->GetReadCount(Filename); 116b5b48db1SCameron Desrochers } 117b5b48db1SCameron Desrochers 118b5b48db1SCameron Desrochers private: 119b5b48db1SCameron Desrochers std::vector<std::pair<std::string, llvm::MemoryBuffer *>> 120b5b48db1SCameron Desrochers GetRemappedFiles() const { 121b5b48db1SCameron Desrochers std::vector<std::pair<std::string, llvm::MemoryBuffer *>> Remapped; 122b5b48db1SCameron Desrochers for (const auto &RemappedFile : RemappedFiles) { 123b5b48db1SCameron Desrochers std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy( 124b5b48db1SCameron Desrochers RemappedFile.second, RemappedFile.first()); 125757bdc64SBenjamin Kramer Remapped.emplace_back(std::string(RemappedFile.first()), buf.release()); 126b5b48db1SCameron Desrochers } 127b5b48db1SCameron Desrochers return Remapped; 128b5b48db1SCameron Desrochers } 129b5b48db1SCameron Desrochers }; 130b5b48db1SCameron Desrochers 131295c19e9SNikolai Kosjar TEST_F(PCHPreambleTest, ReparseReusesPreambleWithUnsavedFileNotExistingOnDisk) { 132295c19e9SNikolai Kosjar std::string Header1 = "//./header1.h"; 133295c19e9SNikolai Kosjar std::string MainName = "//./main.cpp"; 134295c19e9SNikolai Kosjar AddFile(MainName, R"cpp( 135295c19e9SNikolai Kosjar #include "//./header1.h" 136295c19e9SNikolai Kosjar int main() { return ZERO; } 137295c19e9SNikolai Kosjar )cpp"); 138295c19e9SNikolai Kosjar RemapFile(Header1, "#define ZERO 0\n"); 139295c19e9SNikolai Kosjar 140295c19e9SNikolai Kosjar // Parse with header file provided as unsaved file, which does not exist on 141295c19e9SNikolai Kosjar // disk. 142295c19e9SNikolai Kosjar std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 143295c19e9SNikolai Kosjar ASSERT_TRUE(AST.get()); 144295c19e9SNikolai Kosjar ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 145295c19e9SNikolai Kosjar 146295c19e9SNikolai Kosjar // Reparse and check that the preamble was reused. 147295c19e9SNikolai Kosjar ASSERT_TRUE(ReparseAST(AST)); 148295c19e9SNikolai Kosjar ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 149295c19e9SNikolai Kosjar } 150295c19e9SNikolai Kosjar 151295c19e9SNikolai Kosjar TEST_F(PCHPreambleTest, ReparseReusesPreambleAfterUnsavedFileWasCreatedOnDisk) { 152295c19e9SNikolai Kosjar std::string Header1 = "//./header1.h"; 153295c19e9SNikolai Kosjar std::string MainName = "//./main.cpp"; 154295c19e9SNikolai Kosjar AddFile(MainName, R"cpp( 155295c19e9SNikolai Kosjar #include "//./header1.h" 156295c19e9SNikolai Kosjar int main() { return ZERO; } 157295c19e9SNikolai Kosjar )cpp"); 158295c19e9SNikolai Kosjar RemapFile(Header1, "#define ZERO 0\n"); 159295c19e9SNikolai Kosjar 160295c19e9SNikolai Kosjar // Parse with header file provided as unsaved file, which does not exist on 161295c19e9SNikolai Kosjar // disk. 162295c19e9SNikolai Kosjar std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 163295c19e9SNikolai Kosjar ASSERT_TRUE(AST.get()); 164295c19e9SNikolai Kosjar ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 165295c19e9SNikolai Kosjar 166295c19e9SNikolai Kosjar // Create the unsaved file also on disk and check that preamble was reused. 167295c19e9SNikolai Kosjar AddFile(Header1, "#define ZERO 0\n"); 168295c19e9SNikolai Kosjar ASSERT_TRUE(ReparseAST(AST)); 169295c19e9SNikolai Kosjar ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 170295c19e9SNikolai Kosjar } 171295c19e9SNikolai Kosjar 172295c19e9SNikolai Kosjar TEST_F(PCHPreambleTest, 173295c19e9SNikolai Kosjar ReparseReusesPreambleAfterUnsavedFileWasRemovedFromDisk) { 174295c19e9SNikolai Kosjar std::string Header1 = "//./foo/header1.h"; 175295c19e9SNikolai Kosjar std::string MainName = "//./main.cpp"; 176295c19e9SNikolai Kosjar std::string MainFileContent = R"cpp( 177295c19e9SNikolai Kosjar #include "//./foo/header1.h" 178295c19e9SNikolai Kosjar int main() { return ZERO; } 179295c19e9SNikolai Kosjar )cpp"; 180295c19e9SNikolai Kosjar AddFile(MainName, MainFileContent); 181295c19e9SNikolai Kosjar AddFile(Header1, "#define ZERO 0\n"); 182295c19e9SNikolai Kosjar RemapFile(Header1, "#define ZERO 0\n"); 183295c19e9SNikolai Kosjar 184295c19e9SNikolai Kosjar // Parse with header file provided as unsaved file, which exists on disk. 185295c19e9SNikolai Kosjar std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 186295c19e9SNikolai Kosjar ASSERT_TRUE(AST.get()); 187295c19e9SNikolai Kosjar ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 188295c19e9SNikolai Kosjar ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 189295c19e9SNikolai Kosjar 190295c19e9SNikolai Kosjar // Remove the unsaved file from disk and check that the preamble was reused. 191295c19e9SNikolai Kosjar ResetVFS(); 192295c19e9SNikolai Kosjar AddFile(MainName, MainFileContent); 193295c19e9SNikolai Kosjar ASSERT_TRUE(ReparseAST(AST)); 194295c19e9SNikolai Kosjar ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 195295c19e9SNikolai Kosjar } 196295c19e9SNikolai Kosjar 197b5b48db1SCameron Desrochers TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) { 198b5b48db1SCameron Desrochers std::string Header1 = "//./header1.h"; 199b5b48db1SCameron Desrochers std::string Header2 = "//./header2.h"; 200b5b48db1SCameron Desrochers std::string MainName = "//./main.cpp"; 201b5b48db1SCameron Desrochers AddFile(Header1, ""); 202b5b48db1SCameron Desrochers AddFile(Header2, "#pragma once"); 203b5b48db1SCameron Desrochers AddFile(MainName, 204b5b48db1SCameron Desrochers "#include \"//./foo/../header1.h\"\n" 205b5b48db1SCameron Desrochers "#include \"//./foo/../header2.h\"\n" 206b5b48db1SCameron Desrochers "int main() { return ZERO; }"); 207b5b48db1SCameron Desrochers RemapFile(Header1, "static const int ZERO = 0;\n"); 208b5b48db1SCameron Desrochers 209b5b48db1SCameron Desrochers std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 210b5b48db1SCameron Desrochers ASSERT_TRUE(AST.get()); 211b5b48db1SCameron Desrochers ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 212b5b48db1SCameron Desrochers 213b5b48db1SCameron Desrochers unsigned initialCounts[] = { 214b5b48db1SCameron Desrochers GetFileReadCount(MainName), 215b5b48db1SCameron Desrochers GetFileReadCount(Header1), 216b5b48db1SCameron Desrochers GetFileReadCount(Header2) 217b5b48db1SCameron Desrochers }; 218b5b48db1SCameron Desrochers 219b5b48db1SCameron Desrochers ASSERT_TRUE(ReparseAST(AST)); 220b5b48db1SCameron Desrochers 221b5b48db1SCameron Desrochers ASSERT_NE(initialCounts[0], GetFileReadCount(MainName)); 222b5b48db1SCameron Desrochers ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1)); 223b5b48db1SCameron Desrochers ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2)); 224b5b48db1SCameron Desrochers } 225b5b48db1SCameron Desrochers 22684fd064eSCameron Desrochers TEST_F(PCHPreambleTest, ParseWithBom) { 22784fd064eSCameron Desrochers std::string Header = "//./header.h"; 22884fd064eSCameron Desrochers std::string Main = "//./main.cpp"; 22984fd064eSCameron Desrochers AddFile(Header, "int random() { return 4; }"); 23084fd064eSCameron Desrochers AddFile(Main, 23184fd064eSCameron Desrochers "\xef\xbb\xbf" 23284fd064eSCameron Desrochers "#include \"//./header.h\"\n" 23384fd064eSCameron Desrochers "int main() { return random() -2; }"); 23484fd064eSCameron Desrochers 23584fd064eSCameron Desrochers std::unique_ptr<ASTUnit> AST(ParseAST(Main)); 23684fd064eSCameron Desrochers ASSERT_TRUE(AST.get()); 23784fd064eSCameron Desrochers ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 23884fd064eSCameron Desrochers 23984fd064eSCameron Desrochers unsigned HeaderReadCount = GetFileReadCount(Header); 24084fd064eSCameron Desrochers 24184fd064eSCameron Desrochers ASSERT_TRUE(ReparseAST(AST)); 24284fd064eSCameron Desrochers ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 24384fd064eSCameron Desrochers 24484fd064eSCameron Desrochers // Check preamble PCH was really reused 24584fd064eSCameron Desrochers ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header)); 24684fd064eSCameron Desrochers 24784fd064eSCameron Desrochers // Remove BOM 24884fd064eSCameron Desrochers RemapFile(Main, 24984fd064eSCameron Desrochers "#include \"//./header.h\"\n" 25084fd064eSCameron Desrochers "int main() { return random() -2; }"); 25184fd064eSCameron Desrochers 25284fd064eSCameron Desrochers ASSERT_TRUE(ReparseAST(AST)); 25384fd064eSCameron Desrochers ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 25484fd064eSCameron Desrochers 25584fd064eSCameron Desrochers ASSERT_LE(HeaderReadCount, GetFileReadCount(Header)); 25684fd064eSCameron Desrochers HeaderReadCount = GetFileReadCount(Header); 25784fd064eSCameron Desrochers 25884fd064eSCameron Desrochers // Add BOM back 25984fd064eSCameron Desrochers RemapFile(Main, 26084fd064eSCameron Desrochers "\xef\xbb\xbf" 26184fd064eSCameron Desrochers "#include \"//./header.h\"\n" 26284fd064eSCameron Desrochers "int main() { return random() -2; }"); 26384fd064eSCameron Desrochers 26484fd064eSCameron Desrochers ASSERT_TRUE(ReparseAST(AST)); 26584fd064eSCameron Desrochers ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 26684fd064eSCameron Desrochers 26784fd064eSCameron Desrochers ASSERT_LE(HeaderReadCount, GetFileReadCount(Header)); 26884fd064eSCameron Desrochers } 26984fd064eSCameron Desrochers 270b5b48db1SCameron Desrochers } // anonymous namespace 271