llvm-project
269 строк · 8.8 Кб
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
22using namespace llvm;
23using namespace clang;
24
25namespace {
26
27std::string Canonicalize(const Twine &Path) {
28SmallVector<char, 128> PathVec;
29Path.toVector(PathVec);
30llvm::sys::path::remove_dots(PathVec, true);
31return std::string(PathVec.begin(), PathVec.end());
32}
33
34class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem
35{
36std::map<std::string, unsigned> ReadCounts;
37
38public:
39ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override
40{
41++ReadCounts[Canonicalize(Path)];
42return InMemoryFileSystem::openFileForRead(Path);
43}
44
45unsigned GetReadCount(const Twine &Path) const
46{
47auto it = ReadCounts.find(Canonicalize(Path));
48return it == ReadCounts.end() ? 0 : it->second;
49}
50};
51
52class PCHPreambleTest : public ::testing::Test {
53IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS;
54StringMap<std::string> RemappedFiles;
55std::shared_ptr<PCHContainerOperations> PCHContainerOpts;
56FileSystemOptions FSOpts;
57
58public:
59void SetUp() override { ResetVFS(); }
60void TearDown() override {}
61
62void ResetVFS() {
63VFS = 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).
70VFS->setCurrentWorkingDirectory("//./");
71}
72
73void AddFile(const std::string &Filename, const std::string &Contents) {
74::time_t now;
75::time(&now);
76VFS->addFile(Filename, now, MemoryBuffer::getMemBufferCopy(Contents, Filename));
77}
78
79void RemapFile(const std::string &Filename, const std::string &Contents) {
80RemappedFiles[Filename] = Contents;
81}
82
83std::unique_ptr<ASTUnit> ParseAST(const std::string &EntryFile) {
84PCHContainerOpts = std::make_shared<PCHContainerOperations>();
85std::shared_ptr<CompilerInvocation> CI(new CompilerInvocation);
86CI->getFrontendOpts().Inputs.push_back(
87FrontendInputFile(EntryFile, FrontendOptions::getInputKindForExtension(
88llvm::sys::path::extension(EntryFile).substr(1))));
89
90CI->getTargetOpts().Triple = "i386-unknown-linux-gnu";
91
92CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles();
93
94PreprocessorOptions &PPOpts = CI->getPreprocessorOpts();
95PPOpts.RemappedFilesKeepOriginalName = true;
96
97IntrusiveRefCntPtr<DiagnosticsEngine>
98Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions, new DiagnosticConsumer));
99
100FileManager *FileMgr = new FileManager(FSOpts, VFS);
101
102std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
103CI, PCHContainerOpts, Diags, FileMgr, false, CaptureDiagsKind::None,
104/*PrecompilePreambleAfterNParses=*/1);
105return AST;
106}
107
108bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) {
109bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS);
110return !reparseFailed;
111}
112
113unsigned GetFileReadCount(const std::string &Filename) const {
114return VFS->GetReadCount(Filename);
115}
116
117private:
118std::vector<std::pair<std::string, llvm::MemoryBuffer *>>
119GetRemappedFiles() const {
120std::vector<std::pair<std::string, llvm::MemoryBuffer *>> Remapped;
121for (const auto &RemappedFile : RemappedFiles) {
122std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy(
123RemappedFile.second, RemappedFile.first());
124Remapped.emplace_back(std::string(RemappedFile.first()), buf.release());
125}
126return Remapped;
127}
128};
129
130TEST_F(PCHPreambleTest, ReparseReusesPreambleWithUnsavedFileNotExistingOnDisk) {
131std::string Header1 = "//./header1.h";
132std::string MainName = "//./main.cpp";
133AddFile(MainName, R"cpp(
134#include "//./header1.h"
135int main() { return ZERO; }
136)cpp");
137RemapFile(Header1, "#define ZERO 0\n");
138
139// Parse with header file provided as unsaved file, which does not exist on
140// disk.
141std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
142ASSERT_TRUE(AST.get());
143ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
144
145// Reparse and check that the preamble was reused.
146ASSERT_TRUE(ReparseAST(AST));
147ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
148}
149
150TEST_F(PCHPreambleTest, ReparseReusesPreambleAfterUnsavedFileWasCreatedOnDisk) {
151std::string Header1 = "//./header1.h";
152std::string MainName = "//./main.cpp";
153AddFile(MainName, R"cpp(
154#include "//./header1.h"
155int main() { return ZERO; }
156)cpp");
157RemapFile(Header1, "#define ZERO 0\n");
158
159// Parse with header file provided as unsaved file, which does not exist on
160// disk.
161std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
162ASSERT_TRUE(AST.get());
163ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
164
165// Create the unsaved file also on disk and check that preamble was reused.
166AddFile(Header1, "#define ZERO 0\n");
167ASSERT_TRUE(ReparseAST(AST));
168ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
169}
170
171TEST_F(PCHPreambleTest,
172ReparseReusesPreambleAfterUnsavedFileWasRemovedFromDisk) {
173std::string Header1 = "//./foo/header1.h";
174std::string MainName = "//./main.cpp";
175std::string MainFileContent = R"cpp(
176#include "//./foo/header1.h"
177int main() { return ZERO; }
178)cpp";
179AddFile(MainName, MainFileContent);
180AddFile(Header1, "#define ZERO 0\n");
181RemapFile(Header1, "#define ZERO 0\n");
182
183// Parse with header file provided as unsaved file, which exists on disk.
184std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
185ASSERT_TRUE(AST.get());
186ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
187ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
188
189// Remove the unsaved file from disk and check that the preamble was reused.
190ResetVFS();
191AddFile(MainName, MainFileContent);
192ASSERT_TRUE(ReparseAST(AST));
193ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
194}
195
196TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) {
197std::string Header1 = "//./header1.h";
198std::string Header2 = "//./header2.h";
199std::string MainName = "//./main.cpp";
200AddFile(Header1, "");
201AddFile(Header2, "#pragma once");
202AddFile(MainName,
203"#include \"//./foo/../header1.h\"\n"
204"#include \"//./foo/../header2.h\"\n"
205"int main() { return ZERO; }");
206RemapFile(Header1, "static const int ZERO = 0;\n");
207
208std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
209ASSERT_TRUE(AST.get());
210ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
211
212unsigned initialCounts[] = {
213GetFileReadCount(MainName),
214GetFileReadCount(Header1),
215GetFileReadCount(Header2)
216};
217
218ASSERT_TRUE(ReparseAST(AST));
219
220ASSERT_NE(initialCounts[0], GetFileReadCount(MainName));
221ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1));
222ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2));
223}
224
225TEST_F(PCHPreambleTest, ParseWithBom) {
226std::string Header = "//./header.h";
227std::string Main = "//./main.cpp";
228AddFile(Header, "int random() { return 4; }");
229AddFile(Main,
230"\xef\xbb\xbf"
231"#include \"//./header.h\"\n"
232"int main() { return random() -2; }");
233
234std::unique_ptr<ASTUnit> AST(ParseAST(Main));
235ASSERT_TRUE(AST.get());
236ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
237
238unsigned HeaderReadCount = GetFileReadCount(Header);
239
240ASSERT_TRUE(ReparseAST(AST));
241ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
242
243// Check preamble PCH was really reused
244ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header));
245
246// Remove BOM
247RemapFile(Main,
248"#include \"//./header.h\"\n"
249"int main() { return random() -2; }");
250
251ASSERT_TRUE(ReparseAST(AST));
252ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
253
254ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
255HeaderReadCount = GetFileReadCount(Header);
256
257// Add BOM back
258RemapFile(Main,
259"\xef\xbb\xbf"
260"#include \"//./header.h\"\n"
261"int main() { return random() -2; }");
262
263ASSERT_TRUE(ReparseAST(AST));
264ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
265
266ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
267}
268
269} // anonymous namespace
270