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 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());
34 class ReadCountingInMemoryFileSystem
: public vfs::InMemoryFileSystem
36 std::map
<std::string
, unsigned> ReadCounts
;
39 ErrorOr
<std::unique_ptr
<vfs::File
>> openFileForRead(const Twine
&Path
) override
41 ++ReadCounts
[Canonicalize(Path
)];
42 return InMemoryFileSystem::openFileForRead(Path
);
45 unsigned GetReadCount(const Twine
&Path
) const
47 auto it
= ReadCounts
.find(Canonicalize(Path
));
48 return it
== ReadCounts
.end() ? 0 : it
->second
;
52 class PCHPreambleTest
: public ::testing::Test
{
53 IntrusiveRefCntPtr
<ReadCountingInMemoryFileSystem
> VFS
;
54 StringMap
<std::string
> RemappedFiles
;
55 std::shared_ptr
<PCHContainerOperations
> PCHContainerOpts
;
56 FileSystemOptions FSOpts
;
59 void SetUp() override
{ ResetVFS(); }
60 void TearDown() override
{}
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("//./");
73 void AddFile(const std::string
&Filename
, const std::string
&Contents
) {
76 VFS
->addFile(Filename
, now
, MemoryBuffer::getMemBufferCopy(Contents
, Filename
));
79 void RemapFile(const std::string
&Filename
, const std::string
&Contents
) {
80 RemappedFiles
[Filename
] = Contents
;
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))));
90 CI
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
92 CI
->getPreprocessorOpts().RemappedFileBuffers
= GetRemappedFiles();
94 PreprocessorOptions
&PPOpts
= CI
->getPreprocessorOpts();
95 PPOpts
.RemappedFilesKeepOriginalName
= true;
97 IntrusiveRefCntPtr
<DiagnosticsEngine
>
98 Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions
, new DiagnosticConsumer
));
100 FileManager
*FileMgr
= new FileManager(FSOpts
, VFS
);
102 std::unique_ptr
<ASTUnit
> AST
= ASTUnit::LoadFromCompilerInvocation(
103 CI
, PCHContainerOpts
, Diags
, FileMgr
, false, CaptureDiagsKind::None
,
104 /*PrecompilePreambleAfterNParses=*/1);
108 bool ReparseAST(const std::unique_ptr
<ASTUnit
> &AST
) {
109 bool reparseFailed
= AST
->Reparse(PCHContainerOpts
, GetRemappedFiles(), VFS
);
110 return !reparseFailed
;
113 unsigned GetFileReadCount(const std::string
&Filename
) const {
114 return VFS
->GetReadCount(Filename
);
118 std::vector
<std::pair
<std::string
, llvm::MemoryBuffer
*>>
119 GetRemappedFiles() const {
120 std::vector
<std::pair
<std::string
, llvm::MemoryBuffer
*>> Remapped
;
121 for (const auto &RemappedFile
: RemappedFiles
) {
122 std::unique_ptr
<MemoryBuffer
> buf
= MemoryBuffer::getMemBufferCopy(
123 RemappedFile
.second
, RemappedFile
.first());
124 Remapped
.emplace_back(std::string(RemappedFile
.first()), buf
.release());
130 TEST_F(PCHPreambleTest
, ReparseReusesPreambleWithUnsavedFileNotExistingOnDisk
) {
131 std::string Header1
= "//./header1.h";
132 std::string MainName
= "//./main.cpp";
133 AddFile(MainName
, R
"cpp(
134 #include "//./header1.h"
135 int main() { return ZERO
; }
137 RemapFile(Header1, "#define ZERO 0\n");
139 // Parse with header file provided as unsaved file, which does not exist on
141 std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
142 ASSERT_TRUE(AST.get());
143 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
145 // Reparse and check that the preamble was reused.
146 ASSERT_TRUE(ReparseAST(AST));
147 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
150 TEST_F(PCHPreambleTest, ReparseReusesPreambleAfterUnsavedFileWasCreatedOnDisk) {
151 std::string Header1 = "//./header1.h";
152 std::string MainName
= "//./main.cpp";
153 AddFile(MainName
, R
"cpp(
154 #include "//./header1.h"
155 int main() { return ZERO
; }
157 RemapFile(Header1, "#define ZERO 0\n");
159 // Parse with header file provided as unsaved file, which does not exist on
161 std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
162 ASSERT_TRUE(AST.get());
163 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
165 // Create the unsaved file also on disk and check that preamble was reused.
166 AddFile(Header1, "#define ZERO 0\n");
167 ASSERT_TRUE(ReparseAST(AST));
168 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
171 TEST_F(PCHPreambleTest,
172 ReparseReusesPreambleAfterUnsavedFileWasRemovedFromDisk) {
173 std::string Header1 = "//./foo/header1.h";
174 std::string MainName
= "//./main.cpp";
175 std::string MainFileContent
= R
"cpp(
176 #include "//./foo/header1.h"
177 int main() { return ZERO
; }
179 AddFile(MainName, MainFileContent);
180 AddFile(Header1, "#define ZERO 0\n");
181 RemapFile(Header1, "#define ZERO 0\n");
183 // Parse with header file provided as unsaved file, which exists on disk.
184 std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
185 ASSERT_TRUE(AST.get());
186 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
187 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
189 // Remove the unsaved file from disk and check that the preamble was reused.
191 AddFile(MainName, MainFileContent);
192 ASSERT_TRUE(ReparseAST(AST));
193 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
196 TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) {
197 std::string Header1 = "//./header1.h";
198 std::string Header2
= "//./header2.h";
199 std::string MainName
= "//./main.cpp";
200 AddFile(Header1
, "");
201 AddFile(Header2
, "#pragma once");
203 "#include \"//./foo/../header1.h\"\n"
204 "#include \"//./foo/../header2.h\"\n"
205 "int main() { return ZERO; }");
206 RemapFile(Header1
, "static const int ZERO = 0;\n");
208 std::unique_ptr
<ASTUnit
> AST(ParseAST(MainName
));
209 ASSERT_TRUE(AST
.get());
210 ASSERT_FALSE(AST
->getDiagnostics().hasErrorOccurred());
212 unsigned initialCounts
[] = {
213 GetFileReadCount(MainName
),
214 GetFileReadCount(Header1
),
215 GetFileReadCount(Header2
)
218 ASSERT_TRUE(ReparseAST(AST
));
220 ASSERT_NE(initialCounts
[0], GetFileReadCount(MainName
));
221 ASSERT_EQ(initialCounts
[1], GetFileReadCount(Header1
));
222 ASSERT_EQ(initialCounts
[2], GetFileReadCount(Header2
));
225 TEST_F(PCHPreambleTest
, ParseWithBom
) {
226 std::string Header
= "//./header.h";
227 std::string Main
= "//./main.cpp";
228 AddFile(Header
, "int random() { return 4; }");
231 "#include \"//./header.h\"\n"
232 "int main() { return random() -2; }");
234 std::unique_ptr
<ASTUnit
> AST(ParseAST(Main
));
235 ASSERT_TRUE(AST
.get());
236 ASSERT_FALSE(AST
->getDiagnostics().hasErrorOccurred());
238 unsigned HeaderReadCount
= GetFileReadCount(Header
);
240 ASSERT_TRUE(ReparseAST(AST
));
241 ASSERT_FALSE(AST
->getDiagnostics().hasErrorOccurred());
243 // Check preamble PCH was really reused
244 ASSERT_EQ(HeaderReadCount
, GetFileReadCount(Header
));
248 "#include \"//./header.h\"\n"
249 "int main() { return random() -2; }");
251 ASSERT_TRUE(ReparseAST(AST
));
252 ASSERT_FALSE(AST
->getDiagnostics().hasErrorOccurred());
254 ASSERT_LE(HeaderReadCount
, GetFileReadCount(Header
));
255 HeaderReadCount
= GetFileReadCount(Header
);
260 "#include \"//./header.h\"\n"
261 "int main() { return random() -2; }");
263 ASSERT_TRUE(ReparseAST(AST
));
264 ASSERT_FALSE(AST
->getDiagnostics().hasErrorOccurred());
266 ASSERT_LE(HeaderReadCount
, GetFileReadCount(Header
));
269 } // anonymous namespace