xref: /llvm-project/clang/unittests/Frontend/PCHPreambleTest.cpp (revision df9a14d7bbf1180e4f1474254c9d7ed6bcb4ce55)
1b5b48db1SCameron Desrochers //====-- unittests/Frontend/PCHPreambleTest.cpp - FrontendAction tests ---====//
2b5b48db1SCameron Desrochers //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6b5b48db1SCameron Desrochers //
7b5b48db1SCameron Desrochers //===----------------------------------------------------------------------===//
8b5b48db1SCameron Desrochers 
9b5b48db1SCameron Desrochers #include "clang/Frontend/ASTUnit.h"
10b5b48db1SCameron Desrochers #include "clang/Frontend/CompilerInvocation.h"
11b5b48db1SCameron Desrochers #include "clang/Frontend/CompilerInstance.h"
12b5b48db1SCameron Desrochers #include "clang/Frontend/FrontendActions.h"
13b5b48db1SCameron Desrochers #include "clang/Frontend/FrontendOptions.h"
14b5b48db1SCameron Desrochers #include "clang/Lex/PreprocessorOptions.h"
15b5b48db1SCameron Desrochers #include "clang/Basic/Diagnostic.h"
16b5b48db1SCameron Desrochers #include "clang/Basic/FileManager.h"
17b5b48db1SCameron Desrochers #include "llvm/Support/FileSystem.h"
18b5b48db1SCameron Desrochers #include "llvm/Support/MemoryBuffer.h"
19b5b48db1SCameron Desrochers #include "llvm/Support/Path.h"
20b5b48db1SCameron Desrochers #include "gtest/gtest.h"
21b5b48db1SCameron Desrochers 
22b5b48db1SCameron Desrochers using namespace llvm;
23b5b48db1SCameron Desrochers using namespace clang;
24b5b48db1SCameron Desrochers 
25b5b48db1SCameron Desrochers namespace {
26b5b48db1SCameron Desrochers 
2735ab2a11SPaul Pluzhnikov std::string Canonicalize(const Twine &Path) {
2835ab2a11SPaul Pluzhnikov   SmallVector<char, 128> PathVec;
2935ab2a11SPaul Pluzhnikov   Path.toVector(PathVec);
3035ab2a11SPaul Pluzhnikov   llvm::sys::path::remove_dots(PathVec, true);
3135ab2a11SPaul Pluzhnikov   return std::string(PathVec.begin(), PathVec.end());
3235ab2a11SPaul Pluzhnikov }
3335ab2a11SPaul Pluzhnikov 
34b5b48db1SCameron Desrochers class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem
35b5b48db1SCameron Desrochers {
36b5b48db1SCameron Desrochers   std::map<std::string, unsigned> ReadCounts;
37b5b48db1SCameron Desrochers 
38b5b48db1SCameron Desrochers public:
39efdb3ae2SAbhina Sreeskantharajan   ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override
40efdb3ae2SAbhina Sreeskantharajan   {
4135ab2a11SPaul Pluzhnikov     ++ReadCounts[Canonicalize(Path)];
42efdb3ae2SAbhina Sreeskantharajan     return InMemoryFileSystem::openFileForRead(Path);
43b5b48db1SCameron Desrochers   }
44b5b48db1SCameron Desrochers 
45b5b48db1SCameron Desrochers   unsigned GetReadCount(const Twine &Path) const
46b5b48db1SCameron Desrochers   {
4735ab2a11SPaul Pluzhnikov     auto it = ReadCounts.find(Canonicalize(Path));
48b5b48db1SCameron Desrochers     return it == ReadCounts.end() ? 0 : it->second;
49b5b48db1SCameron Desrochers   }
50b5b48db1SCameron Desrochers };
51b5b48db1SCameron Desrochers 
52b5b48db1SCameron Desrochers class PCHPreambleTest : public ::testing::Test {
53b5b48db1SCameron Desrochers   IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS;
54b5b48db1SCameron Desrochers   StringMap<std::string> RemappedFiles;
55b5b48db1SCameron Desrochers   std::shared_ptr<PCHContainerOperations> PCHContainerOpts;
56b5b48db1SCameron Desrochers   FileSystemOptions FSOpts;
57b5b48db1SCameron Desrochers 
58b5b48db1SCameron Desrochers public:
59295c19e9SNikolai Kosjar   void SetUp() override { ResetVFS(); }
60295c19e9SNikolai Kosjar   void TearDown() override {}
61295c19e9SNikolai Kosjar 
62295c19e9SNikolai Kosjar   void ResetVFS() {
63b5b48db1SCameron Desrochers     VFS = new ReadCountingInMemoryFileSystem();
64b5b48db1SCameron Desrochers     // We need the working directory to be set to something absolute,
65b5b48db1SCameron Desrochers     // otherwise it ends up being inadvertently set to the current
66b5b48db1SCameron Desrochers     // working directory in the real file system due to a series of
67b5b48db1SCameron Desrochers     // unfortunate conditions interacting badly.
68b5b48db1SCameron Desrochers     // What's more, this path *must* be absolute on all (real)
69b5b48db1SCameron Desrochers     // filesystems, so just '/' won't work (e.g. on Win32).
70b5b48db1SCameron Desrochers     VFS->setCurrentWorkingDirectory("//./");
71b5b48db1SCameron Desrochers   }
72b5b48db1SCameron Desrochers 
73b5b48db1SCameron Desrochers   void AddFile(const std::string &Filename, const std::string &Contents) {
74b5b48db1SCameron Desrochers     ::time_t now;
75b5b48db1SCameron Desrochers     ::time(&now);
76b5b48db1SCameron Desrochers     VFS->addFile(Filename, now, MemoryBuffer::getMemBufferCopy(Contents, Filename));
77b5b48db1SCameron Desrochers   }
78b5b48db1SCameron Desrochers 
79b5b48db1SCameron Desrochers   void RemapFile(const std::string &Filename, const std::string &Contents) {
80b5b48db1SCameron Desrochers     RemappedFiles[Filename] = Contents;
81b5b48db1SCameron Desrochers   }
82b5b48db1SCameron Desrochers 
83b5b48db1SCameron Desrochers   std::unique_ptr<ASTUnit> ParseAST(const std::string &EntryFile) {
84b5b48db1SCameron Desrochers     PCHContainerOpts = std::make_shared<PCHContainerOperations>();
85b5b48db1SCameron Desrochers     std::shared_ptr<CompilerInvocation> CI(new CompilerInvocation);
86b5b48db1SCameron Desrochers     CI->getFrontendOpts().Inputs.push_back(
87b5b48db1SCameron Desrochers       FrontendInputFile(EntryFile, FrontendOptions::getInputKindForExtension(
88b5b48db1SCameron Desrochers         llvm::sys::path::extension(EntryFile).substr(1))));
89b5b48db1SCameron Desrochers 
90b5b48db1SCameron Desrochers     CI->getTargetOpts().Triple = "i386-unknown-linux-gnu";
91b5b48db1SCameron Desrochers 
92b5b48db1SCameron Desrochers     CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles();
93b5b48db1SCameron Desrochers 
94b5b48db1SCameron Desrochers     PreprocessorOptions &PPOpts = CI->getPreprocessorOpts();
95b5b48db1SCameron Desrochers     PPOpts.RemappedFilesKeepOriginalName = true;
96b5b48db1SCameron Desrochers 
97*df9a14d7SKadir Cetinkaya     IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
98*df9a14d7SKadir Cetinkaya         CompilerInstance::createDiagnostics(*VFS, new DiagnosticOptions,
99*df9a14d7SKadir Cetinkaya                                             new DiagnosticConsumer));
100b5b48db1SCameron Desrochers 
101b5b48db1SCameron Desrochers     FileManager *FileMgr = new FileManager(FSOpts, VFS);
102b5b48db1SCameron Desrochers 
103b5b48db1SCameron Desrochers     std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
1048edd8da4SNikolai Kosjar         CI, PCHContainerOpts, Diags, FileMgr, false, CaptureDiagsKind::None,
105b5b48db1SCameron Desrochers         /*PrecompilePreambleAfterNParses=*/1);
106b5b48db1SCameron Desrochers     return AST;
107b5b48db1SCameron Desrochers   }
108b5b48db1SCameron Desrochers 
109b5b48db1SCameron Desrochers   bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) {
110b5b48db1SCameron Desrochers     bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS);
111b5b48db1SCameron Desrochers     return !reparseFailed;
112b5b48db1SCameron Desrochers   }
113b5b48db1SCameron Desrochers 
114b5b48db1SCameron Desrochers   unsigned GetFileReadCount(const std::string &Filename) const {
115b5b48db1SCameron Desrochers     return VFS->GetReadCount(Filename);
116b5b48db1SCameron Desrochers   }
117b5b48db1SCameron Desrochers 
118b5b48db1SCameron Desrochers private:
119b5b48db1SCameron Desrochers   std::vector<std::pair<std::string, llvm::MemoryBuffer *>>
120b5b48db1SCameron Desrochers   GetRemappedFiles() const {
121b5b48db1SCameron Desrochers     std::vector<std::pair<std::string, llvm::MemoryBuffer *>> Remapped;
122b5b48db1SCameron Desrochers     for (const auto &RemappedFile : RemappedFiles) {
123b5b48db1SCameron Desrochers       std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy(
124b5b48db1SCameron Desrochers         RemappedFile.second, RemappedFile.first());
125757bdc64SBenjamin Kramer       Remapped.emplace_back(std::string(RemappedFile.first()), buf.release());
126b5b48db1SCameron Desrochers     }
127b5b48db1SCameron Desrochers     return Remapped;
128b5b48db1SCameron Desrochers   }
129b5b48db1SCameron Desrochers };
130b5b48db1SCameron Desrochers 
131295c19e9SNikolai Kosjar TEST_F(PCHPreambleTest, ReparseReusesPreambleWithUnsavedFileNotExistingOnDisk) {
132295c19e9SNikolai Kosjar   std::string Header1 = "//./header1.h";
133295c19e9SNikolai Kosjar   std::string MainName = "//./main.cpp";
134295c19e9SNikolai Kosjar   AddFile(MainName, R"cpp(
135295c19e9SNikolai Kosjar #include "//./header1.h"
136295c19e9SNikolai Kosjar int main() { return ZERO; }
137295c19e9SNikolai Kosjar )cpp");
138295c19e9SNikolai Kosjar   RemapFile(Header1, "#define ZERO 0\n");
139295c19e9SNikolai Kosjar 
140295c19e9SNikolai Kosjar   // Parse with header file provided as unsaved file, which does not exist on
141295c19e9SNikolai Kosjar   // disk.
142295c19e9SNikolai Kosjar   std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
143295c19e9SNikolai Kosjar   ASSERT_TRUE(AST.get());
144295c19e9SNikolai Kosjar   ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
145295c19e9SNikolai Kosjar 
146295c19e9SNikolai Kosjar   // Reparse and check that the preamble was reused.
147295c19e9SNikolai Kosjar   ASSERT_TRUE(ReparseAST(AST));
148295c19e9SNikolai Kosjar   ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
149295c19e9SNikolai Kosjar }
150295c19e9SNikolai Kosjar 
151295c19e9SNikolai Kosjar TEST_F(PCHPreambleTest, ReparseReusesPreambleAfterUnsavedFileWasCreatedOnDisk) {
152295c19e9SNikolai Kosjar   std::string Header1 = "//./header1.h";
153295c19e9SNikolai Kosjar   std::string MainName = "//./main.cpp";
154295c19e9SNikolai Kosjar   AddFile(MainName, R"cpp(
155295c19e9SNikolai Kosjar #include "//./header1.h"
156295c19e9SNikolai Kosjar int main() { return ZERO; }
157295c19e9SNikolai Kosjar )cpp");
158295c19e9SNikolai Kosjar   RemapFile(Header1, "#define ZERO 0\n");
159295c19e9SNikolai Kosjar 
160295c19e9SNikolai Kosjar   // Parse with header file provided as unsaved file, which does not exist on
161295c19e9SNikolai Kosjar   // disk.
162295c19e9SNikolai Kosjar   std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
163295c19e9SNikolai Kosjar   ASSERT_TRUE(AST.get());
164295c19e9SNikolai Kosjar   ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
165295c19e9SNikolai Kosjar 
166295c19e9SNikolai Kosjar   // Create the unsaved file also on disk and check that preamble was reused.
167295c19e9SNikolai Kosjar   AddFile(Header1, "#define ZERO 0\n");
168295c19e9SNikolai Kosjar   ASSERT_TRUE(ReparseAST(AST));
169295c19e9SNikolai Kosjar   ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
170295c19e9SNikolai Kosjar }
171295c19e9SNikolai Kosjar 
172295c19e9SNikolai Kosjar TEST_F(PCHPreambleTest,
173295c19e9SNikolai Kosjar        ReparseReusesPreambleAfterUnsavedFileWasRemovedFromDisk) {
174295c19e9SNikolai Kosjar   std::string Header1 = "//./foo/header1.h";
175295c19e9SNikolai Kosjar   std::string MainName = "//./main.cpp";
176295c19e9SNikolai Kosjar   std::string MainFileContent = R"cpp(
177295c19e9SNikolai Kosjar #include "//./foo/header1.h"
178295c19e9SNikolai Kosjar int main() { return ZERO; }
179295c19e9SNikolai Kosjar )cpp";
180295c19e9SNikolai Kosjar   AddFile(MainName, MainFileContent);
181295c19e9SNikolai Kosjar   AddFile(Header1, "#define ZERO 0\n");
182295c19e9SNikolai Kosjar   RemapFile(Header1, "#define ZERO 0\n");
183295c19e9SNikolai Kosjar 
184295c19e9SNikolai Kosjar   // Parse with header file provided as unsaved file, which exists on disk.
185295c19e9SNikolai Kosjar   std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
186295c19e9SNikolai Kosjar   ASSERT_TRUE(AST.get());
187295c19e9SNikolai Kosjar   ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
188295c19e9SNikolai Kosjar   ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
189295c19e9SNikolai Kosjar 
190295c19e9SNikolai Kosjar   // Remove the unsaved file from disk and check that the preamble was reused.
191295c19e9SNikolai Kosjar   ResetVFS();
192295c19e9SNikolai Kosjar   AddFile(MainName, MainFileContent);
193295c19e9SNikolai Kosjar   ASSERT_TRUE(ReparseAST(AST));
194295c19e9SNikolai Kosjar   ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
195295c19e9SNikolai Kosjar }
196295c19e9SNikolai Kosjar 
197b5b48db1SCameron Desrochers TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) {
198b5b48db1SCameron Desrochers   std::string Header1 = "//./header1.h";
199b5b48db1SCameron Desrochers   std::string Header2 = "//./header2.h";
200b5b48db1SCameron Desrochers   std::string MainName = "//./main.cpp";
201b5b48db1SCameron Desrochers   AddFile(Header1, "");
202b5b48db1SCameron Desrochers   AddFile(Header2, "#pragma once");
203b5b48db1SCameron Desrochers   AddFile(MainName,
204b5b48db1SCameron Desrochers     "#include \"//./foo/../header1.h\"\n"
205b5b48db1SCameron Desrochers     "#include \"//./foo/../header2.h\"\n"
206b5b48db1SCameron Desrochers     "int main() { return ZERO; }");
207b5b48db1SCameron Desrochers   RemapFile(Header1, "static const int ZERO = 0;\n");
208b5b48db1SCameron Desrochers 
209b5b48db1SCameron Desrochers   std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
210b5b48db1SCameron Desrochers   ASSERT_TRUE(AST.get());
211b5b48db1SCameron Desrochers   ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
212b5b48db1SCameron Desrochers 
213b5b48db1SCameron Desrochers   unsigned initialCounts[] = {
214b5b48db1SCameron Desrochers     GetFileReadCount(MainName),
215b5b48db1SCameron Desrochers     GetFileReadCount(Header1),
216b5b48db1SCameron Desrochers     GetFileReadCount(Header2)
217b5b48db1SCameron Desrochers   };
218b5b48db1SCameron Desrochers 
219b5b48db1SCameron Desrochers   ASSERT_TRUE(ReparseAST(AST));
220b5b48db1SCameron Desrochers 
221b5b48db1SCameron Desrochers   ASSERT_NE(initialCounts[0], GetFileReadCount(MainName));
222b5b48db1SCameron Desrochers   ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1));
223b5b48db1SCameron Desrochers   ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2));
224b5b48db1SCameron Desrochers }
225b5b48db1SCameron Desrochers 
22684fd064eSCameron Desrochers TEST_F(PCHPreambleTest, ParseWithBom) {
22784fd064eSCameron Desrochers   std::string Header = "//./header.h";
22884fd064eSCameron Desrochers   std::string Main = "//./main.cpp";
22984fd064eSCameron Desrochers   AddFile(Header, "int random() { return 4; }");
23084fd064eSCameron Desrochers   AddFile(Main,
23184fd064eSCameron Desrochers     "\xef\xbb\xbf"
23284fd064eSCameron Desrochers     "#include \"//./header.h\"\n"
23384fd064eSCameron Desrochers     "int main() { return random() -2; }");
23484fd064eSCameron Desrochers 
23584fd064eSCameron Desrochers   std::unique_ptr<ASTUnit> AST(ParseAST(Main));
23684fd064eSCameron Desrochers   ASSERT_TRUE(AST.get());
23784fd064eSCameron Desrochers   ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
23884fd064eSCameron Desrochers 
23984fd064eSCameron Desrochers   unsigned HeaderReadCount = GetFileReadCount(Header);
24084fd064eSCameron Desrochers 
24184fd064eSCameron Desrochers   ASSERT_TRUE(ReparseAST(AST));
24284fd064eSCameron Desrochers   ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
24384fd064eSCameron Desrochers 
24484fd064eSCameron Desrochers   // Check preamble PCH was really reused
24584fd064eSCameron Desrochers   ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header));
24684fd064eSCameron Desrochers 
24784fd064eSCameron Desrochers   // Remove BOM
24884fd064eSCameron Desrochers   RemapFile(Main,
24984fd064eSCameron Desrochers     "#include \"//./header.h\"\n"
25084fd064eSCameron Desrochers     "int main() { return random() -2; }");
25184fd064eSCameron Desrochers 
25284fd064eSCameron Desrochers   ASSERT_TRUE(ReparseAST(AST));
25384fd064eSCameron Desrochers   ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
25484fd064eSCameron Desrochers 
25584fd064eSCameron Desrochers   ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
25684fd064eSCameron Desrochers   HeaderReadCount = GetFileReadCount(Header);
25784fd064eSCameron Desrochers 
25884fd064eSCameron Desrochers   // Add BOM back
25984fd064eSCameron Desrochers   RemapFile(Main,
26084fd064eSCameron Desrochers     "\xef\xbb\xbf"
26184fd064eSCameron Desrochers     "#include \"//./header.h\"\n"
26284fd064eSCameron Desrochers     "int main() { return random() -2; }");
26384fd064eSCameron Desrochers 
26484fd064eSCameron Desrochers   ASSERT_TRUE(ReparseAST(AST));
26584fd064eSCameron Desrochers   ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
26684fd064eSCameron Desrochers 
26784fd064eSCameron Desrochers   ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
26884fd064eSCameron Desrochers }
26984fd064eSCameron Desrochers 
270b5b48db1SCameron Desrochers } // anonymous namespace
271