266 lines
8.7 KiB
C++
266 lines
8.7 KiB
C++
|
//====-- unittests/Frontend/PCHPreambleTest.cpp - FrontendAction tests ---====//
|
||
|
//
|
||
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||
|
// See https://llvm.org/LICENSE.txt for license information.
|
||
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include "clang/Frontend/ASTUnit.h"
|
||
|
#include "clang/Frontend/CompilerInvocation.h"
|
||
|
#include "clang/Frontend/CompilerInstance.h"
|
||
|
#include "clang/Frontend/FrontendActions.h"
|
||
|
#include "clang/Frontend/FrontendOptions.h"
|
||
|
#include "clang/Lex/PreprocessorOptions.h"
|
||
|
#include "clang/Basic/Diagnostic.h"
|
||
|
#include "clang/Basic/FileManager.h"
|
||
|
#include "llvm/Support/FileSystem.h"
|
||
|
#include "llvm/Support/MemoryBuffer.h"
|
||
|
#include "llvm/Support/Path.h"
|
||
|
#include "gtest/gtest.h"
|
||
|
|
||
|
using namespace llvm;
|
||
|
using namespace clang;
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem
|
||
|
{
|
||
|
std::map<std::string, unsigned> ReadCounts;
|
||
|
|
||
|
public:
|
||
|
ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override
|
||
|
{
|
||
|
SmallVector<char, 128> PathVec;
|
||
|
Path.toVector(PathVec);
|
||
|
llvm::sys::path::remove_dots(PathVec, true);
|
||
|
++ReadCounts[std::string(PathVec.begin(), PathVec.end())];
|
||
|
return InMemoryFileSystem::openFileForRead(Path);
|
||
|
}
|
||
|
|
||
|
unsigned GetReadCount(const Twine &Path) const
|
||
|
{
|
||
|
auto it = ReadCounts.find(Path.str());
|
||
|
return it == ReadCounts.end() ? 0 : it->second;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
class PCHPreambleTest : public ::testing::Test {
|
||
|
IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS;
|
||
|
StringMap<std::string> RemappedFiles;
|
||
|
std::shared_ptr<PCHContainerOperations> PCHContainerOpts;
|
||
|
FileSystemOptions FSOpts;
|
||
|
|
||
|
public:
|
||
|
void SetUp() override { ResetVFS(); }
|
||
|
void TearDown() override {}
|
||
|
|
||
|
void ResetVFS() {
|
||
|
VFS = new ReadCountingInMemoryFileSystem();
|
||
|
// We need the working directory to be set to something absolute,
|
||
|
// otherwise it ends up being inadvertently set to the current
|
||
|
// working directory in the real file system due to a series of
|
||
|
// unfortunate conditions interacting badly.
|
||
|
// What's more, this path *must* be absolute on all (real)
|
||
|
// filesystems, so just '/' won't work (e.g. on Win32).
|
||
|
VFS->setCurrentWorkingDirectory("//./");
|
||
|
}
|
||
|
|
||
|
void AddFile(const std::string &Filename, const std::string &Contents) {
|
||
|
::time_t now;
|
||
|
::time(&now);
|
||
|
VFS->addFile(Filename, now, MemoryBuffer::getMemBufferCopy(Contents, Filename));
|
||
|
}
|
||
|
|
||
|
void RemapFile(const std::string &Filename, const std::string &Contents) {
|
||
|
RemappedFiles[Filename] = Contents;
|
||
|
}
|
||
|
|
||
|
std::unique_ptr<ASTUnit> ParseAST(const std::string &EntryFile) {
|
||
|
PCHContainerOpts = std::make_shared<PCHContainerOperations>();
|
||
|
std::shared_ptr<CompilerInvocation> CI(new CompilerInvocation);
|
||
|
CI->getFrontendOpts().Inputs.push_back(
|
||
|
FrontendInputFile(EntryFile, FrontendOptions::getInputKindForExtension(
|
||
|
llvm::sys::path::extension(EntryFile).substr(1))));
|
||
|
|
||
|
CI->getTargetOpts().Triple = "i386-unknown-linux-gnu";
|
||
|
|
||
|
CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles();
|
||
|
|
||
|
PreprocessorOptions &PPOpts = CI->getPreprocessorOpts();
|
||
|
PPOpts.RemappedFilesKeepOriginalName = true;
|
||
|
|
||
|
IntrusiveRefCntPtr<DiagnosticsEngine>
|
||
|
Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions, new DiagnosticConsumer));
|
||
|
|
||
|
FileManager *FileMgr = new FileManager(FSOpts, VFS);
|
||
|
|
||
|
std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
|
||
|
CI, PCHContainerOpts, Diags, FileMgr, false, CaptureDiagsKind::None,
|
||
|
/*PrecompilePreambleAfterNParses=*/1);
|
||
|
return AST;
|
||
|
}
|
||
|
|
||
|
bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) {
|
||
|
bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS);
|
||
|
return !reparseFailed;
|
||
|
}
|
||
|
|
||
|
unsigned GetFileReadCount(const std::string &Filename) const {
|
||
|
return VFS->GetReadCount(Filename);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
std::vector<std::pair<std::string, llvm::MemoryBuffer *>>
|
||
|
GetRemappedFiles() const {
|
||
|
std::vector<std::pair<std::string, llvm::MemoryBuffer *>> Remapped;
|
||
|
for (const auto &RemappedFile : RemappedFiles) {
|
||
|
std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy(
|
||
|
RemappedFile.second, RemappedFile.first());
|
||
|
Remapped.emplace_back(std::string(RemappedFile.first()), buf.release());
|
||
|
}
|
||
|
return Remapped;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
TEST_F(PCHPreambleTest, ReparseReusesPreambleWithUnsavedFileNotExistingOnDisk) {
|
||
|
std::string Header1 = "//./header1.h";
|
||
|
std::string MainName = "//./main.cpp";
|
||
|
AddFile(MainName, R"cpp(
|
||
|
#include "//./header1.h"
|
||
|
int main() { return ZERO; }
|
||
|
)cpp");
|
||
|
RemapFile(Header1, "#define ZERO 0\n");
|
||
|
|
||
|
// Parse with header file provided as unsaved file, which does not exist on
|
||
|
// disk.
|
||
|
std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
|
||
|
ASSERT_TRUE(AST.get());
|
||
|
ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
|
||
|
|
||
|
// Reparse and check that the preamble was reused.
|
||
|
ASSERT_TRUE(ReparseAST(AST));
|
||
|
ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
|
||
|
}
|
||
|
|
||
|
TEST_F(PCHPreambleTest, ReparseReusesPreambleAfterUnsavedFileWasCreatedOnDisk) {
|
||
|
std::string Header1 = "//./header1.h";
|
||
|
std::string MainName = "//./main.cpp";
|
||
|
AddFile(MainName, R"cpp(
|
||
|
#include "//./header1.h"
|
||
|
int main() { return ZERO; }
|
||
|
)cpp");
|
||
|
RemapFile(Header1, "#define ZERO 0\n");
|
||
|
|
||
|
// Parse with header file provided as unsaved file, which does not exist on
|
||
|
// disk.
|
||
|
std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
|
||
|
ASSERT_TRUE(AST.get());
|
||
|
ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
|
||
|
|
||
|
// Create the unsaved file also on disk and check that preamble was reused.
|
||
|
AddFile(Header1, "#define ZERO 0\n");
|
||
|
ASSERT_TRUE(ReparseAST(AST));
|
||
|
ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
|
||
|
}
|
||
|
|
||
|
TEST_F(PCHPreambleTest,
|
||
|
ReparseReusesPreambleAfterUnsavedFileWasRemovedFromDisk) {
|
||
|
std::string Header1 = "//./foo/header1.h";
|
||
|
std::string MainName = "//./main.cpp";
|
||
|
std::string MainFileContent = R"cpp(
|
||
|
#include "//./foo/header1.h"
|
||
|
int main() { return ZERO; }
|
||
|
)cpp";
|
||
|
AddFile(MainName, MainFileContent);
|
||
|
AddFile(Header1, "#define ZERO 0\n");
|
||
|
RemapFile(Header1, "#define ZERO 0\n");
|
||
|
|
||
|
// Parse with header file provided as unsaved file, which exists on disk.
|
||
|
std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
|
||
|
ASSERT_TRUE(AST.get());
|
||
|
ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
|
||
|
ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
|
||
|
|
||
|
// Remove the unsaved file from disk and check that the preamble was reused.
|
||
|
ResetVFS();
|
||
|
AddFile(MainName, MainFileContent);
|
||
|
ASSERT_TRUE(ReparseAST(AST));
|
||
|
ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
|
||
|
}
|
||
|
|
||
|
TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) {
|
||
|
std::string Header1 = "//./header1.h";
|
||
|
std::string Header2 = "//./header2.h";
|
||
|
std::string MainName = "//./main.cpp";
|
||
|
AddFile(Header1, "");
|
||
|
AddFile(Header2, "#pragma once");
|
||
|
AddFile(MainName,
|
||
|
"#include \"//./foo/../header1.h\"\n"
|
||
|
"#include \"//./foo/../header2.h\"\n"
|
||
|
"int main() { return ZERO; }");
|
||
|
RemapFile(Header1, "static const int ZERO = 0;\n");
|
||
|
|
||
|
std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
|
||
|
ASSERT_TRUE(AST.get());
|
||
|
ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
|
||
|
|
||
|
unsigned initialCounts[] = {
|
||
|
GetFileReadCount(MainName),
|
||
|
GetFileReadCount(Header1),
|
||
|
GetFileReadCount(Header2)
|
||
|
};
|
||
|
|
||
|
ASSERT_TRUE(ReparseAST(AST));
|
||
|
|
||
|
ASSERT_NE(initialCounts[0], GetFileReadCount(MainName));
|
||
|
ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1));
|
||
|
ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2));
|
||
|
}
|
||
|
|
||
|
TEST_F(PCHPreambleTest, ParseWithBom) {
|
||
|
std::string Header = "//./header.h";
|
||
|
std::string Main = "//./main.cpp";
|
||
|
AddFile(Header, "int random() { return 4; }");
|
||
|
AddFile(Main,
|
||
|
"\xef\xbb\xbf"
|
||
|
"#include \"//./header.h\"\n"
|
||
|
"int main() { return random() -2; }");
|
||
|
|
||
|
std::unique_ptr<ASTUnit> AST(ParseAST(Main));
|
||
|
ASSERT_TRUE(AST.get());
|
||
|
ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
|
||
|
|
||
|
unsigned HeaderReadCount = GetFileReadCount(Header);
|
||
|
|
||
|
ASSERT_TRUE(ReparseAST(AST));
|
||
|
ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
|
||
|
|
||
|
// Check preamble PCH was really reused
|
||
|
ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header));
|
||
|
|
||
|
// Remove BOM
|
||
|
RemapFile(Main,
|
||
|
"#include \"//./header.h\"\n"
|
||
|
"int main() { return random() -2; }");
|
||
|
|
||
|
ASSERT_TRUE(ReparseAST(AST));
|
||
|
ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
|
||
|
|
||
|
ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
|
||
|
HeaderReadCount = GetFileReadCount(Header);
|
||
|
|
||
|
// Add BOM back
|
||
|
RemapFile(Main,
|
||
|
"\xef\xbb\xbf"
|
||
|
"#include \"//./header.h\"\n"
|
||
|
"int main() { return random() -2; }");
|
||
|
|
||
|
ASSERT_TRUE(ReparseAST(AST));
|
||
|
ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
|
||
|
|
||
|
ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
|
||
|
}
|
||
|
|
||
|
} // anonymous namespace
|