1 //====-- unittests/Frontend/PCHPreambleTest.cpp - FrontendAction tests ---====//
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
7 //===----------------------------------------------------------------------===//
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"
23 using namespace clang
;
27 class ReadCountingInMemoryFileSystem
: public vfs::InMemoryFileSystem
29 std::map
<std::string
, unsigned> ReadCounts
;
32 ErrorOr
<std::unique_ptr
<vfs::File
>> openFileForRead(const Twine
&Path
) override
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
);
41 unsigned GetReadCount(const Twine
&Path
) const
43 auto it
= ReadCounts
.find(Path
.str());
44 return it
== ReadCounts
.end() ? 0 : it
->second
;
48 class PCHPreambleTest
: public ::testing::Test
{
49 IntrusiveRefCntPtr
<ReadCountingInMemoryFileSystem
> VFS
;
50 StringMap
<std::string
> RemappedFiles
;
51 std::shared_ptr
<PCHContainerOperations
> PCHContainerOpts
;
52 FileSystemOptions FSOpts
;
55 void SetUp() override
{ ResetVFS(); }
56 void TearDown() override
{}
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("//./");
69 void AddFile(const std::string
&Filename
, const std::string
&Contents
) {
72 VFS
->addFile(Filename
, now
, MemoryBuffer::getMemBufferCopy(Contents
, Filename
));
75 void RemapFile(const std::string
&Filename
, const std::string
&Contents
) {
76 RemappedFiles
[Filename
] = Contents
;
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))));
86 CI
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
88 CI
->getPreprocessorOpts().RemappedFileBuffers
= GetRemappedFiles();
90 PreprocessorOptions
&PPOpts
= CI
->getPreprocessorOpts();
91 PPOpts
.RemappedFilesKeepOriginalName
= true;
93 IntrusiveRefCntPtr
<DiagnosticsEngine
>
94 Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions
, new DiagnosticConsumer
));
96 FileManager
*FileMgr
= new FileManager(FSOpts
, VFS
);
98 std::unique_ptr
<ASTUnit
> AST
= ASTUnit::LoadFromCompilerInvocation(
99 CI
, PCHContainerOpts
, Diags
, FileMgr
, false, CaptureDiagsKind::None
,
100 /*PrecompilePreambleAfterNParses=*/1);
104 bool ReparseAST(const std::unique_ptr
<ASTUnit
> &AST
) {
105 bool reparseFailed
= AST
->Reparse(PCHContainerOpts
, GetRemappedFiles(), VFS
);
106 return !reparseFailed
;
109 unsigned GetFileReadCount(const std::string
&Filename
) const {
110 return VFS
->GetReadCount(Filename
);
114 std::vector
<std::pair
<std::string
, llvm::MemoryBuffer
*>>
115 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());
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
; }
133 RemapFile(Header1, "#define ZERO 0\n");
135 // Parse with header file provided as unsaved file, which does not exist on
137 std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
138 ASSERT_TRUE(AST.get());
139 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
141 // Reparse and check that the preamble was reused.
142 ASSERT_TRUE(ReparseAST(AST));
143 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
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
; }
153 RemapFile(Header1, "#define ZERO 0\n");
155 // Parse with header file provided as unsaved file, which does not exist on
157 std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
158 ASSERT_TRUE(AST.get());
159 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
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);
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
; }
175 AddFile(MainName, MainFileContent);
176 AddFile(Header1, "#define ZERO 0\n");
177 RemapFile(Header1, "#define ZERO 0\n");
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);
185 // Remove the unsaved file from disk and check that the preamble was reused.
187 AddFile(MainName, MainFileContent);
188 ASSERT_TRUE(ReparseAST(AST));
189 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
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");
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");
204 std::unique_ptr
<ASTUnit
> AST(ParseAST(MainName
));
205 ASSERT_TRUE(AST
.get());
206 ASSERT_FALSE(AST
->getDiagnostics().hasErrorOccurred());
208 unsigned initialCounts
[] = {
209 GetFileReadCount(MainName
),
210 GetFileReadCount(Header1
),
211 GetFileReadCount(Header2
)
214 ASSERT_TRUE(ReparseAST(AST
));
216 ASSERT_NE(initialCounts
[0], GetFileReadCount(MainName
));
217 ASSERT_EQ(initialCounts
[1], GetFileReadCount(Header1
));
218 ASSERT_EQ(initialCounts
[2], GetFileReadCount(Header2
));
221 TEST_F(PCHPreambleTest
, ParseWithBom
) {
222 std::string Header
= "//./header.h";
223 std::string Main
= "//./main.cpp";
224 AddFile(Header
, "int random() { return 4; }");
227 "#include \"//./header.h\"\n"
228 "int main() { return random() -2; }");
230 std::unique_ptr
<ASTUnit
> AST(ParseAST(Main
));
231 ASSERT_TRUE(AST
.get());
232 ASSERT_FALSE(AST
->getDiagnostics().hasErrorOccurred());
234 unsigned HeaderReadCount
= GetFileReadCount(Header
);
236 ASSERT_TRUE(ReparseAST(AST
));
237 ASSERT_FALSE(AST
->getDiagnostics().hasErrorOccurred());
239 // Check preamble PCH was really reused
240 ASSERT_EQ(HeaderReadCount
, GetFileReadCount(Header
));
244 "#include \"//./header.h\"\n"
245 "int main() { return random() -2; }");
247 ASSERT_TRUE(ReparseAST(AST
));
248 ASSERT_FALSE(AST
->getDiagnostics().hasErrorOccurred());
250 ASSERT_LE(HeaderReadCount
, GetFileReadCount(Header
));
251 HeaderReadCount
= GetFileReadCount(Header
);
256 "#include \"//./header.h\"\n"
257 "int main() { return random() -2; }");
259 ASSERT_TRUE(ReparseAST(AST
));
260 ASSERT_FALSE(AST
->getDiagnostics().hasErrorOccurred());
262 ASSERT_LE(HeaderReadCount
, GetFileReadCount(Header
));
265 } // anonymous namespace