xref: /llvm-project/clang/unittests/Frontend/PCHPreambleTest.cpp (revision df9a14d7bbf1180e4f1474254c9d7ed6bcb4ce55)
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