The minimizing filesystem used by the dependency scanner isn't great when it comes to the consistency of its caches. There are two problems that can be exposed by a filesystem that changes during dependency scan: 1. In-memory cache entries for original and minimized files are distinct, populated at different times using separate stat/open syscalls. This means that when a file is read with minimization disabled, its contents might be inconsistent when the same file is read with minimization enabled at later point (and vice versa). 2. In-memory cache entries are indexed by filename. This is problematic for symlinks, where the contents of the symlink might be inconsistent with contents of the original file (for the same reason as in problem 1). This patch ensures consistency by always stating/reading a file exactly once. The original contents are always cached and minimized contents are derived from that on demand. The cache entries are now indexed by their `UniqueID` ensuring consistency for symlinks too. Moreover, the stat/read syscalls are now issued outside of critical section. Depends on D115935. Reviewed By: dexonsmith Differential Revision: https://reviews.llvm.org/D114966
257 lines
9.9 KiB
C++
257 lines
9.9 KiB
C++
//===- unittest/Tooling/ToolingTest.cpp - Tooling unit 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/AST/ASTConsumer.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/DeclGroup.h"
|
|
#include "clang/Frontend/ASTUnit.h"
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
#include "clang/Frontend/FrontendAction.h"
|
|
#include "clang/Frontend/FrontendActions.h"
|
|
#include "clang/Tooling/CompilationDatabase.h"
|
|
#include "clang/Tooling/DependencyScanning/DependencyScanningFilesystem.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/MC/TargetRegistry.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/TargetSelect.h"
|
|
#include "gtest/gtest.h"
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
namespace clang {
|
|
namespace tooling {
|
|
|
|
namespace {
|
|
|
|
/// Prints out all of the gathered dependencies into a string.
|
|
class TestFileCollector : public DependencyFileGenerator {
|
|
public:
|
|
TestFileCollector(DependencyOutputOptions &Opts,
|
|
std::vector<std::string> &Deps)
|
|
: DependencyFileGenerator(Opts), Deps(Deps) {}
|
|
|
|
void finishedMainFile(DiagnosticsEngine &Diags) override {
|
|
auto NewDeps = getDependencies();
|
|
Deps.insert(Deps.end(), NewDeps.begin(), NewDeps.end());
|
|
}
|
|
|
|
private:
|
|
std::vector<std::string> &Deps;
|
|
};
|
|
|
|
class TestDependencyScanningAction : public tooling::ToolAction {
|
|
public:
|
|
TestDependencyScanningAction(std::vector<std::string> &Deps) : Deps(Deps) {}
|
|
|
|
bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,
|
|
FileManager *FileMgr,
|
|
std::shared_ptr<PCHContainerOperations> PCHContainerOps,
|
|
DiagnosticConsumer *DiagConsumer) override {
|
|
CompilerInstance Compiler(std::move(PCHContainerOps));
|
|
Compiler.setInvocation(std::move(Invocation));
|
|
Compiler.setFileManager(FileMgr);
|
|
|
|
Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false);
|
|
if (!Compiler.hasDiagnostics())
|
|
return false;
|
|
|
|
Compiler.createSourceManager(*FileMgr);
|
|
Compiler.addDependencyCollector(std::make_shared<TestFileCollector>(
|
|
Compiler.getInvocation().getDependencyOutputOpts(), Deps));
|
|
|
|
auto Action = std::make_unique<PreprocessOnlyAction>();
|
|
return Compiler.ExecuteAction(*Action);
|
|
}
|
|
|
|
private:
|
|
std::vector<std::string> &Deps;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(DependencyScanner, ScanDepsReuseFilemanager) {
|
|
std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"};
|
|
StringRef CWD = "/root";
|
|
FixedCompilationDatabase CDB(CWD, Compilation);
|
|
|
|
auto VFS = new llvm::vfs::InMemoryFileSystem();
|
|
VFS->setCurrentWorkingDirectory(CWD);
|
|
auto Sept = llvm::sys::path::get_separator();
|
|
std::string HeaderPath =
|
|
std::string(llvm::formatv("{0}root{0}header.h", Sept));
|
|
std::string SymlinkPath =
|
|
std::string(llvm::formatv("{0}root{0}symlink.h", Sept));
|
|
std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));
|
|
|
|
VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
|
|
VFS->addHardLink(SymlinkPath, HeaderPath);
|
|
VFS->addFile(TestPath, 0,
|
|
llvm::MemoryBuffer::getMemBuffer(
|
|
"#include \"symlink.h\"\n#include \"header.h\"\n"));
|
|
|
|
ClangTool Tool(CDB, {"test.cpp"}, std::make_shared<PCHContainerOperations>(),
|
|
VFS);
|
|
Tool.clearArgumentsAdjusters();
|
|
std::vector<std::string> Deps;
|
|
TestDependencyScanningAction Action(Deps);
|
|
Tool.run(&Action);
|
|
using llvm::sys::path::convert_to_slash;
|
|
// The first invocation should return dependencies in order of access.
|
|
ASSERT_EQ(Deps.size(), 3u);
|
|
EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");
|
|
EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h");
|
|
EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h");
|
|
|
|
// The file manager should still have two FileEntries, as one file is a
|
|
// hardlink.
|
|
FileManager &Files = Tool.getFiles();
|
|
EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u);
|
|
|
|
Deps.clear();
|
|
Tool.run(&Action);
|
|
// The second invocation should have the same order of dependencies.
|
|
ASSERT_EQ(Deps.size(), 3u);
|
|
EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");
|
|
EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h");
|
|
EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h");
|
|
|
|
EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u);
|
|
}
|
|
|
|
TEST(DependencyScanner, ScanDepsReuseFilemanagerSkippedFile) {
|
|
std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"};
|
|
StringRef CWD = "/root";
|
|
FixedCompilationDatabase CDB(CWD, Compilation);
|
|
|
|
auto VFS = new llvm::vfs::InMemoryFileSystem();
|
|
VFS->setCurrentWorkingDirectory(CWD);
|
|
auto Sept = llvm::sys::path::get_separator();
|
|
std::string HeaderPath =
|
|
std::string(llvm::formatv("{0}root{0}header.h", Sept));
|
|
std::string SymlinkPath =
|
|
std::string(llvm::formatv("{0}root{0}symlink.h", Sept));
|
|
std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));
|
|
std::string Test2Path =
|
|
std::string(llvm::formatv("{0}root{0}test2.cpp", Sept));
|
|
|
|
VFS->addFile(HeaderPath, 0,
|
|
llvm::MemoryBuffer::getMemBuffer("#pragma once\n"));
|
|
VFS->addHardLink(SymlinkPath, HeaderPath);
|
|
VFS->addFile(TestPath, 0,
|
|
llvm::MemoryBuffer::getMemBuffer(
|
|
"#include \"header.h\"\n#include \"symlink.h\"\n"));
|
|
VFS->addFile(Test2Path, 0,
|
|
llvm::MemoryBuffer::getMemBuffer(
|
|
"#include \"symlink.h\"\n#include \"header.h\"\n"));
|
|
|
|
ClangTool Tool(CDB, {"test.cpp", "test2.cpp"},
|
|
std::make_shared<PCHContainerOperations>(), VFS);
|
|
Tool.clearArgumentsAdjusters();
|
|
std::vector<std::string> Deps;
|
|
TestDependencyScanningAction Action(Deps);
|
|
Tool.run(&Action);
|
|
using llvm::sys::path::convert_to_slash;
|
|
ASSERT_EQ(Deps.size(), 6u);
|
|
EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");
|
|
EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h");
|
|
EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h");
|
|
EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test2.cpp");
|
|
EXPECT_EQ(convert_to_slash(Deps[4]), "/root/symlink.h");
|
|
EXPECT_EQ(convert_to_slash(Deps[5]), "/root/header.h");
|
|
}
|
|
|
|
TEST(DependencyScanner, ScanDepsReuseFilemanagerHasInclude) {
|
|
std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"};
|
|
StringRef CWD = "/root";
|
|
FixedCompilationDatabase CDB(CWD, Compilation);
|
|
|
|
auto VFS = new llvm::vfs::InMemoryFileSystem();
|
|
VFS->setCurrentWorkingDirectory(CWD);
|
|
auto Sept = llvm::sys::path::get_separator();
|
|
std::string HeaderPath =
|
|
std::string(llvm::formatv("{0}root{0}header.h", Sept));
|
|
std::string SymlinkPath =
|
|
std::string(llvm::formatv("{0}root{0}symlink.h", Sept));
|
|
std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));
|
|
|
|
VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
|
|
VFS->addHardLink(SymlinkPath, HeaderPath);
|
|
VFS->addFile(
|
|
TestPath, 0,
|
|
llvm::MemoryBuffer::getMemBuffer("#if __has_include(\"header.h\") && "
|
|
"__has_include(\"symlink.h\")\n#endif"));
|
|
|
|
ClangTool Tool(CDB, {"test.cpp", "test.cpp"},
|
|
std::make_shared<PCHContainerOperations>(), VFS);
|
|
Tool.clearArgumentsAdjusters();
|
|
std::vector<std::string> Deps;
|
|
TestDependencyScanningAction Action(Deps);
|
|
Tool.run(&Action);
|
|
using llvm::sys::path::convert_to_slash;
|
|
ASSERT_EQ(Deps.size(), 6u);
|
|
EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");
|
|
EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h");
|
|
EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h");
|
|
EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test.cpp");
|
|
EXPECT_EQ(convert_to_slash(Deps[4]), "/root/header.h");
|
|
EXPECT_EQ(convert_to_slash(Deps[5]), "/root/symlink.h");
|
|
}
|
|
|
|
namespace dependencies {
|
|
TEST(DependencyScanningFilesystem, IgnoredFilesAreCachedSeparately1) {
|
|
auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
|
|
VFS->addFile("/mod.h", 0,
|
|
llvm::MemoryBuffer::getMemBuffer("#include <foo.h>\n"
|
|
"// hi there!\n"));
|
|
|
|
DependencyScanningFilesystemSharedCache SharedCache;
|
|
auto Mappings = std::make_unique<ExcludedPreprocessorDirectiveSkipMapping>();
|
|
DependencyScanningWorkerFilesystem DepFS(SharedCache, VFS, Mappings.get());
|
|
|
|
DepFS.enableMinimizationOfAllFiles(); // Let's be explicit for clarity.
|
|
auto StatusMinimized0 = DepFS.status("/mod.h");
|
|
DepFS.disableMinimization("/mod.h");
|
|
auto StatusFull1 = DepFS.status("/mod.h");
|
|
|
|
EXPECT_TRUE(StatusMinimized0);
|
|
EXPECT_TRUE(StatusFull1);
|
|
EXPECT_EQ(StatusMinimized0->getSize(), 17u);
|
|
EXPECT_EQ(StatusFull1->getSize(), 30u);
|
|
EXPECT_EQ(StatusMinimized0->getName(), StringRef("/mod.h"));
|
|
EXPECT_EQ(StatusFull1->getName(), StringRef("/mod.h"));
|
|
}
|
|
|
|
TEST(DependencyScanningFilesystem, IgnoredFilesAreCachedSeparately2) {
|
|
auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
|
|
VFS->addFile("/mod.h", 0,
|
|
llvm::MemoryBuffer::getMemBuffer("#include <foo.h>\n"
|
|
"// hi there!\n"));
|
|
|
|
DependencyScanningFilesystemSharedCache SharedCache;
|
|
auto Mappings = std::make_unique<ExcludedPreprocessorDirectiveSkipMapping>();
|
|
DependencyScanningWorkerFilesystem DepFS(SharedCache, VFS, Mappings.get());
|
|
|
|
DepFS.disableMinimization("/mod.h");
|
|
auto StatusFull0 = DepFS.status("/mod.h");
|
|
DepFS.enableMinimizationOfAllFiles();
|
|
auto StatusMinimized1 = DepFS.status("/mod.h");
|
|
|
|
EXPECT_TRUE(StatusFull0);
|
|
EXPECT_TRUE(StatusMinimized1);
|
|
EXPECT_EQ(StatusFull0->getSize(), 30u);
|
|
EXPECT_EQ(StatusMinimized1->getSize(), 17u);
|
|
EXPECT_EQ(StatusFull0->getName(), StringRef("/mod.h"));
|
|
EXPECT_EQ(StatusMinimized1->getName(), StringRef("/mod.h"));
|
|
}
|
|
|
|
} // end namespace dependencies
|
|
} // end namespace tooling
|
|
} // end namespace clang
|