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> Diags( 98 CompilerInstance::createDiagnostics(*VFS, new DiagnosticOptions, 99 new DiagnosticConsumer)); 100 101 FileManager *FileMgr = new FileManager(FSOpts, VFS); 102 103 std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation( 104 CI, PCHContainerOpts, Diags, FileMgr, false, CaptureDiagsKind::None, 105 /*PrecompilePreambleAfterNParses=*/1); 106 return AST; 107 } 108 109 bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) { 110 bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS); 111 return !reparseFailed; 112 } 113 114 unsigned GetFileReadCount(const std::string &Filename) const { 115 return VFS->GetReadCount(Filename); 116 } 117 118 private: 119 std::vector<std::pair<std::string, llvm::MemoryBuffer *>> 120 GetRemappedFiles() const { 121 std::vector<std::pair<std::string, llvm::MemoryBuffer *>> Remapped; 122 for (const auto &RemappedFile : RemappedFiles) { 123 std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy( 124 RemappedFile.second, RemappedFile.first()); 125 Remapped.emplace_back(std::string(RemappedFile.first()), buf.release()); 126 } 127 return Remapped; 128 } 129 }; 130 131 TEST_F(PCHPreambleTest, ReparseReusesPreambleWithUnsavedFileNotExistingOnDisk) { 132 std::string Header1 = "//./header1.h"; 133 std::string MainName = "//./main.cpp"; 134 AddFile(MainName, R"cpp( 135 #include "//./header1.h" 136 int main() { return ZERO; } 137 )cpp"); 138 RemapFile(Header1, "#define ZERO 0\n"); 139 140 // Parse with header file provided as unsaved file, which does not exist on 141 // disk. 142 std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 143 ASSERT_TRUE(AST.get()); 144 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 145 146 // Reparse and check that the preamble was reused. 147 ASSERT_TRUE(ReparseAST(AST)); 148 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 149 } 150 151 TEST_F(PCHPreambleTest, ReparseReusesPreambleAfterUnsavedFileWasCreatedOnDisk) { 152 std::string Header1 = "//./header1.h"; 153 std::string MainName = "//./main.cpp"; 154 AddFile(MainName, R"cpp( 155 #include "//./header1.h" 156 int main() { return ZERO; } 157 )cpp"); 158 RemapFile(Header1, "#define ZERO 0\n"); 159 160 // Parse with header file provided as unsaved file, which does not exist on 161 // disk. 162 std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 163 ASSERT_TRUE(AST.get()); 164 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 165 166 // Create the unsaved file also on disk and check that preamble was reused. 167 AddFile(Header1, "#define ZERO 0\n"); 168 ASSERT_TRUE(ReparseAST(AST)); 169 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 170 } 171 172 TEST_F(PCHPreambleTest, 173 ReparseReusesPreambleAfterUnsavedFileWasRemovedFromDisk) { 174 std::string Header1 = "//./foo/header1.h"; 175 std::string MainName = "//./main.cpp"; 176 std::string MainFileContent = R"cpp( 177 #include "//./foo/header1.h" 178 int main() { return ZERO; } 179 )cpp"; 180 AddFile(MainName, MainFileContent); 181 AddFile(Header1, "#define ZERO 0\n"); 182 RemapFile(Header1, "#define ZERO 0\n"); 183 184 // Parse with header file provided as unsaved file, which exists on disk. 185 std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 186 ASSERT_TRUE(AST.get()); 187 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 188 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 189 190 // Remove the unsaved file from disk and check that the preamble was reused. 191 ResetVFS(); 192 AddFile(MainName, MainFileContent); 193 ASSERT_TRUE(ReparseAST(AST)); 194 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U); 195 } 196 197 TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) { 198 std::string Header1 = "//./header1.h"; 199 std::string Header2 = "//./header2.h"; 200 std::string MainName = "//./main.cpp"; 201 AddFile(Header1, ""); 202 AddFile(Header2, "#pragma once"); 203 AddFile(MainName, 204 "#include \"//./foo/../header1.h\"\n" 205 "#include \"//./foo/../header2.h\"\n" 206 "int main() { return ZERO; }"); 207 RemapFile(Header1, "static const int ZERO = 0;\n"); 208 209 std::unique_ptr<ASTUnit> AST(ParseAST(MainName)); 210 ASSERT_TRUE(AST.get()); 211 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 212 213 unsigned initialCounts[] = { 214 GetFileReadCount(MainName), 215 GetFileReadCount(Header1), 216 GetFileReadCount(Header2) 217 }; 218 219 ASSERT_TRUE(ReparseAST(AST)); 220 221 ASSERT_NE(initialCounts[0], GetFileReadCount(MainName)); 222 ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1)); 223 ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2)); 224 } 225 226 TEST_F(PCHPreambleTest, ParseWithBom) { 227 std::string Header = "//./header.h"; 228 std::string Main = "//./main.cpp"; 229 AddFile(Header, "int random() { return 4; }"); 230 AddFile(Main, 231 "\xef\xbb\xbf" 232 "#include \"//./header.h\"\n" 233 "int main() { return random() -2; }"); 234 235 std::unique_ptr<ASTUnit> AST(ParseAST(Main)); 236 ASSERT_TRUE(AST.get()); 237 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 238 239 unsigned HeaderReadCount = GetFileReadCount(Header); 240 241 ASSERT_TRUE(ReparseAST(AST)); 242 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 243 244 // Check preamble PCH was really reused 245 ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header)); 246 247 // Remove BOM 248 RemapFile(Main, 249 "#include \"//./header.h\"\n" 250 "int main() { return random() -2; }"); 251 252 ASSERT_TRUE(ReparseAST(AST)); 253 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 254 255 ASSERT_LE(HeaderReadCount, GetFileReadCount(Header)); 256 HeaderReadCount = GetFileReadCount(Header); 257 258 // Add BOM back 259 RemapFile(Main, 260 "\xef\xbb\xbf" 261 "#include \"//./header.h\"\n" 262 "int main() { return random() -2; }"); 263 264 ASSERT_TRUE(ReparseAST(AST)); 265 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 266 267 ASSERT_LE(HeaderReadCount, GetFileReadCount(Header)); 268 } 269 270 } // anonymous namespace 271