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:
openFileForRead(const Twine & Path)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 
GetReadCount(const Twine & Path) const41   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:
SetUp()55   void SetUp() override { ResetVFS(); }
TearDown()56   void TearDown() override {}
57 
ResetVFS()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 
AddFile(const std::string & Filename,const std::string & Contents)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 
RemapFile(const std::string & Filename,const std::string & Contents)75   void RemapFile(const std::string &Filename, const std::string &Contents) {
76     RemappedFiles[Filename] = Contents;
77   }
78 
ParseAST(const std::string & EntryFile)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 
ReparseAST(const std::unique_ptr<ASTUnit> & AST)104   bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) {
105     bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS);
106     return !reparseFailed;
107   }
108 
GetFileReadCount(const std::string & Filename) const109   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 *>>
GetRemappedFiles() const115   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 
TEST_F(PCHPreambleTest,ReparseReusesPreambleWithUnsavedFileNotExistingOnDisk)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 
TEST_F(PCHPreambleTest,ParseWithBom)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