1 //====-- unittests/Frontend/PCHPreambleTest.cpp - FrontendAction tests ---====// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "clang/Frontend/ASTUnit.h" 10 #include "clang/Frontend/CompilerInvocation.h" 11 #include "clang/Frontend/CompilerInstance.h" 12 #include "clang/Frontend/FrontendActions.h" 13 #include "clang/Frontend/FrontendOptions.h" 14 #include "clang/Lex/PreprocessorOptions.h" 15 #include "clang/Basic/Diagnostic.h" 16 #include "clang/Basic/FileManager.h" 17 #include "llvm/Support/FileSystem.h" 18 #include "llvm/Support/MemoryBuffer.h" 19 #include "llvm/Support/Path.h" 20 #include "gtest/gtest.h" 21 22 using namespace llvm; 23 using namespace clang; 24 25 namespace { 26 27 class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem 28 { 29 std::map<std::string, unsigned> ReadCounts; 30 31 public: 32 ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override 33 { 34 SmallVector<char, 128> PathVec; 35 Path.toVector(PathVec); 36 llvm::sys::path::remove_dots(PathVec, true); 37 ++ReadCounts[std::string(PathVec.begin(), PathVec.end())]; 38 return InMemoryFileSystem::openFileForRead(Path); 39 } 40 41 unsigned GetReadCount(const Twine &Path) const 42 { 43 auto it = ReadCounts.find(Path.str()); 44 return it == ReadCounts.end() ? 0 : it->second; 45 } 46 }; 47 48 class PCHPreambleTest : public ::testing::Test { 49 IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS; 50 StringMap<std::string> RemappedFiles; 51 std::shared_ptr<PCHContainerOperations> PCHContainerOpts; 52 FileSystemOptions FSOpts; 53 54 public: 55 void SetUp() override { ResetVFS(); } 56 void TearDown() override {} 57 58 void ResetVFS() { 59 VFS = new ReadCountingInMemoryFileSystem(); 60 // We need the working directory to be set to something absolute, 61 // otherwise it ends up being inadvertently set to the current 62 // working directory in the real file system due to a series of 63 // unfortunate conditions interacting badly. 64 // What's more, this path *must* be absolute on all (real) 65 // filesystems, so just '/' won't work (e.g. on Win32). 66 VFS->setCurrentWorkingDirectory("//./"); 67 } 68 69 void AddFile(const std::string &Filename, const std::string &Contents) { 70 ::time_t now; 71 ::time(&now); 72 VFS->addFile(Filename, now, MemoryBuffer::getMemBufferCopy(Contents, Filename)); 73 } 74 75 void RemapFile(const std::string &Filename, const std::string &Contents) { 76 RemappedFiles[Filename] = Contents; 77 } 78 79 std::unique_ptr<ASTUnit> ParseAST(const std::string &EntryFile) { 80 PCHContainerOpts = std::make_shared<PCHContainerOperations>(); 81 std::shared_ptr<CompilerInvocation> CI(new CompilerInvocation); 82 CI->getFrontendOpts().Inputs.push_back( 83 FrontendInputFile(EntryFile, FrontendOptions::getInputKindForExtension( 84 llvm::sys::path::extension(EntryFile).substr(1)))); 85 86 CI->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 87 88 CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles(); 89 90 PreprocessorOptions &PPOpts = CI->getPreprocessorOpts(); 91 PPOpts.RemappedFilesKeepOriginalName = true; 92 93 IntrusiveRefCntPtr<DiagnosticsEngine> 94 Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions, new DiagnosticConsumer)); 95 96 FileManager *FileMgr = new FileManager(FSOpts, VFS); 97 98 std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation( 99 CI, PCHContainerOpts, Diags, FileMgr, false, CaptureDiagsKind::None, 100 /*PrecompilePreambleAfterNParses=*/1); 101 return AST; 102 } 103 104 bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) { 105 bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS); 106 return !reparseFailed; 107 } 108 109 unsigned GetFileReadCount(const std::string &Filename) const { 110 return VFS->GetReadCount(Filename); 111 } 112 113 private: 114 std::vector<std::pair<std::string, llvm::MemoryBuffer *>> 115 GetRemappedFiles() const { 116 std::vector<std::pair<std::string, llvm::MemoryBuffer *>> Remapped; 117 for (const auto &RemappedFile : RemappedFiles) { 118 std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy( 119 RemappedFile.second, RemappedFile.first()); 120 Remapped.emplace_back(std::string(RemappedFile.first()), buf.release()); 121 } 122 return Remapped; 123 } 124 }; 125 126 TEST_F(PCHPreambleTest, ReparseReusesPreambleWithUnsavedFileNotExistingOnDisk) { 127 std::string Header1 = "//./header1.h"; 128 std::string MainName = "//./main.cpp"; 129 AddFile(MainName, R"cpp( 130 #include "//./header1.h" 131 int main() { return ZERO; } 132 )cpp"); 133 RemapFile(Header1, "#define ZERO 0\n"); 134 135 // Parse with header file provided as unsaved file, which does not exist on 136 // disk. 137 std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 138 ASSERT_TRUE(AST.get()); 139 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 140 141 // Reparse and check that the preamble was reused. 142 ASSERT_TRUE(ReparseAST(AST)); 143 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 144 } 145 146 TEST_F(PCHPreambleTest, ReparseReusesPreambleAfterUnsavedFileWasCreatedOnDisk) { 147 std::string Header1 = "//./header1.h"; 148 std::string MainName = "//./main.cpp"; 149 AddFile(MainName, R"cpp( 150 #include "//./header1.h" 151 int main() { return ZERO; } 152 )cpp"); 153 RemapFile(Header1, "#define ZERO 0\n"); 154 155 // Parse with header file provided as unsaved file, which does not exist on 156 // disk. 157 std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 158 ASSERT_TRUE(AST.get()); 159 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 160 161 // Create the unsaved file also on disk and check that preamble was reused. 162 AddFile(Header1, "#define ZERO 0\n"); 163 ASSERT_TRUE(ReparseAST(AST)); 164 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 165 } 166 167 TEST_F(PCHPreambleTest, 168 ReparseReusesPreambleAfterUnsavedFileWasRemovedFromDisk) { 169 std::string Header1 = "//./foo/header1.h"; 170 std::string MainName = "//./main.cpp"; 171 std::string MainFileContent = R"cpp( 172 #include "//./foo/header1.h" 173 int main() { return ZERO; } 174 )cpp"; 175 AddFile(MainName, MainFileContent); 176 AddFile(Header1, "#define ZERO 0\n"); 177 RemapFile(Header1, "#define ZERO 0\n"); 178 179 // Parse with header file provided as unsaved file, which exists on disk. 180 std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 181 ASSERT_TRUE(AST.get()); 182 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 183 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 184 185 // Remove the unsaved file from disk and check that the preamble was reused. 186 ResetVFS(); 187 AddFile(MainName, MainFileContent); 188 ASSERT_TRUE(ReparseAST(AST)); 189 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 190 } 191 192 TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) { 193 std::string Header1 = "//./header1.h"; 194 std::string Header2 = "//./header2.h"; 195 std::string MainName = "//./main.cpp"; 196 AddFile(Header1, ""); 197 AddFile(Header2, "#pragma once"); 198 AddFile(MainName, 199 "#include \"//./foo/../header1.h\"\n" 200 "#include \"//./foo/../header2.h\"\n" 201 "int main() { return ZERO; }"); 202 RemapFile(Header1, "static const int ZERO = 0;\n"); 203 204 std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 205 ASSERT_TRUE(AST.get()); 206 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 207 208 unsigned initialCounts[] = { 209 GetFileReadCount(MainName), 210 GetFileReadCount(Header1), 211 GetFileReadCount(Header2) 212 }; 213 214 ASSERT_TRUE(ReparseAST(AST)); 215 216 ASSERT_NE(initialCounts[0], GetFileReadCount(MainName)); 217 ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1)); 218 ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2)); 219 } 220 221 TEST_F(PCHPreambleTest, ParseWithBom) { 222 std::string Header = "//./header.h"; 223 std::string Main = "//./main.cpp"; 224 AddFile(Header, "int random() { return 4; }"); 225 AddFile(Main, 226 "\xef\xbb\xbf" 227 "#include \"//./header.h\"\n" 228 "int main() { return random() -2; }"); 229 230 std::unique_ptr<ASTUnit> AST(ParseAST(Main)); 231 ASSERT_TRUE(AST.get()); 232 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 233 234 unsigned HeaderReadCount = GetFileReadCount(Header); 235 236 ASSERT_TRUE(ReparseAST(AST)); 237 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 238 239 // Check preamble PCH was really reused 240 ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header)); 241 242 // Remove BOM 243 RemapFile(Main, 244 "#include \"//./header.h\"\n" 245 "int main() { return random() -2; }"); 246 247 ASSERT_TRUE(ReparseAST(AST)); 248 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 249 250 ASSERT_LE(HeaderReadCount, GetFileReadCount(Header)); 251 HeaderReadCount = GetFileReadCount(Header); 252 253 // Add BOM back 254 RemapFile(Main, 255 "\xef\xbb\xbf" 256 "#include \"//./header.h\"\n" 257 "int main() { return random() -2; }"); 258 259 ASSERT_TRUE(ReparseAST(AST)); 260 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 261 262 ASSERT_LE(HeaderReadCount, GetFileReadCount(Header)); 263 } 264 265 } // anonymous namespace 266