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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang/unittests/Frontend/PCHPreambleTest.cpp