[VirtualFileSystem] InMemoryFileSystem::status: Return a Status with the requested name

Summary:
InMemoryFileSystem::status behaves differently than
RealFileSystem::status.  The Name contained in the Status returned by
RealFileSystem::status will be the path as requested by the caller,
whereas InMemoryFileSystem::status returns the normalized path.

For example, when requested the status for "../src/first.h",
RealFileSystem returns a Status with "../src/first.h" as the Name.
InMemoryFileSystem returns "/absolute/path/to/src/first.h".

The reason for this change is that I want to make a unit test in the
clangd testsuite (where we use an InMemoryFileSystem) to reproduce a
bug I get with the clangd program (where a RealFileSystem is used).
This difference in behavior "hides" the bug in the unit test version.

In general, I guess it's good if InMemoryFileSystem works as much as
possible like RealFileSystem.

Doing so made the FileEntry::RealPathName value (assigned in
FileManager::getFile) wrong when using the InMemoryFileSystem.  That's
because it assumes that vfs::File::getName will always return the real
path.  I changed to to use FileSystem::getRealPath instead.

Subscribers: ilya-biryukov, ioeric, cfe-commits

Differential Revision: https://reviews.llvm.org/D48903

llvm-svn: 336807
This commit is contained in:
Simon Marchi 2018-07-11 14:08:17 +00:00
parent 1edde95abd
commit a37ef291c8
4 changed files with 95 additions and 22 deletions

View File

@ -315,9 +315,11 @@ const FileEntry *FileManager::getFile(StringRef Filename, bool openFile,
UFE.InPCH = Data.InPCH;
UFE.File = std::move(F);
UFE.IsValid = true;
if (UFE.File)
if (auto RealPathName = UFE.File->getName())
UFE.RealPathName = *RealPathName;
SmallString<128> RealPathName;
if (!FS->getRealPath(InterndFileName, RealPathName))
UFE.RealPathName = RealPathName.str();
return &UFE;
}

View File

