From a37ef291c84787329d1d77be66593042577b05ec Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Wed, 11 Jul 2018 14:08:17 +0000 Subject: [PATCH] [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 --- clang/lib/Basic/FileManager.cpp | 8 +- clang/lib/Basic/VirtualFileSystem.cpp | 74 ++++++++++++++----- .../unittests/Basic/VirtualFileSystemTest.cpp | 33 ++++++++- clang/unittests/Driver/ToolChainTest.cpp | 2 +- 4 files changed, 95 insertions(+), 22 deletions(-) diff --git a/clang/lib/Basic/FileManager.cpp b/clang/lib/Basic/FileManager.cpp index 7e2d01c4981d..0a79800ed72a 100644 --- a/clang/lib/Basic/FileManager.cpp +++ b/clang/lib/Basic/FileManager.cpp @@ -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; } diff --git a/clang/lib/Basic/VirtualFileSystem.cpp b/clang/lib/Basic/VirtualFileSystem.cpp index bcfcbdbb9014..f8acde94ca92 100644 --- a/clang/lib/Basic/VirtualFileSystem.cpp +++ b/clang/lib/Basic/VirtualFileSystem.cpp @@ -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() override { return Node.getStatus(); } +public: + explicit InMemoryFileAdaptor(InMemoryFile &Node, std::string RequestedName) + : Node(Node), RequestedName(std::move(RequestedName)) {} + + llvm::ErrorOr status() override { + return Node.getStatus(RequestedName); + } llvm::ErrorOr> getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator, @@ -711,7 +737,7 @@ lookupInMemoryNode(const InMemoryFileSystem &FS, detail::InMemoryDirectory *Dir, llvm::ErrorOr 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(*Node)) - return std::unique_ptr(new detail::InMemoryFileAdaptor(*F)); + return std::unique_ptr( + 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(*Node)) - return directory_iterator(std::make_shared(*DirNode)); + return directory_iterator( + std::make_shared(*DirNode, Dir.str())); EC = make_error_code(llvm::errc::not_a_directory); return directory_iterator(std::make_shared()); diff --git a/clang/unittests/Basic/VirtualFileSystemTest.cpp b/clang/unittests/Basic/VirtualFileSystemTest.cpp index c795be07acf1..54c355d94835 100644 --- a/clang/unittests/Basic/VirtualFileSystemTest.cpp +++ b/clang/unittests/Basic/VirtualFileSystemTest.cpp @@ -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 { diff --git a/clang/unittests/Driver/ToolChainTest.cpp b/clang/unittests/Driver/ToolChainTest.cpp index d4198eaeb1cb..0d4c545bd796 100644 --- a/clang/unittests/Driver/ToolChainTest.cpp +++ b/clang/unittests/Driver/ToolChainTest.cpp @@ -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"