blob: 70567405f10860813cc692ae425c5e95eb2456bb [file] [log] [blame]
Cameron Desrochersb5b48db2017-09-11 15:03:23 +00001//====-- unittests/Frontend/PCHPreambleTest.cpp - FrontendAction tests ---====//
2//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Cameron Desrochersb5b48db2017-09-11 15:03:23 +00006//
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
27class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem
28{
29 std::map<std::string, unsigned> ReadCounts;
30
31public:
32 ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override
33 {
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);
39 }
40
41 unsigned GetReadCount(const Twine &Path) const
42 {
43 auto it = ReadCounts.find(Path.str());
44 return it == ReadCounts.end() ? 0 : it->second;
45 }
46};
47
48class PCHPreambleTest : public ::testing::Test {
49 IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS;
50 StringMap<std::string> RemappedFiles;
51 std::shared_ptr<PCHContainerOperations> PCHContainerOpts;
52 FileSystemOptions FSOpts;
53
54public:
55 void SetUp() override {
56 VFS = new ReadCountingInMemoryFileSystem();
57 // We need the working directory to be set to something absolute,
58 // otherwise it ends up being inadvertently set to the current
59 // working directory in the real file system due to a series of
60 // unfortunate conditions interacting badly.
61 // What's more, this path *must* be absolute on all (real)
62 // filesystems, so just '/' won't work (e.g. on Win32).
63 VFS->setCurrentWorkingDirectory("//./");
64 }
65
66 void TearDown() override {
67 }
68
69 void AddFile(const std::string &Filename, const std::string &Contents) {
70 ::time_t now;
71 ::time(&now);
72 VFS->addFile(Filename, now, MemoryBuffer::getMemBufferCopy(Contents, Filename));
73 }
74
75 void RemapFile(const std::string &Filename, const std::string &Contents) {
76 RemappedFiles[Filename] = Contents;
77 }
78
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))));
85
86 CI->getTargetOpts().Triple = "i386-unknown-linux-gnu";
87
88 CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles();
89
90 PreprocessorOptions &PPOpts = CI->getPreprocessorOpts();
91 PPOpts.RemappedFilesKeepOriginalName = true;
92
93 IntrusiveRefCntPtr<DiagnosticsEngine>
94 Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions, new DiagnosticConsumer));
95
96 FileManager *FileMgr = new FileManager(FSOpts, VFS);
97
98 std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
99 CI, PCHContainerOpts, Diags, FileMgr, false, false,
100 /*PrecompilePreambleAfterNParses=*/1);
101 return AST;
102 }
103
104 bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) {
105 bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS);
106 return !reparseFailed;
107 }
108
109 unsigned GetFileReadCount(const std::string &Filename) const {
110 return VFS->GetReadCount(Filename);
111 }
112
113private:
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(RemappedFile.first(), buf.release());
121 }
122 return Remapped;
123 }
124};
125
126TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) {
127 std::string Header1 = "//./header1.h";
128 std::string Header2 = "//./header2.h";
129 std::string MainName = "//./main.cpp";
130 AddFile(Header1, "");
131 AddFile(Header2, "#pragma once");
132 AddFile(MainName,
133 "#include \"//./foo/../header1.h\"\n"
134 "#include \"//./foo/../header2.h\"\n"
135 "int main() { return ZERO; }");
136 RemapFile(Header1, "static const int ZERO = 0;\n");
137
138 std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
139 ASSERT_TRUE(AST.get());
140 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
141
142 unsigned initialCounts[] = {
143 GetFileReadCount(MainName),
144 GetFileReadCount(Header1),
145 GetFileReadCount(Header2)
146 };
147
148 ASSERT_TRUE(ReparseAST(AST));
149
150 ASSERT_NE(initialCounts[0], GetFileReadCount(MainName));
151 ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1));
152 ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2));
153}
154
Cameron Desrochers84fd0642017-09-20 19:03:37 +0000155TEST_F(PCHPreambleTest, ParseWithBom) {
156 std::string Header = "//./header.h";
157 std::string Main = "//./main.cpp";
158 AddFile(Header, "int random() { return 4; }");
159 AddFile(Main,
160 "\xef\xbb\xbf"
161 "#include \"//./header.h\"\n"
162 "int main() { return random() -2; }");
163
164 std::unique_ptr<ASTUnit> AST(ParseAST(Main));
165 ASSERT_TRUE(AST.get());
166 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
167
168 unsigned HeaderReadCount = GetFileReadCount(Header);
169
170 ASSERT_TRUE(ReparseAST(AST));
171 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
172
173 // Check preamble PCH was really reused
174 ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header));
175
176 // Remove BOM
177 RemapFile(Main,
178 "#include \"//./header.h\"\n"
179 "int main() { return random() -2; }");
180
181 ASSERT_TRUE(ReparseAST(AST));
182 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
183
184 ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
185 HeaderReadCount = GetFileReadCount(Header);
186
187 // Add BOM back
188 RemapFile(Main,
189 "\xef\xbb\xbf"
190 "#include \"//./header.h\"\n"
191 "int main() { return random() -2; }");
192
193 ASSERT_TRUE(ReparseAST(AST));
194 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
195
196 ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
197}
198
Cameron Desrochersb5b48db2017-09-11 15:03:23 +0000199} // anonymous namespace