@ -471,15 +471,33 @@ enum InMemoryNodeKind { IME_File, IME_Directory };
/// The in memory file system is a tree of Nodes. Every node can either be a
/// file or a directory.
class InMemoryNode {
Status Stat;
InMemoryNodeKind Kind;
Status Stat;
protected:
/// Return Stat. This should only be used for internal/debugging use. When
/// clients wants the Status of this node, they should use
/// \p getStatus(StringRef).
const Status& getStatus() const {
return Stat;
}
public:
InMemoryNode(Status Stat, InMemoryNodeKind Kind)
: Stat(std::move(Stat)), Kind(Kind) {}
: Kind(Kind), Stat(std::move(Stat)) {}
virtual ~InMemoryNode() = default;
const Status &getStatus() const { return Stat; }
/// Return the \p Status for this node. \p RequestedName should be the name
/// through which the caller referred to this node. It will override
/// \p Status::Name in the return value, to mimic the behavior of \p RealFile.
Status getStatus(StringRef RequestedName) const {
return Status::copyWithNewName(Stat, RequestedName);
}
/// Get the filename of this node (the name without the directory part).
StringRef getFileName() const {
return llvm::sys::path::filename(Stat.getName());
}
InMemoryNodeKind getKind() const { return Kind; }
virtual std::string toString(unsigned Indent) const = 0;
};
@ -504,14 +522,22 @@ public:
}
};
/// Adapt a InMemoryFile for VFS' File interface.
/// Adapt a InMemoryFile for VFS' File interface. The goal is to make
/// \p InMemoryFileAdaptor mimic as much as possible the behavior of
/// \p RealFile.
class InMemoryFileAdaptor : public File {
InMemoryFile &Node;
public:
explicit InMemoryFileAdaptor(InMemoryFile &Node) : Node(Node) {}
/// The name to use when returning a Status for this file.
std::string RequestedName;
llvm::ErrorOr<Status> status() override { return Node.getStatus(); }
public:
explicit InMemoryFileAdaptor(InMemoryFile &Node, std::string RequestedName)
: Node(Node), RequestedName(std::move(RequestedName)) {}
llvm::ErrorOr<Status> status() override {
return Node.getStatus(RequestedName);
}
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator,
@ -711,7 +737,7 @@ lookupInMemoryNode(const InMemoryFileSystem &FS, detail::InMemoryDirectory *Dir,
llvm::ErrorOr<Status> InMemoryFileSystem::status(const Twine &Path) {
auto Node = lookupInMemoryNode(*this, Root.get(), Path);
if (Node)
return (*Node)->getStatus();
return (*Node)->getStatus(Path.str());
return Node.getError();
}
@ -724,7 +750,8 @@ InMemoryFileSystem::openFileForRead(const Twine &Path) {
// When we have a file provide a heap-allocated wrapper for the memory buffer
// to match the ownership semantics for File.
if (auto *F = dyn_cast<detail::InMemoryFile>(*Node))
return std::unique_ptr<File>(new detail::InMemoryFileAdaptor(*F));
return std::unique_ptr<File>(
new detail::InMemoryFileAdaptor(*F, Path.str()));
// FIXME: errc::not_a_file?
return make_error_code(llvm::errc::invalid_argument);
@ -736,21 +763,33 @@ namespace {
class InMemoryDirIterator : public clang::vfs::detail::DirIterImpl {
detail::InMemoryDirectory::const_iterator I;
detail::InMemoryDirectory::const_iterator E;
std::string RequestedDirName;
void setCurrentEntry() {
if (I != E) {
SmallString<256> Path(RequestedDirName);
llvm::sys::path::append(Path, I->second->getFileName());
CurrentEntry = I->second->getStatus(Path.str());
} else {
// When we're at the end, make CurrentEntry invalid and DirIterImpl will
// do the rest.
CurrentEntry = Status();
}
}
public:
InMemoryDirIterator() = default;
explicit InMemoryDirIterator(detail::InMemoryDirectory &Dir)
: I(Dir.begin()), E(Dir.end()) {
if (I != E)
CurrentEntry = I->second->getStatus();
explicit InMemoryDirIterator(detail::InMemoryDirectory &Dir,
std::string RequestedDirName)
: I(Dir.begin()), E(Dir.end()),
RequestedDirName(std::move(RequestedDirName)) {
setCurrentEntry();
}
std::error_code increment() override {
++I;
// When we're at the end, make CurrentEntry invalid and DirIterImpl will do
// the rest.
CurrentEntry = I != E ? I->second->getStatus() : Status();
setCurrentEntry();
return {};
}
};
@ -766,7 +805,8 @@ directory_iterator InMemoryFileSystem::dir_begin(const Twine &Dir,
}
if (auto *DirNode = dyn_cast<detail::InMemoryDirectory>(*Node))
return directory_iterator(std::make_shared<InMemoryDirIterator>(*DirNode));
return directory_iterator(
std::make_shared<InMemoryDirIterator>(*DirNode, Dir.str()));
EC = make_error_code(llvm::errc::not_a_directory);
return directory_iterator(std::make_shared<InMemoryDirIterator>());

View File

@ -794,7 +794,7 @@ TEST_F(InMemoryFileSystemTest, WorkingDirectory) {
auto Stat = FS.status("/b/c");
ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
ASSERT_EQ("c", Stat->getName());
ASSERT_EQ("/b/c", Stat->getName());
ASSERT_EQ("/b", *FS.getCurrentWorkingDirectory());
Stat = FS.status("c");
@ -919,6 +919,37 @@ TEST_F(InMemoryFileSystemTest, AddDirectoryThenAddChild) {
ASSERT_TRUE(Stat->isRegularFile());
}
// Test that the name returned by status() is in the same form as the path that
// was requested (to match the behavior of RealFileSystem).
TEST_F(InMemoryFileSystemTest, StatusName) {
NormalizedFS.addFile("/a/b/c", 0, MemoryBuffer::getMemBuffer("abc"),
/*User=*/None,
/*Group=*/None, sys::fs::file_type::regular_file);
NormalizedFS.setCurrentWorkingDirectory("/a/b");
// Access using InMemoryFileSystem::status.
auto Stat = NormalizedFS.status("../b/c");
ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n"
<< NormalizedFS.toString();
ASSERT_TRUE(Stat->isRegularFile());
ASSERT_EQ("../b/c", Stat->getName());
// Access using InMemoryFileAdaptor::status.
auto File = NormalizedFS.openFileForRead("../b/c");
ASSERT_FALSE(File.getError()) << File.getError() << "\n"
<< NormalizedFS.toString();
Stat = (*File)->status();
ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n"
<< NormalizedFS.toString();
ASSERT_TRUE(Stat->isRegularFile());
ASSERT_EQ("../b/c", Stat->getName());
// Access using a directory iterator.
std::error_code EC;
clang::vfs::directory_iterator It = NormalizedFS.dir_begin("../b", EC);
ASSERT_EQ("../b/c", It->getName());
}
// NOTE: in the tests below, we use '//root/' as our root directory, since it is
// a legal *absolute* path on Windows as well as *nix.
class VFSFromYAMLTest : public ::testing::Test {

View File

@ -113,7 +113,7 @@ TEST(ToolChainTest, VFSGCCInstallationRelativeDir) {
std::replace(S.begin(), S.end(), '\\', '/');
#endif
EXPECT_EQ("Found candidate GCC installation: "
"/home/test/lib/gcc/arm-linux-gnueabi/4.6.1\n"
"/home/test/bin/../lib/gcc/arm-linux-gnueabi/4.6.1\n"
"Selected GCC installation: "
"/home/test/bin/../lib/gcc/arm-linux-gnueabi/4.6.1\n"
"Candidate multilib: .;@m32\n"