Index: include/clang/Basic/VirtualFileSystem.h =================================================================== --- include/clang/Basic/VirtualFileSystem.h +++ include/clang/Basic/VirtualFileSystem.h @@ -265,6 +265,15 @@ /// the operating system. IntrusiveRefCntPtr getRealFileSystem(); +/// \brief Creates a \p vfs::FileSystem for the 'real' file system that does not +/// change to global state of the program on calls to +/// setCurrentWorkingDirectory(). Internally it has to do more paths +/// manipulations than the vfs::FileSystem returned by getRealFileSystem(). Note +/// that returned instance of vfs::FileSystem is not thread-safe. +/// Thread-friendly refers to the fact that it does not use global +/// WorkingDirectory. +IntrusiveRefCntPtr createThreadFriendlyRealFS(); + /// \brief A file system that allows overlaying one \p AbstractFileSystem on top /// of another. /// Index: lib/Basic/VirtualFileSystem.cpp =================================================================== --- lib/Basic/VirtualFileSystem.cpp +++ lib/Basic/VirtualFileSystem.cpp @@ -136,6 +136,7 @@ Status S; std::string RealName; friend class RealFileSystem; + friend class ThreadFriendlyRealFileSystem; RealFile(int FD, StringRef NewName, StringRef NewRealPathName) : FD(FD), S(NewName, {}, {}, {}, {}, {}, llvm::sys::fs::file_type::status_error, {}), @@ -239,7 +240,9 @@ namespace { class RealFSDirIter : public clang::vfs::detail::DirIterImpl { +protected: llvm::sys::fs::directory_iterator Iter; + public: RealFSDirIter(const Twine &Path, std::error_code &EC) : Iter(Path, EC) { if (!EC && Iter != llvm::sys::fs::directory_iterator()) { @@ -271,6 +274,131 @@ return directory_iterator(std::make_shared(Dir, EC)); } +//===-----------------------------------------------------------------------===/ +// ThreadFriendlyRealFileSystem implementation +//===-----------------------------------------------------------------------===/ + +namespace { +/// \brief A thread-friendly version of RealFileSystem. Does not call +/// llvm::sys::fs::set_current_path, stores CurrentWorkingDirectory as a field +/// instead. This class is not thread-safe, though, it merely gets rid of +/// manipulating global state. +class ThreadFriendlyRealFileSystem : public RealFileSystem { +public: + ThreadFriendlyRealFileSystem() + : CurrentWorkingDir(RealFileSystem::getCurrentWorkingDirectory()) {} + + ErrorOr status(const Twine &Path) override; + ErrorOr> openFileForRead(const Twine &Path) override; + directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override; + + llvm::ErrorOr getCurrentWorkingDirectory() const override; + std::error_code setCurrentWorkingDirectory(const Twine &Path) override; + +private: + llvm::ErrorOr CurrentWorkingDir; +}; +} // end anonymous namespace + +ErrorOr ThreadFriendlyRealFileSystem::status(const Twine &Path) { + SmallString<256> AbsPath; + Path.toVector(AbsPath); + if (std::error_code Err = makeAbsolute(AbsPath)) + return Err; + + auto Stat = RealFileSystem::status(AbsPath); + if (!Stat) + return Stat; + return Status::copyWithNewName(Stat.get(), Path.str()); +} + +ErrorOr> +ThreadFriendlyRealFileSystem::openFileForRead(const Twine &Name) { + SmallString<256> AbsPath; + Name.toVector(AbsPath); + if (std::error_code Err = makeAbsolute(AbsPath)) + return Err; + + // Slightly modified copy of RealFileSystem::openFileForRead. + // We use AbsPath here instead of name as an argument to + // sys::fs::openFileForRead(). + // If you change this function, please update RealFileSystem::openFileForRead + // accordingly . + int FD; + SmallString<256> RealName; + if (std::error_code EC = sys::fs::openFileForRead(AbsPath, FD, &RealName)) + return EC; + return std::unique_ptr(new RealFile(FD, Name.str(), RealName.str())); +} + +llvm::ErrorOr +ThreadFriendlyRealFileSystem::getCurrentWorkingDirectory() const { + return CurrentWorkingDir; +} + +std::error_code +ThreadFriendlyRealFileSystem::setCurrentWorkingDirectory(const Twine &Path) { + SmallString<256> AbsPath; + Path.toVector(AbsPath); + if (std::error_code EC = makeAbsolute(AbsPath)) + return EC; + + CurrentWorkingDir = AbsPath.str().str(); + return std::error_code(); +} + +IntrusiveRefCntPtr vfs::createThreadFriendlyRealFS() { + return new ThreadFriendlyRealFileSystem(); +} + +namespace { +class ThreadFriendlyRealFSDirIter : public RealFSDirIter { + std::string OriginalPath; + +public: + ThreadFriendlyRealFSDirIter(const Twine &OriginalPath, const Twine &AbsPath, + std::error_code &EC) + : RealFSDirIter(AbsPath, EC), OriginalPath(OriginalPath.str()) { + if (!EC) { + fixupCurrentEntryName(); + } + } + + std::error_code increment() override { + std::error_code EC = RealFSDirIter::increment(); + if (EC) + return EC; + + fixupCurrentEntryName(); + return EC; + } + +private: + void fixupCurrentEntryName() { + if (Iter == llvm::sys::fs::directory_iterator()) + return; + + llvm::SmallString<256> ModifiedPath = StringRef(OriginalPath); + + assert(llvm::sys::path::has_filename(Iter->path())); + auto FileName = llvm::sys::path::filename(Iter->path()); + llvm::sys::path::append(ModifiedPath, FileName); + + CurrentEntry = Status::copyWithNewName(CurrentEntry, ModifiedPath); + } +}; +} // namespace + +directory_iterator +ThreadFriendlyRealFileSystem::dir_begin(const Twine &Dir, std::error_code &EC) { + llvm::SmallString<256> AbsPath; + Dir.toVector(AbsPath); + EC = makeAbsolute(AbsPath); + + return directory_iterator( + std::make_shared(Dir, AbsPath, EC)); +} + //===-----------------------------------------------------------------------===/ // OverlayFileSystem implementation //===-----------------------------------------------------------------------===/ Index: unittests/Basic/VirtualFileSystemTest.cpp =================================================================== --- unittests/Basic/VirtualFileSystemTest.cpp +++ unittests/Basic/VirtualFileSystemTest.cpp @@ -12,6 +12,7 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/Host.h" #include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" #include "llvm/Support/SourceMgr.h" #include "gtest/gtest.h" #include @@ -477,6 +478,75 @@ } #endif +TEST(VirtualFileSystemTest, ThreadFriendlyRealFSWorkingDirTest) { + ScopedDir TestDirectory("thread-friendly-vfs-test", /*Unique*/ true); + + SmallString<128> PathA = TestDirectory.Path; + llvm::sys::path::append(PathA, "a"); + SmallString<128> PathAC = PathA; + llvm::sys::path::append(PathAC, "c"); + SmallString<128> PathB = TestDirectory.Path; + llvm::sys::path::append(PathB, "b"); + SmallString<128> PathBD = PathB; + llvm::sys::path::append(PathBD, "d"); + + ScopedDir A(PathA); + ScopedDir AC(PathAC); + ScopedDir B(PathB); + ScopedDir BD(PathBD); + + IntrusiveRefCntPtr FSA = vfs::createThreadFriendlyRealFS(); + // setCurrentWorkingDirectory should fininsh without error + ASSERT_TRUE(!FSA->setCurrentWorkingDirectory(Twine(A))); + + IntrusiveRefCntPtr FSB = vfs::createThreadFriendlyRealFS(); + // setCurrentWorkingDirectory should fininsh without error + ASSERT_TRUE(!FSB->setCurrentWorkingDirectory(Twine(B))); + + ASSERT_TRUE(FSA->getCurrentWorkingDirectory()); + EXPECT_EQ(*FSA->getCurrentWorkingDirectory(), StringRef(A)); + + ASSERT_TRUE(FSB->getCurrentWorkingDirectory()); + EXPECT_EQ(*FSB->getCurrentWorkingDirectory(), StringRef(B)); + + auto C = FSA->status("c"); + // a/c should be found in FSA + ASSERT_TRUE(!!C); + // name of 'c' must be relative + EXPECT_EQ(C->getName(), "c"); + EXPECT_TRUE(C->isDirectory()); + + // "a", "b" and "d" should not be found in FSA. + EXPECT_FALSE(!!FSA->status("a")); + EXPECT_FALSE(!!FSA->status("b")); + EXPECT_FALSE(!!FSA->status("d")); + + auto D = FSB->status("d"); + // b/d should be found in FSB + ASSERT_TRUE(!!D); + // name of 'd' must be relative + EXPECT_EQ(D->getName(), "d"); + EXPECT_TRUE(D->isDirectory()); + + // "a", "b" and "c" should not be found in FSB. + EXPECT_FALSE(!!FSB->status("a")); + EXPECT_FALSE(!!FSB->status("b")); + EXPECT_FALSE(!!FSB->status("c")); + + SmallString<128> RelPathC = StringRef(".."); + llvm::sys::path::append(RelPathC, "a", "c"); + auto RelCFromFSA = FSA->status(RelPathC); + ASSERT_TRUE(!!RelCFromFSA); + EXPECT_EQ(RelCFromFSA->getName(), StringRef(RelPathC)); + + auto RelCFromFSB = FSB->status(RelPathC); + ASSERT_TRUE(!!RelCFromFSB); + EXPECT_EQ(RelCFromFSB->getName(), StringRef(RelPathC)); + + EXPECT_TRUE(C->equivalent(*RelCFromFSA)); + EXPECT_TRUE(C->equivalent(*RelCFromFSB)); +} + template static void checkContents(DirIter I, ArrayRef ExpectedOut) { std::error_code EC;