PCHPreambleTest.cpp 8.7 KB

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