blob: 162a281b04d49ece34976cf4fa0d84579e59ed07 [file] [log] [blame]
Cameron Desrochersb5b48db2017-09-11 15:03:23 +00001//====-- unittests/Frontend/PCHPreambleTest.cpp - FrontendAction tests ---====//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "clang/Frontend/ASTUnit.h"
11#include "clang/Frontend/CompilerInvocation.h"
12#include "clang/Frontend/CompilerInstance.h"
13#include "clang/Frontend/FrontendActions.h"
14#include "clang/Frontend/FrontendOptions.h"
15#include "clang/Lex/PreprocessorOptions.h"
16#include "clang/Basic/Diagnostic.h"
17#include "clang/Basic/FileManager.h"
18#include "llvm/Support/FileSystem.h"
19#include "llvm/Support/MemoryBuffer.h"
20#include "llvm/Support/Path.h"
21#include "gtest/gtest.h"
22
23using namespace llvm;
24using namespace clang;
25
26namespace {
27
28class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem
29{
30 std::map<std::string, unsigned> ReadCounts;
31
32public:
33 ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override
34 {
35 SmallVector<char, 128> PathVec;
36 Path.toVector(PathVec);
37 llvm::sys::path::remove_dots(PathVec, true);
38 ++ReadCounts[std::string(PathVec.begin(), PathVec.end())];
39 return InMemoryFileSystem::openFileForRead(Path);
40 }
41
42 unsigned GetReadCount(const Twine &Path) const
43 {
44 auto it = ReadCounts.find(Path.str());
45 return it == ReadCounts.end() ? 0 : it->second;
46 }
47};
48
49class PCHPreambleTest : public ::testing::Test {
50 IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS;
51 StringMap<std::string> RemappedFiles;
52 std::shared_ptr<PCHContainerOperations> PCHContainerOpts;
53 FileSystemOptions FSOpts;
54
55public:
56 void SetUp() override {
57 VFS = new ReadCountingInMemoryFileSystem();
58 // We need the working directory to be set to something absolute,
59 // otherwise it ends up being inadvertently set to the current
60 // working directory in the real file system due to a series of
61 // unfortunate conditions interacting badly.
62 // What's more, this path *must* be absolute on all (real)
63 // filesystems, so just '/' won't work (e.g. on Win32).
64 VFS->setCurrentWorkingDirectory("//./");
65 }
66
67 void TearDown() override {
68 }
69
70 void AddFile(const std::string &Filename, const std::string &Contents) {
71 ::time_t now;
72 ::time(&now);
73 VFS->addFile(Filename, now, MemoryBuffer::getMemBufferCopy(Contents, Filename));
74 }
75
76 void RemapFile(const std::string &Filename, const std::string &Contents) {
77 RemappedFiles[Filename] = Contents;
78 }
79
80 std::unique_ptr<ASTUnit> ParseAST(const std::string &EntryFile) {
81 PCHContainerOpts = std::make_shared<PCHContainerOperations>();
82 std::shared_ptr<CompilerInvocation> CI(new CompilerInvocation);
83 CI->getFrontendOpts().Inputs.push_back(
84 FrontendInputFile(EntryFile, FrontendOptions::getInputKindForExtension(
85 llvm::sys::path::extension(EntryFile).substr(1))));
86
87 CI->getTargetOpts().Triple = "i386-unknown-linux-gnu";
88
89 CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles();
90
91 PreprocessorOptions &PPOpts = CI->getPreprocessorOpts();
92 PPOpts.RemappedFilesKeepOriginalName = true;
93
94 IntrusiveRefCntPtr<DiagnosticsEngine>
95 Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions, new DiagnosticConsumer));
96
97 FileManager *FileMgr = new FileManager(FSOpts, VFS);
98
99 std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
100 CI, PCHContainerOpts, Diags, FileMgr, false, false,
101 /*PrecompilePreambleAfterNParses=*/1);
102 return AST;
103 }
104
105 bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) {
106 bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS);
107 return !reparseFailed;
108 }
109
110 unsigned GetFileReadCount(const std::string &Filename) const {
111 return VFS->GetReadCount(Filename);
112 }
113
114private:
115 std::vector<std::pair<std::string, llvm::MemoryBuffer *>>
116 GetRemappedFiles() const {
117 std::vector<std::pair<std::string, llvm::MemoryBuffer *>> Remapped;
118 for (const auto &RemappedFile : RemappedFiles) {
119 std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy(
120 RemappedFile.second, RemappedFile.first());
121 Remapped.emplace_back(RemappedFile.first(), buf.release());
122 }
123 return Remapped;
124 }
125};
126
127TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) {
128 std::string Header1 = "//./header1.h";
129 std::string Header2 = "//./header2.h";
130 std::string MainName = "//./main.cpp";
131 AddFile(Header1, "");
132 AddFile(Header2, "#pragma once");
133 AddFile(MainName,
134 "#include \"//./foo/../header1.h\"\n"
135 "#include \"//./foo/../header2.h\"\n"
136 "int main() { return ZERO; }");
137 RemapFile(Header1, "static const int ZERO = 0;\n");
138
139 std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
140 ASSERT_TRUE(AST.get());
141 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
142
143 unsigned initialCounts[] = {
144 GetFileReadCount(MainName),
145 GetFileReadCount(Header1),
146 GetFileReadCount(Header2)
147 };
148
149 ASSERT_TRUE(ReparseAST(AST));
150
151 ASSERT_NE(initialCounts[0], GetFileReadCount(MainName));
152 ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1));
153 ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2));
154}
155
Cameron Desrochers84fd0642017-09-20 19:03:37 +0000156TEST_F(PCHPreambleTest, ParseWithBom) {
157 std::string Header = "//./header.h";
158 std::string Main = "//./main.cpp";
159 AddFile(Header, "int random() { return 4; }");
160 AddFile(Main,
161 "\xef\xbb\xbf"
162 "#include \"//./header.h\"\n"
163 "int main() { return random() -2; }");
164
165 std::unique_ptr<ASTUnit> AST(ParseAST(Main));
166 ASSERT_TRUE(AST.get());
167 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
168
169 unsigned HeaderReadCount = GetFileReadCount(Header);
170
171 ASSERT_TRUE(ReparseAST(AST));
172 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
173
174 // Check preamble PCH was really reused
175 ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header));
176
177 // Remove BOM
178 RemapFile(Main,
179 "#include \"//./header.h\"\n"
180 "int main() { return random() -2; }");
181
182 ASSERT_TRUE(ReparseAST(AST));
183 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
184
185 ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
186 HeaderReadCount = GetFileReadCount(Header);
187
188 // Add BOM back
189 RemapFile(Main,
190 "\xef\xbb\xbf"
191 "#include \"//./header.h\"\n"
192 "int main() { return random() -2; }");
193
194 ASSERT_TRUE(ReparseAST(AST));
195 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
196
197 ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
198}
199
Cameron Desrochersb5b48db2017-09-11 15:03:23 +0000200} // anonymous namespace