[SCCP] Avoid modifying AdditionalUsers while iterating over it
[llvm-project.git] / clang / unittests / Frontend / PCHPreambleTest.cpp
blobd253d937ace97f31e828286c091c24d3afc0c525
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 //===----------------------------------------------------------------------===//
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"
22 using namespace llvm;
23 using namespace clang;
25 namespace {
27 class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem
29 std::map<std::string, unsigned> ReadCounts;
31 public:
32 ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override
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);
41 unsigned GetReadCount(const Twine &Path) const
43 auto it = ReadCounts.find(Path.str());
44 return it == ReadCounts.end() ? 0 : it->second;
48 class PCHPreambleTest : public ::testing::Test {
49 IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS;
50 StringMap<std::string> RemappedFiles;
51 std::shared_ptr<PCHContainerOperations> PCHContainerOpts;
52 FileSystemOptions FSOpts;
54 public:
55 void SetUp() override { ResetVFS(); }
56 void TearDown() override {}
58 void ResetVFS() {
59 VFS = new ReadCountingInMemoryFileSystem();
60 // We need the working directory to be set to something absolute,
61 // otherwise it ends up being inadvertently set to the current
62 // working directory in the real file system due to a series of
63 // unfortunate conditions interacting badly.
64 // What's more, this path *must* be absolute on all (real)
65 // filesystems, so just '/' won't work (e.g. on Win32).
66 VFS->setCurrentWorkingDirectory("//./");
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));
75 void RemapFile(const std::string &Filename, const std::string &Contents) {
76 RemappedFiles[Filename] = Contents;
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))));
86 CI->getTargetOpts().Triple = "i386-unknown-linux-gnu";
88 CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles();
90 PreprocessorOptions &PPOpts = CI->getPreprocessorOpts();
91 PPOpts.RemappedFilesKeepOriginalName = true;
93 IntrusiveRefCntPtr<DiagnosticsEngine>
94 Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions, new DiagnosticConsumer));
96 FileManager *FileMgr = new FileManager(FSOpts, VFS);
98 std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
99 CI, PCHContainerOpts, Diags, FileMgr, false, CaptureDiagsKind::None,
100 /*PrecompilePreambleAfterNParses=*/1);
101 return AST;
104 bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) {
105 bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS);
106 return !reparseFailed;
109 unsigned GetFileReadCount(const std::string &Filename) const {
110 return VFS->GetReadCount(Filename);
113 private:
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(std::string(RemappedFile.first()), buf.release());
122 return Remapped;
126 TEST_F(PCHPreambleTest, ReparseReusesPreambleWithUnsavedFileNotExistingOnDisk) {
127 std::string Header1 = "//./header1.h";
128 std::string MainName = "//./main.cpp";
129 AddFile(MainName, R"cpp(
130 #include "//./header1.h"
131 int main() { return ZERO; }
132 )cpp");
133 RemapFile(Header1, "#define ZERO 0\n");
135 // Parse with header file provided as unsaved file, which does not exist on
136 // disk.
137 std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
138 ASSERT_TRUE(AST.get());
139 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
141 // Reparse and check that the preamble was reused.
142 ASSERT_TRUE(ReparseAST(AST));
143 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
146 TEST_F(PCHPreambleTest, ReparseReusesPreambleAfterUnsavedFileWasCreatedOnDisk) {
147 std::string Header1 = "//./header1.h";
148 std::string MainName = "//./main.cpp";
149 AddFile(MainName, R"cpp(
150 #include "//./header1.h"
151 int main() { return ZERO; }
152 )cpp");
153 RemapFile(Header1, "#define ZERO 0\n");
155 // Parse with header file provided as unsaved file, which does not exist on
156 // disk.
157 std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
158 ASSERT_TRUE(AST.get());
159 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
161 // Create the unsaved file also on disk and check that preamble was reused.
162 AddFile(Header1, "#define ZERO 0\n");
163 ASSERT_TRUE(ReparseAST(AST));
164 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
167 TEST_F(PCHPreambleTest,
168 ReparseReusesPreambleAfterUnsavedFileWasRemovedFromDisk) {
169 std::string Header1 = "//./foo/header1.h";
170 std::string MainName = "//./main.cpp";
171 std::string MainFileContent = R"cpp(
172 #include "//./foo/header1.h"
173 int main() { return ZERO; }
174 )cpp";
175 AddFile(MainName, MainFileContent);
176 AddFile(Header1, "#define ZERO 0\n");
177 RemapFile(Header1, "#define ZERO 0\n");
179 // Parse with header file provided as unsaved file, which exists on disk.
180 std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
181 ASSERT_TRUE(AST.get());
182 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
183 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
185 // Remove the unsaved file from disk and check that the preamble was reused.
186 ResetVFS();
187 AddFile(MainName, MainFileContent);
188 ASSERT_TRUE(ReparseAST(AST));
189 ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
192 TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) {
193 std::string Header1 = "//./header1.h";
194 std::string Header2 = "//./header2.h";
195 std::string MainName = "//./main.cpp";
196 AddFile(Header1, "");
197 AddFile(Header2, "#pragma once");
198 AddFile(MainName,
199 "#include \"//./foo/../header1.h\"\n"
200 "#include \"//./foo/../header2.h\"\n"
201 "int main() { return ZERO; }");
202 RemapFile(Header1, "static const int ZERO = 0;\n");
204 std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
205 ASSERT_TRUE(AST.get());
206 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
208 unsigned initialCounts[] = {
209 GetFileReadCount(MainName),
210 GetFileReadCount(Header1),
211 GetFileReadCount(Header2)
214 ASSERT_TRUE(ReparseAST(AST));
216 ASSERT_NE(initialCounts[0], GetFileReadCount(MainName));
217 ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1));
218 ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2));
221 TEST_F(PCHPreambleTest, ParseWithBom) {
222 std::string Header = "//./header.h";
223 std::string Main = "//./main.cpp";
224 AddFile(Header, "int random() { return 4; }");
225 AddFile(Main,
226 "\xef\xbb\xbf"
227 "#include \"//./header.h\"\n"
228 "int main() { return random() -2; }");
230 std::unique_ptr<ASTUnit> AST(ParseAST(Main));
231 ASSERT_TRUE(AST.get());
232 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
234 unsigned HeaderReadCount = GetFileReadCount(Header);
236 ASSERT_TRUE(ReparseAST(AST));
237 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
239 // Check preamble PCH was really reused
240 ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header));
242 // Remove BOM
243 RemapFile(Main,
244 "#include \"//./header.h\"\n"
245 "int main() { return random() -2; }");
247 ASSERT_TRUE(ReparseAST(AST));
248 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
250 ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
251 HeaderReadCount = GetFileReadCount(Header);
253 // Add BOM back
254 RemapFile(Main,
255 "\xef\xbb\xbf"
256 "#include \"//./header.h\"\n"
257 "int main() { return random() -2; }");
259 ASSERT_TRUE(ReparseAST(AST));
260 ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
262 ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
265 } // anonymous namespace