Clang Project

clang_source_code/unittests/Frontend/PCHPreambleTest.cpp
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
27class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem
28{
29  std::map<std::stringunsignedReadCounts;
30
31public:
32  ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override
33  {
34    SmallVector<char128PathVec;
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 &Pathconst
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<ReadCountingInMemoryFileSystemVFS;
50  StringMap<std::string> RemappedFiles;
51  std::shared_ptr<PCHContainerOperationsPCHContainerOpts;
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 &Filenameconst 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 &Filenameconst std::string &Contents) {
76    RemappedFiles[Filename] = Contents;
77  }
78
79  std::unique_ptr<ASTUnitParseAST(const std::string &EntryFile) {
80    PCHContainerOpts = std::make_shared<PCHContainerOperations>();
81    std::shared_ptr<CompilerInvocationCI(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<ASTUnitAST = ASTUnit::LoadFromCompilerInvocation(
99      CI, PCHContainerOpts, Diags, FileMgr, falsefalse,
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 &Filenameconst {
110    return VFS->GetReadCount(Filename);
111  }
112
113private:
114  std::vector<std::pair<std::stringllvm::MemoryBuffer *>>
115  GetRemappedFiles() const {
116    std::vector<std::pair<std::stringllvm::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<ASTUnitAST(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
155TEST_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<ASTUnitAST(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
199// anonymous namespace
200