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