diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp --- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp +++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp @@ -363,26 +363,26 @@ std::move(OverrideOptions), std::move(FS)); } -llvm::IntrusiveRefCntPtr -getVfsFromFile(const std::string &OverlayFile, - llvm::IntrusiveRefCntPtr BaseFS) { - llvm::ErrorOr> Buffer = - BaseFS->getBufferForFile(OverlayFile); - if (!Buffer) { - llvm::errs() << "Can't load virtual filesystem overlay file '" - << OverlayFile << "': " << Buffer.getError().message() - << ".\n"; - return nullptr; - } - - IntrusiveRefCntPtr FS = vfs::getVFSFromYAML( - std::move(Buffer.get()), /*DiagHandler*/ nullptr, OverlayFile); - if (!FS) { - llvm::errs() << "Error: invalid virtual filesystem overlay file '" - << OverlayFile << "'.\n"; +static IntrusiveRefCntPtr +getVFSFromFile(StringRef OverlayFile) { + if (OverlayFile.empty()) + return makeIntrusiveRefCnt( + vfs::getRealFileSystem()); + + auto OverlayFS = vfs::getVFSFromYAMLs(OverlayFile, vfs::getRealFileSystem()); + if (auto Err = OverlayFS.takeError()) { + handleAllErrors(std::move(Err), [](const FileError &FE) { + if (FE.convertToErrorCode() == std::errc::no_such_file_or_directory) { + errs() << "Can't load virtual filesystem overlay file '" + << FE.getFileName() << "': " << FE.message() << ".\n"; + } else { + errs() << "Error: invalid virtual filesystem overlay file '" + << FE.getFileName() << "'.\n"; + } + }); return nullptr; } - return FS; + return std::move(*OverlayFS); } int clangTidyMain(int argc, const char **argv) { @@ -400,16 +400,10 @@ return 1; } - llvm::IntrusiveRefCntPtr BaseFS( - new vfs::OverlayFileSystem(vfs::getRealFileSystem())); - - if (!VfsOverlay.empty()) { - IntrusiveRefCntPtr VfsFromFile = - getVfsFromFile(VfsOverlay, BaseFS); - if (!VfsFromFile) - return 1; - BaseFS->pushOverlay(std::move(VfsFromFile)); - } + llvm::IntrusiveRefCntPtr BaseFS = + getVFSFromFile(VfsOverlay); + if (!BaseFS) + return 1; auto OwningOptionsProvider = createOptionsProvider(BaseFS); auto *OptionsProvider = OwningOptionsProvider.get(); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -4693,28 +4693,22 @@ clang::createVFSFromCompilerInvocation( const CompilerInvocation &CI, DiagnosticsEngine &Diags, IntrusiveRefCntPtr BaseFS) { - if (CI.getHeaderSearchOpts().VFSOverlayFiles.empty()) + ArrayRef OptFiles = CI.getHeaderSearchOpts().VFSOverlayFiles; + if (OptFiles.empty()) return BaseFS; - IntrusiveRefCntPtr Result = BaseFS; - // earlier vfs files are on the bottom - for (const auto &File : CI.getHeaderSearchOpts().VFSOverlayFiles) { - llvm::ErrorOr> Buffer = - Result->getBufferForFile(File); - if (!Buffer) { - Diags.Report(diag::err_missing_vfs_overlay_file) << File; - continue; - } - - IntrusiveRefCntPtr FS = llvm::vfs::getVFSFromYAML( - std::move(Buffer.get()), /*DiagHandler*/ nullptr, File, - /*DiagContext*/ nullptr, Result); - if (!FS) { - Diags.Report(diag::err_invalid_vfs_overlay) << File; - continue; - } - - Result = FS; + SmallVector Files(OptFiles.begin(), OptFiles.end()); + Expected> OverlayFS = + llvm::vfs::getVFSFromYAMLs(Files, BaseFS); + if (auto Err = OverlayFS.takeError()) { + llvm::handleAllErrors(std::move(Err), [&](const llvm::FileError &FE) { + if (FE.convertToErrorCode() == std::errc::no_such_file_or_directory) { + Diags.Report(diag::err_missing_vfs_overlay_file) << FE.getFileName(); + } else { + Diags.Report(diag::err_invalid_vfs_overlay) << FE.getFileName(); + } + }); + return BaseFS; } - return Result; + return *OverlayFS; } diff --git a/clang/test/VFS/Inputs/vfsroot.yaml b/clang/test/VFS/Inputs/vfsroot.yaml --- a/clang/test/VFS/Inputs/vfsroot.yaml +++ b/clang/test/VFS/Inputs/vfsroot.yaml @@ -26,13 +26,6 @@ } ] }, - { 'name': '/indirect-vfs-root-files', 'type': 'directory', - 'contents': [ - { 'name': 'actual_header.h', 'type': 'file', - 'external-contents': 'TEST_DIR/Inputs/actual_header.h' - } - ] - }, { 'name': 'TEST_DIR/Inputs/Broken.framework', 'type': 'directory', 'contents': [ { 'name': 'Headers/A.h', 'type': 'file', diff --git a/clang/test/VFS/directory.c b/clang/test/VFS/directory.c --- a/clang/test/VFS/directory.c +++ b/clang/test/VFS/directory.c @@ -1,13 +1,11 @@ // RUN: rm -rf %t // RUN: mkdir -p %t/Underlying // RUN: mkdir -p %t/Overlay -// RUN: mkdir -p %t/Middle // RUN: echo '// B.h in Underlying' > %t/Underlying/B.h // RUN: echo '#ifdef NESTED' >> %t/Underlying/B.h // RUN: echo '#include "C.h"' >> %t/Underlying/B.h // RUN: echo '#endif' >> %t/Underlying/B.h // RUN: echo '// C.h in Underlying' > %t/Underlying/C.h -// RUN: echo '// C.h in Middle' > %t/Middle/C.h // RUN: echo '// C.h in Overlay' > %t/Overlay/C.h // 1) Underlying -> Overlay (C.h found, B.h falling back to Underlying) @@ -17,31 +15,11 @@ // RUN: sed -e "s@INPUT_DIR@Overlay@g" -e "s@OUT_DIR@%{/t:regex_replacement}/Underlying@g" %S/Inputs/vfsoverlay-directory-relative.yaml > %t/vfs-relative.yaml // RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs-relative.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s +// DIRECT: # 1 "{{.*(/|\\\\)Underlying(/|\\\\)}}B.h" // DIRECT: {{^}}// B.h in Underlying +// DIRECT: # 1 "{{.*(/|\\\\)Overlay(/|\\\\)}}C.h" // DIRECT: {{^}}// C.h in Overlay -// 2) Underlying -> Middle -> Overlay (C.h found, B.h falling back to Underlying) -// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}/Overlay@g" -e "s@OUT_DIR@%{/t:regex_replacement}/Middle@g" %S/Inputs/vfsoverlay-directory.yaml > %t/vfs.yaml -// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}/Middle@g" -e "s@OUT_DIR@%{/t:regex_replacement}/Underlying@g" %S/Inputs/vfsoverlay-directory.yaml > %t/vfs2.yaml -// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s -// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -DNESTED -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s - -// Same as direct above - -// 3) Underlying -> Middle -> Overlay (C.h falling back to Middle, B.h falling back to Underlying) -// RUN: rm -f %t/Overlay/C.h -// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=FALLBACK %s - -// FALLBACK: {{^}}// B.h in Underlying -// FALLBACK: {{^}}// C.h in Middle - -// 3) Underlying -> Middle -> Overlay (C.h falling back to Underlying, B.h falling back to Underlying) -// RUN: rm -f %t/Middle/C.h -// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=FALLBACK2 %s - -// FALLBACK2: {{^}}// B.h in Underlying -// FALLBACK2: {{^}}// C.h in Underlying - #include "B.h" #ifndef NESTED #include "C.h" diff --git a/clang/test/VFS/multiple-overlays.c b/clang/test/VFS/multiple-overlays.c new file mode 100644 --- /dev/null +++ b/clang/test/VFS/multiple-overlays.c @@ -0,0 +1,39 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s@EXTERNAL_DIR@%{/t:regex_replacement}/B@g" -e "s@NAME_DIR@%{/t:regex_replacement}/A@g" %t/vfs/base.yaml > %t/vfs/a-b.yaml +// RUN: sed -e "s@EXTERNAL_DIR@%{/t:regex_replacement}/C@g" -e "s@NAME_DIR@%{/t:regex_replacement}/B@g" %t/vfs/base.yaml > %t/vfs/b-c.yaml + +// Overlays should not be transitive, ie. given overlays of A -> B and B -> C, A should not remap to +// C. + +// RUN: %clang_cc1 -Werror -I %t/A -ivfsoverlay %t/vfs/b-c.yaml -ivfsoverlay %t/vfs/a-b.yaml -fsyntax-only -E -C %t/main.c 2>&1 | FileCheck --check-prefix=FROM_B %s +// FROM_B: # 1 "{{.*(/|\\\\)B(/|\\\\)}}Header.h" +// FROM_B: // Header.h in B + +// RUN: %clang_cc1 -Werror -I %t/B -ivfsoverlay %t/vfs/b-c.yaml -ivfsoverlay %t/vfs/a-b.yaml -fsyntax-only -E -C %t/main.c 2>&1 | FileCheck --check-prefix=FROM_C %s +// FROM_C: # 1 "{{.*(/|\\\\)C(/|\\\\)}}Header.h" +// FROM_C: // Header.h in C + +//--- main.c +#include "Header.h" + +//--- A/Header.h +// Header.h in A + +//--- B/Header.h +// Header.h in B + +//--- C/Header.h +// Header.h in C + +//--- vfs/base.yaml +{ + 'version': 0, + 'redirecting-with': 'fallthrough', + 'roots': [ + { 'name': 'NAME_DIR', + 'type': 'directory-remap', + 'external-contents': 'EXTERNAL_DIR' + } + ] +} diff --git a/clang/test/VFS/vfsroot-with-overlay.c b/clang/test/VFS/vfsroot-with-overlay.c --- a/clang/test/VFS/vfsroot-with-overlay.c +++ b/clang/test/VFS/vfsroot-with-overlay.c @@ -1,7 +1,7 @@ // RUN: rm -rf %t // RUN: mkdir -p %t // RUN: sed -e "s@TEST_DIR@%{/S:regex_replacement}@g" -e "s@OUT_DIR@%{/t:regex_replacement}@g" %S/Inputs/vfsroot.yaml > %t.yaml -// RUN: sed -e "s@INPUT_DIR@/indirect-vfs-root-files@g" -e "s@OUT_DIR@/overlay-dir@g" %S/Inputs/vfsoverlay.yaml > %t/vfsoverlay.yaml +// RUN: sed -e "s@INPUT_DIR@%{/S:regex_replacement}/Inputs@g" -e "s@OUT_DIR@/overlay-dir@g" %S/Inputs/vfsoverlay.yaml > %t/vfsoverlay.yaml // RUN: %clang_cc1 -Werror -ivfsoverlay %t.yaml -ivfsoverlay /direct-vfs-root-files/vfsoverlay.yaml -I /overlay-dir -fsyntax-only /tests/vfsroot-with-overlay.c #include "not_real.h" diff --git a/lldb/source/Commands/CommandObjectReproducer.cpp b/lldb/source/Commands/CommandObjectReproducer.cpp --- a/lldb/source/Commands/CommandObjectReproducer.cpp +++ b/lldb/source/Commands/CommandObjectReproducer.cpp @@ -409,23 +409,19 @@ switch (m_options.provider) { case eReproducerProviderFiles: { FileSpec vfs_mapping = loader->GetFile(); + std::string overlay_path = vfs_mapping.GetPath(); - // Read the VFS mapping. - ErrorOr> buffer = - vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath()); - if (!buffer) { - SetError(result, errorCodeToError(buffer.getError())); + Expected> vfs = + vfs::getVFSFromYAMLs(StringRef(overlay_path)); + if (auto err = vfs.takeError()) { + SetError(result, std::move(err)); return false; } - // Initialize a VFS from the given mapping. - IntrusiveRefCntPtr vfs = vfs::getVFSFromYAML( - std::move(buffer.get()), nullptr, vfs_mapping.GetPath()); - // Dump the VFS to a buffer. std::string str; raw_string_ostream os(str); - static_cast(*vfs).print(os); + (*vfs)->print(os); os.flush(); // Return the string. diff --git a/llvm/include/llvm/Support/VirtualFileSystem.h b/llvm/include/llvm/Support/VirtualFileSystem.h --- a/llvm/include/llvm/Support/VirtualFileSystem.h +++ b/llvm/include/llvm/Support/VirtualFileSystem.h @@ -570,14 +570,62 @@ /// Get a globally unique ID for a virtual file or directory. llvm::sys::fs::UniqueID getNextVirtualUniqueID(); -/// Gets a \p FileSystem for a virtual file system described in YAML -/// format. +/// \see getVFSFromYAMLs +/// +/// Returns nullptr if \p Buffer could not be parsed as a YAML containing +/// overlay mappings. std::unique_ptr getVFSFromYAML(std::unique_ptr Buffer, llvm::SourceMgr::DiagHandlerTy DiagHandler, StringRef YAMLFilePath, void *DiagContext = nullptr, IntrusiveRefCntPtr ExternalFS = getRealFileSystem()); +/// Convenience function to read each file from \p ExternalFS and pass their +/// buffers along to the next overload. Returns a \c FileError if any file +/// could not be read. +Expected> +getVFSFromYAMLs(ArrayRef YAMLOverlayPaths, + IntrusiveRefCntPtr ExternalFS = getRealFileSystem(), + SourceMgr::DiagHandlerTy DiagHandler = nullptr, + void *DiagContext = nullptr); + +/// Gets a \c FileSystem that runs operations on the virtual filesystems +/// described by each buffer in \p Buffers and then the \p ExternalFS if those +/// all fail (dependent on the options below). The order that each is run is +/// the reverse of the list, ie. the last buffer creates the first used +/// \c FileSystem. +/// +/// Returns a \c FileError if any of the buffers are invalid and an +/// \c OverlayFileSystem with just the \p ExternalFS if no buffers were +/// provided. +/// +/// Two extra root configuration options are handled by this method when +/// parsing the YAML of each filesystem: +/// 'fallthrough': +/// 'redirecting-with': +/// +/// These specify the order each filesystem is added to an \c OverlayFileSystem +/// and whether they're added at all - +/// - 'fallthrough': allow "falling through" to the next filesystem +/// - 'fallback': run opertions on the next filesystem before this one. +/// Most useful when specified on the first overlay, which +/// causes the \p ExternalFS to run before it (ie. "fallback" +/// to using the mapped paths only when original fails) +/// - 'redirect-only': skip running operations on any further filesystems, +/// ie. this is the last used filesystem. As with +/// 'fallback', this is more useful when specified on the +/// first overlay to avoid also checking the \p ExternalFS. +/// +/// \see OverlayFileSystem +/// \see RedirectingFileSystem +Expected> +getVFSFromYAMLs(ArrayRef YAMLOverlays, + IntrusiveRefCntPtr ExternalFS = getRealFileSystem(), + SourceMgr::DiagHandlerTy DiagHandler = nullptr, + void *DiagContext = nullptr); + struct YAMLVFSEntry { template YAMLVFSEntry(T1 &&VPath, T2 &&RPath, bool IsDirectory = false) @@ -616,10 +664,6 @@ /// 'case-sensitive': /// 'use-external-names': /// 'overlay-relative': -/// 'fallthrough': -/// 'redirecting-with': /// /// Virtual directories that list their contents are represented as /// \verbatim @@ -690,6 +734,11 @@ enum EntryKind { EK_Directory, EK_DirectoryRemap, EK_File }; enum NameKind { NK_NotSet, NK_External, NK_Virtual }; + // TODO: Simplify RedirectingFileSystem to remove redirection completely. + // Remove RedirectKind, Redirection, and all non-redirect-only paths. Require + // any old uses to move to using OverlayFileSystem instead (see the new + // getVFSFromYAMLs API). + /// The type of redirection to perform. enum class RedirectKind { /// Lookup the redirected path first (ie. the one specified in @@ -733,7 +782,7 @@ DirectoryEntry(StringRef Name, Status S) : Entry(EK_Directory, Name), S(std::move(S)) {} - Status getStatus() { return S; } + Status getStatus() const { return S; } void addContent(std::unique_ptr Content) { Contents.push_back(std::move(Content)); @@ -869,11 +918,6 @@ /// The file system to use for external references. IntrusiveRefCntPtr ExternalFS; - /// If IsRelativeOverlay is set, this represents the directory - /// path that should be prefixed to each 'external-contents' entry - /// when reading from YAML files. - std::string ExternalContentsPrefixDir; - /// @name Configuration /// @{ @@ -882,10 +926,6 @@ /// Currently, case-insensitive matching only works correctly with ASCII. bool CaseSensitive = is_style_posix(sys::path::Style::native); - /// IsRelativeOverlay marks whether a ExternalContentsPrefixDir path must - /// be prefixed in every 'external-contents' when reading from YAML files. - bool IsRelativeOverlay = false; - /// Whether to use to use the value of 'external-contents' for the /// names of files. This global value is overridable on a per-file basis. bool UseExternalNames = true; @@ -928,6 +968,7 @@ bool UseExternalNames, FileSystem &ExternalFS); ErrorOr status(const Twine &Path) override; + ErrorOr> openFileForRead(const Twine &Path) override; std::error_code getRealPath(const Twine &Path, @@ -943,10 +984,6 @@ directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override; - void setExternalContentsPrefixDir(StringRef PrefixDir); - - StringRef getExternalContentsPrefixDir() const; - /// Sets the redirection kind to \c Fallthrough if true or \c RedirectOnly /// otherwise. Will removed in the future, use \c setRedirection instead. void setFallthrough(bool Fallthrough); diff --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp --- a/llvm/lib/Support/VirtualFileSystem.cpp +++ b/llvm/lib/Support/VirtualFileSystem.cpp @@ -1461,14 +1461,6 @@ return Combined; } -void RedirectingFileSystem::setExternalContentsPrefixDir(StringRef PrefixDir) { - ExternalContentsPrefixDir = PrefixDir.str(); -} - -StringRef RedirectingFileSystem::getExternalContentsPrefixDir() const { - return ExternalContentsPrefixDir; -} - void RedirectingFileSystem::setFallthrough(bool Fallthrough) { if (Fallthrough) { Redirection = RedirectingFileSystem::RedirectKind::Fallthrough; @@ -1546,6 +1538,41 @@ void RedirectingFileSystem::dump() const { print(dbgs()); } #endif +namespace { + +struct YAMLParseResult { + /// The way in which this VFS should be added to an overlay. Note that the + /// order is right to left - given [A, B, C], any FS operations run on C + /// *first* and if failing, B, followed by A. + enum class RedirectKind { + /// Allow "falling through" to the next (ie. to the left) VFS. + Fallthrough, + /// Run any operations on the next VFS *first*, using this VFS as a + /// "fallback". + Fallback, + /// Do not perform any more operations on further VFS. + RedirectOnly + }; + + std::unique_ptr FS; + RedirectKind Redirection; + + // TODO: Remove after simplifying RedirectingFileSystem + static RedirectingFileSystem::RedirectKind + toRFSKind(RedirectKind Redirection) { + switch (Redirection) { + case RedirectKind::Fallthrough: + return RedirectingFileSystem::RedirectKind::Fallthrough; + case RedirectKind::Fallback: + return RedirectingFileSystem::RedirectKind::Fallback; + case RedirectKind::RedirectOnly: + return RedirectingFileSystem::RedirectKind::RedirectOnly; + } + } +}; + +} // namespace + /// A helper class to hold the common YAML parsing state. class llvm::vfs::RedirectingFileSystemParser { yaml::Stream &Stream; @@ -1587,19 +1614,18 @@ return false; } - Optional - parseRedirectKind(yaml::Node *N) { + Optional parseRedirectKind(yaml::Node *N) { SmallString<12> Storage; StringRef Value; if (!parseScalarString(N, Value, Storage)) return None; if (Value.equals_insensitive("fallthrough")) { - return RedirectingFileSystem::RedirectKind::Fallthrough; + return YAMLParseResult::RedirectKind::Fallthrough; } else if (Value.equals_insensitive("fallback")) { - return RedirectingFileSystem::RedirectKind::Fallback; + return YAMLParseResult::RedirectKind::Fallback; } else if (Value.equals_insensitive("redirect-only")) { - return RedirectingFileSystem::RedirectKind::RedirectOnly; + return YAMLParseResult::RedirectKind::RedirectOnly; } return None; } @@ -1642,10 +1668,10 @@ public: static RedirectingFileSystem::Entry * - lookupOrCreateEntry(RedirectingFileSystem *FS, StringRef Name, + lookupOrCreateEntry(RedirectingFileSystem &FS, StringRef Name, RedirectingFileSystem::Entry *ParentEntry = nullptr) { if (!ParentEntry) { // Look for a existent root - for (const auto &Root : FS->Roots) { + for (const auto &Root : FS.Roots) { if (Name.equals(Root->getName())) { ParentEntry = Root.get(); return ParentEntry; @@ -1670,8 +1696,8 @@ file_type::directory_file, sys::fs::all_all)); if (!ParentEntry) { // Add a new root to the overlay - FS->Roots.push_back(std::move(E)); - ParentEntry = FS->Roots.back().get(); + FS.Roots.push_back(std::move(E)); + ParentEntry = FS.Roots.back().get(); return ParentEntry; } @@ -1681,7 +1707,7 @@ } private: - void uniqueOverlayTree(RedirectingFileSystem *FS, + void uniqueOverlayTree(RedirectingFileSystem &FS, RedirectingFileSystem::Entry *SrcE, RedirectingFileSystem::Entry *NewParentE = nullptr) { StringRef Name = SrcE->getName(); @@ -1719,7 +1745,8 @@ } std::unique_ptr - parseEntry(yaml::Node *N, RedirectingFileSystem *FS, bool IsRootEntry) { + parseEntry(yaml::Node *N, bool IsRelativeOverlay, + StringRef ExternalContentsPrefixDir, bool IsRootEntry) { auto *M = dyn_cast(N); if (!M) { error(N, "expected mapping node for file or directory entry"); @@ -1794,7 +1821,8 @@ for (auto &I : *Contents) { if (std::unique_ptr E = - parseEntry(&I, FS, /*IsRootEntry*/ false)) + parseEntry(&I, IsRelativeOverlay, ExternalContentsPrefixDir, + /*IsRootEntry=*/false)) EntryArrayContents.push_back(std::move(E)); else return nullptr; @@ -1810,8 +1838,8 @@ return nullptr; SmallString<256> FullPath; - if (FS->IsRelativeOverlay) { - FullPath = FS->getExternalContentsPrefixDir(); + if (IsRelativeOverlay) { + FullPath.append(ExternalContentsPrefixDir); assert(!FullPath.empty() && "External contents prefix directory must exist"); llvm::sys::path::append(FullPath, Value); @@ -1933,12 +1961,45 @@ public: RedirectingFileSystemParser(yaml::Stream &S) : Stream(S) {} - // false on error - bool parse(yaml::Node *Root, RedirectingFileSystem *FS) { + static Optional + parse(MemoryBufferRef Buffer, SourceMgr &SM, + IntrusiveRefCntPtr ExternalFS) { + yaml::Stream Stream(Buffer, SM); + yaml::document_iterator DI = Stream.begin(); + yaml::Node *Root = DI->getRoot(); + if (DI == Stream.end() || !Root) { + SM.PrintMessage(SMLoc(), SourceMgr::DK_Error, "expected root node"); + return None; + } + + SmallString<256> ExternalContentsPrefixDir; + if (!Buffer.getBufferIdentifier().empty()) { + // Use the YAML path from -ivfsoverlay to compute the dir to be prefixed + // to each 'external-contents' path. + // + // Example: + // -ivfsoverlay dummy.cache/vfs/vfs.yaml + // yields: + // FS->ExternalContentsPrefixDir => //dummy.cache/vfs + // + ExternalContentsPrefixDir = + sys::path::parent_path(Buffer.getBufferIdentifier()); + std::error_code EC = ExternalFS->makeAbsolute(ExternalContentsPrefixDir); + assert(!EC && "Overlay dir final path must be absolute"); + (void)EC; + } + + RedirectingFileSystemParser P(Stream); + return P.parse(Root, ExternalContentsPrefixDir, std::move(ExternalFS)); + } + + Optional parse(yaml::Node *Root, + StringRef ExternalContentsPrefixDir, + IntrusiveRefCntPtr ExternalFS) { auto *Top = dyn_cast(Root); if (!Top) { error(Root, "expected mapping node"); - return false; + return None; } KeyStatusPair Fields[] = { @@ -1954,85 +2015,101 @@ DenseMap Keys(std::begin(Fields), std::end(Fields)); std::vector> RootEntries; + bool IsRelativeOverlay = false; + YAMLParseResult Result{std::unique_ptr( + new RedirectingFileSystem(ExternalFS)), + YAMLParseResult::RedirectKind::Fallthrough}; + if (auto CWD = ExternalFS->getCurrentWorkingDirectory()) + Result.FS->setCurrentWorkingDirectory(*CWD); + // Parse configuration and 'roots' for (auto &I : *Top) { SmallString<10> KeyBuffer; StringRef Key; if (!parseScalarString(I.getKey(), Key, KeyBuffer)) - return false; + return None; if (!checkDuplicateOrUnknownKey(I.getKey(), Key, Keys)) - return false; + return None; if (Key == "roots") { auto *Roots = dyn_cast(I.getValue()); if (!Roots) { error(I.getValue(), "expected array"); - return false; + return None; } for (auto &I : *Roots) { if (std::unique_ptr E = - parseEntry(&I, FS, /*IsRootEntry*/ true)) + parseEntry(&I, IsRelativeOverlay, ExternalContentsPrefixDir, + /*IsRootEntry=*/true)) RootEntries.push_back(std::move(E)); else - return false; + return None; } } else if (Key == "version") { StringRef VersionString; SmallString<4> Storage; if (!parseScalarString(I.getValue(), VersionString, Storage)) - return false; + return None; int Version; if (VersionString.getAsInteger(10, Version)) { error(I.getValue(), "expected integer"); - return false; + return None; } if (Version < 0) { error(I.getValue(), "invalid version number"); - return false; + return None; } if (Version != 0) { error(I.getValue(), "version mismatch, expected 0"); - return false; + return None; } } else if (Key == "case-sensitive") { - if (!parseScalarBool(I.getValue(), FS->CaseSensitive)) - return false; + if (!parseScalarBool(I.getValue(), Result.FS->CaseSensitive)) + return None; } else if (Key == "overlay-relative") { - if (!parseScalarBool(I.getValue(), FS->IsRelativeOverlay)) - return false; + if (!parseScalarBool(I.getValue(), IsRelativeOverlay)) + return None; } else if (Key == "use-external-names") { - if (!parseScalarBool(I.getValue(), FS->UseExternalNames)) - return false; + if (!parseScalarBool(I.getValue(), Result.FS->UseExternalNames)) + return None; } else if (Key == "fallthrough") { if (Keys["redirecting-with"].Seen) { error(I.getValue(), "'fallthrough' and 'redirecting-with' are mutually exclusive"); - return false; + return None; } bool ShouldFallthrough = false; if (!parseScalarBool(I.getValue(), ShouldFallthrough)) - return false; + return None; if (ShouldFallthrough) { - FS->Redirection = RedirectingFileSystem::RedirectKind::Fallthrough; + Result.Redirection = YAMLParseResult::RedirectKind::Fallthrough; + // TODO: Remove after simplifying RedirectingFileSystem + Result.FS->Redirection = + RedirectingFileSystem::RedirectKind::Fallthrough; } else { - FS->Redirection = RedirectingFileSystem::RedirectKind::RedirectOnly; + Result.Redirection = YAMLParseResult::RedirectKind::RedirectOnly; + // TODO: Remove after simplifying RedirectingFileSystem + Result.FS->Redirection = + RedirectingFileSystem::RedirectKind::RedirectOnly; } } else if (Key == "redirecting-with") { if (Keys["fallthrough"].Seen) { error(I.getValue(), "'fallthrough' and 'redirecting-with' are mutually exclusive"); - return false; + return None; } if (auto Kind = parseRedirectKind(I.getValue())) { - FS->Redirection = *Kind; + Result.Redirection = *Kind; + // TODO: Remove after simplifying RedirectingFileSystem + Result.FS->Redirection = YAMLParseResult::toRFSKind(*Kind); } else { error(I.getValue(), "expected valid redirect kind"); - return false; + return None; } } else { llvm_unreachable("key missing from Keys"); @@ -2040,18 +2117,18 @@ } if (Stream.failed()) - return false; + return None; if (!checkMissingKeys(Top, Keys)) - return false; + return None; // Now that we sucessefully parsed the YAML file, canonicalize the internal // representation to a proper directory tree so that we can search faster // inside the VFS. for (auto &E : RootEntries) - uniqueOverlayTree(FS, E.get()); + uniqueOverlayTree(*Result.FS, E.get()); - return true; + return Result; } }; @@ -2061,41 +2138,12 @@ StringRef YAMLFilePath, void *DiagContext, IntrusiveRefCntPtr ExternalFS) { SourceMgr SM; - yaml::Stream Stream(Buffer->getMemBufferRef(), SM); - SM.setDiagHandler(DiagHandler, DiagContext); - yaml::document_iterator DI = Stream.begin(); - yaml::Node *Root = DI->getRoot(); - if (DI == Stream.end() || !Root) { - SM.PrintMessage(SMLoc(), SourceMgr::DK_Error, "expected root node"); - return nullptr; - } - - RedirectingFileSystemParser P(Stream); - - std::unique_ptr FS( - new RedirectingFileSystem(ExternalFS)); - - if (!YAMLFilePath.empty()) { - // Use the YAML path from -ivfsoverlay to compute the dir to be prefixed - // to each 'external-contents' path. - // - // Example: - // -ivfsoverlay dummy.cache/vfs/vfs.yaml - // yields: - // FS->ExternalContentsPrefixDir => //dummy.cache/vfs - // - SmallString<256> OverlayAbsDir = sys::path::parent_path(YAMLFilePath); - std::error_code EC = llvm::sys::fs::make_absolute(OverlayAbsDir); - assert(!EC && "Overlay dir final path must be absolute"); - (void)EC; - FS->setExternalContentsPrefixDir(OverlayAbsDir); - } - - if (!P.parse(Root, FS.get())) - return nullptr; - return FS; + if (Optional Result = RedirectingFileSystemParser::parse( + Buffer->getMemBufferRef(), SM, std::move(ExternalFS))) + return std::move(Result->FS); + return nullptr; } std::unique_ptr RedirectingFileSystem::create( @@ -2128,8 +2176,8 @@ for (auto I = llvm::sys::path::begin(FromDirectory), E = llvm::sys::path::end(FromDirectory); I != E; ++I) { - Parent = RedirectingFileSystemParser::lookupOrCreateEntry(FS.get(), *I, - Parent); + Parent = + RedirectingFileSystemParser::lookupOrCreateEntry(*FS, *I, Parent); } assert(Parent && "File without a directory?"); { @@ -2325,8 +2373,8 @@ : InnerFile(std::move(InnerFile)), S(std::move(S)) {} ErrorOr status() override { return S; } - ErrorOr> + ErrorOr> getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator, bool IsVolatile) override { return InnerFile->getBuffer(Name, FileSize, RequiresNullTerminator, @@ -2471,9 +2519,135 @@ SourceMgr::DiagHandlerTy DiagHandler, StringRef YAMLFilePath, void *DiagContext, IntrusiveRefCntPtr ExternalFS) { - return RedirectingFileSystem::create(std::move(Buffer), DiagHandler, - YAMLFilePath, DiagContext, - std::move(ExternalFS)); + MemoryBufferRef BufferRef{Buffer->getBuffer(), YAMLFilePath}; + auto FS = getVFSFromYAMLs(BufferRef, ExternalFS, DiagHandler, DiagContext); + if (auto Err = FS.takeError()) { + consumeError(std::move(Err)); + return nullptr; + } + return std::move(*FS); +} + +namespace { + +struct VFSResult { + IntrusiveRefCntPtr FS; + YAMLParseResult::RedirectKind Redirection; + + VFSResult(IntrusiveRefCntPtr FS, + YAMLParseResult::RedirectKind Redirection) + : FS(std::move(FS)), Redirection(Redirection) {} + + VFSResult(YAMLParseResult &Result) + : FS(std::move(Result.FS)), Redirection(Result.Redirection) {} +}; + +} // namespace + +static std::unique_ptr +createOverlay(ArrayRef VFSResults) { + if (VFSResults.empty()) + return nullptr; + + auto OverlayFS = std::make_unique(VFSResults.front().FS); + for (const auto &Result : VFSResults.drop_front()) { + OverlayFS->pushOverlay(Result.FS); + } + return OverlayFS; +} + +Expected> +vfs::getVFSFromYAMLs(ArrayRef YAMLOverlayPaths, + IntrusiveRefCntPtr ExternalFS, + SourceMgr::DiagHandlerTy DiagHandler, void *DiagContext) { + SmallVector BufferRefs; + for (StringRef Path : YAMLOverlayPaths) { + BufferRefs.emplace_back(StringRef(), Path); + } + return getVFSFromYAMLs(BufferRefs, std::move(ExternalFS), DiagHandler, + DiagContext); +} + +Expected> +vfs::getVFSFromYAMLs(ArrayRef YAMLOverlays, + IntrusiveRefCntPtr ExternalFS, + SourceMgr::DiagHandlerTy DiagHandler, void *DiagContext) { + if (YAMLOverlays.empty()) + return std::make_unique(std::move(ExternalFS)); + + SourceMgr SM; + SM.setDiagHandler(DiagHandler, DiagContext); + + SmallVector Overlays; + Overlays.emplace_back(ExternalFS, YAMLParseResult::RedirectKind::Fallthrough); + std::unique_ptr OverlayFS = createOverlay(Overlays); + + for (MemoryBufferRef Buffer : YAMLOverlays) { + // Bit of a hack. If the buffer's data is a nullptr (not just empty), + // attempt to read the the file from the current FS. This is to + // differentiate between an actual buffer and one that was made from just + // a path. The paths cannot be read all up front in order to support + // overlay files being defined in a VFS. + std::unique_ptr TempBuffer; + if (Buffer.getBuffer().data() == nullptr) { + ErrorOr> NewBufferOrError = + OverlayFS->getBufferForFile(Buffer.getBufferIdentifier()); + if (!NewBufferOrError) + return createFileError(Buffer.getBufferIdentifier(), + NewBufferOrError.getError()); + TempBuffer = std::move(*NewBufferOrError); + Buffer = MemoryBufferRef(TempBuffer->getBuffer(), + Buffer.getBufferIdentifier()); + } + + Optional Result = + RedirectingFileSystemParser::parse(Buffer, SM, ExternalFS); + if (!Result) + return createFileError(Buffer.getBufferIdentifier(), + llvm::errc::invalid_argument); + + // TODO: Remove after simplifying RedirectingFileSystem + Result->FS->setRedirection( + RedirectingFileSystem::RedirectKind::RedirectOnly); + switch (Result->Redirection) { + case YAMLParseResult::RedirectKind::Fallthrough: + // Simple case - add on the FS + Overlays.emplace_back(*Result); + OverlayFS->pushOverlay(Overlays.back().FS); + break; + case YAMLParseResult::RedirectKind::Fallback: + // Fallback implies that the previously added FS should run operations + // before hitting this FS. This doesn't make a whole lot of sense for any + // position other than the beginning where we want the external FS to run + // first. For other positions the overlays could just be specified in the + // opposite order. + // + // This is fallout from the history of \c RedirectingFileSystem. Ideally + // we would just depend on order and have some way to specify the real + // filesystem. + + if (Overlays.back().Redirection != + YAMLParseResult::RedirectKind::RedirectOnly) { + // If the previously inserted overlay is redirect only, don't bother + // adding this one - it would never run anyway (since redirect only + // is necessarily the last FS). + + // Note: Overlays is never empty, it always has at least the + // \c ExternalFS + Overlays.insert(Overlays.end() - 1, VFSResult(*Result)); + OverlayFS = createOverlay(Overlays); + } + break; + case YAMLParseResult::RedirectKind::RedirectOnly: + // This is now the last FS, clear the rest if there were any + Overlays.clear(); + Overlays.emplace_back(*Result); + OverlayFS = createOverlay(Overlays); + break; + } + } + + return OverlayFS; } static void getVFSEntries(RedirectingFileSystem::Entry *SrcE, diff --git a/llvm/tools/dsymutil/Reproducer.h b/llvm/tools/dsymutil/Reproducer.h --- a/llvm/tools/dsymutil/Reproducer.h +++ b/llvm/tools/dsymutil/Reproducer.h @@ -59,8 +59,7 @@ }; /// Reproducer instance used to use an existing reproducer. The VFS returned by -/// this instance is a RedirectingFileSystem that remaps paths to their -/// counterpart in the reproducer. +/// this instance remaps paths to their counterpart in the reproducer. class ReproducerUse : public Reproducer { public: ReproducerUse(StringRef Root, std::error_code &EC); diff --git a/llvm/tools/dsymutil/Reproducer.cpp b/llvm/tools/dsymutil/Reproducer.cpp --- a/llvm/tools/dsymutil/Reproducer.cpp +++ b/llvm/tools/dsymutil/Reproducer.cpp @@ -48,15 +48,15 @@ ReproducerUse::ReproducerUse(StringRef Root, std::error_code &EC) { SmallString<128> Mapping(Root); sys::path::append(Mapping, "mapping.yaml"); - ErrorOr> Buffer = - vfs::getRealFileSystem()->getBufferForFile(Mapping.str()); - if (!Buffer) { - EC = Buffer.getError(); + auto OverlayFS = llvm::vfs::getVFSFromYAMLs(Mapping.str()); + if (auto Err = OverlayFS.takeError()) { + llvm::handleAllErrors(std::move(Err), [&](const llvm::ErrorInfoBase &E) { + EC = E.convertToErrorCode(); + }); return; } - - VFS = llvm::vfs::getVFSFromYAML(std::move(Buffer.get()), nullptr, Mapping); + VFS = std::move(*OverlayFS); } llvm::Expected> diff --git a/llvm/unittests/Support/VirtualFileSystemTest.cpp b/llvm/unittests/Support/VirtualFileSystemTest.cpp --- a/llvm/unittests/Support/VirtualFileSystemTest.cpp +++ b/llvm/unittests/Support/VirtualFileSystemTest.cpp @@ -14,6 +14,7 @@ #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/SourceMgr.h" +#include "llvm/Testing/Support/Error.h" #include "llvm/Testing/Support/SupportHelpers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -2571,7 +2572,7 @@ } TEST_F(VFSFromYAMLTest, WorkingDirectory) { - IntrusiveRefCntPtr Lower(new DummyFileSystem()); + IntrusiveRefCntPtr Lower(new ErrorDummyFileSystem()); Lower->addDirectory("//root/"); Lower->addDirectory("//root/foo"); Lower->addRegularFile("//root/foo/a"); @@ -2593,6 +2594,7 @@ "}", Lower); ASSERT_NE(FS.get(), nullptr); + std::error_code EC = FS->setCurrentWorkingDirectory("//root/bar"); ASSERT_FALSE(EC); @@ -2621,6 +2623,14 @@ ASSERT_TRUE(WorkingDir); EXPECT_EQ(*WorkingDir, "//root/"); + Status = FS->status("bar/a"); + ASSERT_FALSE(Status.getError()); + EXPECT_TRUE(Status->exists()); + + Status = FS->status("foo/a"); + ASSERT_FALSE(Status.getError()); + EXPECT_TRUE(Status->exists()); + EC = FS->setCurrentWorkingDirectory("bar"); ASSERT_FALSE(EC); WorkingDir = FS->getCurrentWorkingDirectory(); @@ -2710,43 +2720,6 @@ EXPECT_TRUE(Status->exists()); } -TEST_F(VFSFromYAMLTest, WorkingDirectoryFallthroughInvalid) { - IntrusiveRefCntPtr Lower(new ErrorDummyFileSystem()); - Lower->addDirectory("//root/"); - Lower->addDirectory("//root/foo"); - Lower->addRegularFile("//root/foo/a"); - Lower->addRegularFile("//root/foo/b"); - Lower->addRegularFile("//root/c"); - IntrusiveRefCntPtr FS = getFromYAMLString( - "{ 'use-external-names': false,\n" - " 'roots': [\n" - "{\n" - " 'type': 'directory',\n" - " 'name': '//root/bar',\n" - " 'contents': [ {\n" - " 'type': 'file',\n" - " 'name': 'a',\n" - " 'external-contents': '//root/foo/a'\n" - " }\n" - " ]\n" - "}\n" - "]\n" - "}", - Lower); - ASSERT_NE(FS.get(), nullptr); - std::error_code EC = FS->setCurrentWorkingDirectory("//root/"); - ASSERT_FALSE(EC); - ASSERT_NE(FS.get(), nullptr); - - llvm::ErrorOr Status = FS->status("bar/a"); - ASSERT_FALSE(Status.getError()); - EXPECT_TRUE(Status->exists()); - - Status = FS->status("foo/a"); - ASSERT_FALSE(Status.getError()); - EXPECT_TRUE(Status->exists()); -} - TEST_F(VFSFromYAMLTest, VirtualWorkingDirectory) { IntrusiveRefCntPtr Lower(new ErrorDummyFileSystem()); Lower->addDirectory("//root/"); @@ -3207,3 +3180,168 @@ " DummyFileSystem (RecursiveContents)\n", Output); } + +static std::unique_ptr +getVFSOrNull(ArrayRef YAMLOverlays, + IntrusiveRefCntPtr ExternalFS) { + SmallVector OverlayRefs; + for (const auto &Overlay : YAMLOverlays) { + OverlayRefs.emplace_back(Overlay, ""); + } + + auto ExpectedFS = vfs::getVFSFromYAMLs(OverlayRefs, ExternalFS); + if (auto Err = ExpectedFS.takeError()) { + consumeError(std::move(Err)); + return nullptr; + } + return std::move(*ExpectedFS); +} + +static std::string createSimpleOverlay(StringRef RedirectKind, StringRef From, + StringRef To) { + return ("{\n" + " 'version': 0,\n" + " 'redirecting-with': '" + + RedirectKind + + "'\n" + " 'roots': [\n" + " {\n" + " 'type': 'directory-remap',\n" + " 'name': '" + + From + + "',\n" + " 'external-contents': '" + + To + + "',\n" + " }]\n" + " }" + " ]") + .str(); +} + +// Make sure that overlays are not transitive. Given A -> B and B -> C, if a +// file in A is requested, it should not end up mapping to C. +TEST(VFSFromYAMLsTest, NotTransitive) { + auto C = makeIntrusiveRefCnt(); + C->addDirectory("/real/c"); + C->addRegularFile("/real/c/f"); + + SmallVector Overlays; + Overlays.push_back(createSimpleOverlay("fallthrough", "/b", "/real/c")); + Overlays.push_back(createSimpleOverlay("fallthrough", "/a", "/b")); + auto FS = getVFSOrNull(Overlays, C); + ASSERT_TRUE(FS); + + EXPECT_PATHS(*FS, "/b/f", "/real/c/f", "/real/c/f"); + auto S = FS->status("/b/f"); + ASSERT_FALSE(S.getError()); + EXPECT_EQ("/real/c/f", S->getName()); + + EXPECT_TRUE(FS->status("/a/f").getError()); +} + +// When fallback is the first overlay, the external FS should be checked before +// it. Given B -> A, A should only be used if the file does not exist in B. +TEST(VFSFromYAMLsTest, FallbackChecksExternalFirst) { + auto AOnly = makeIntrusiveRefCnt(); + AOnly->addDirectory("/a"); + AOnly->addRegularFile("/a/f"); + + auto BOnly = makeIntrusiveRefCnt(); + BOnly->addDirectory("/b"); + BOnly->addRegularFile("/b/f"); + + auto Both = makeIntrusiveRefCnt(); + Both->addDirectory("/a"); + Both->addRegularFile("/a/f"); + Both->addDirectory("/b"); + Both->addRegularFile("/b/f"); + + auto FallbackOverlay = createSimpleOverlay("fallback", "/b", "/a"); + + auto FS = getVFSOrNull(FallbackOverlay, AOnly); + ASSERT_TRUE(FS); + EXPECT_PATHS(*FS, "/b/f", "/a/f", "/a/f"); + + FS = getVFSOrNull(FallbackOverlay, BOnly); + ASSERT_TRUE(FS); + EXPECT_PATHS(*FS, "/b/f", "/b/f", "/b/f"); + + FS = getVFSOrNull(FallbackOverlay, Both); + ASSERT_TRUE(FS); + EXPECT_PATHS(*FS, "/b/f", "/b/f", "/b/f"); +} + +// Ensure the last overlay with redirect-only specified is the final FS +TEST(VFSFromYAMLsTest, RedirectOnlyIsFinalFS) { + auto Real = makeIntrusiveRefCnt(); + Real->addDirectory("/real"); + Real->addRegularFile("/real/f"); + Real->setCurrentWorkingDirectory("/real"); + + SmallVector Overlays; + Overlays.push_back(createSimpleOverlay("redirect-only", "/ro1", "/real")); + Overlays.push_back(createSimpleOverlay("fallthrough", "/ft1", "/real")); + Overlays.push_back(createSimpleOverlay("redirect-only", "/ro2", "/real")); + Overlays.push_back(createSimpleOverlay("fallback", "/fb", "/real")); + Overlays.push_back(createSimpleOverlay("fallthrough", "/ft2", "/real")); + auto FS = getVFSOrNull(Overlays, Real); + ASSERT_TRUE(FS); + + // Should have the same CWD as the external FS we passed down, even if that + // FS isn't actually being used in the overlay any more. + auto CWD = FS->getCurrentWorkingDirectory(); + ASSERT_FALSE(CWD.getError()); + EXPECT_EQ(*CWD, "/real"); + + // Only //ft2/f and //ro2/f should be valid: + // - //ro1 and //ft1 are specified before //ro2 so never run + // - //fb is specified after, but it's set to fallback and hence //ro2 + // should run first (and not continue) + + EXPECT_TRUE(FS->status("/ro1/f").getError()); + EXPECT_TRUE(FS->status("/ft1/f").getError()); + EXPECT_TRUE(FS->status("/fb/f").getError()); + + EXPECT_PATHS(*FS, "/ro2/f", "/real/f", "/real/f"); + EXPECT_PATHS(*FS, "/ft2/f", "/real/f", "/real/f"); +} + +// Ensure overlays are read from the OverlayFS built so far +TEST(VFSFromYAMLsTest, OverlayFromVFS) { + std::string FT1 = createSimpleOverlay("fallthrough", "/vfs1", "/a"); + std::string FT2 = createSimpleOverlay("fallthrough", "/vfs2", "/b"); + std::string FT3 = createSimpleOverlay("fallthrough", "/vfs3", "/c"); + std::string RO = createSimpleOverlay("redirect-only", "/vfs", "/a"); + + auto Real = makeIntrusiveRefCnt(); + Real->addFile("/ft1.yaml", 0, MemoryBuffer::getMemBuffer(FT1)); + Real->addFile("/ft2.yaml", 0, MemoryBuffer::getMemBuffer(FT2)); + Real->addFile("/a/ft3.yaml", 0, MemoryBuffer::getMemBuffer(FT3)); + Real->addFile("/ro.yaml", 0, MemoryBuffer::getMemBuffer(RO)); + Real->addFile("/a/f", 0, MemoryBuffer::getMemBuffer("a")); + Real->addFile("/b/f", 0, MemoryBuffer::getMemBuffer("b")); + Real->addFile("/c/f", 0, MemoryBuffer::getMemBuffer("c")); + Real->setCurrentWorkingDirectory("/"); + + // 3 to check we're not just using the last overlay + auto ExpectedFS = + vfs::getVFSFromYAMLs({"/ft1.yaml", "/ft2.yaml", "/vfs1/ft3.yaml"}, Real); + ASSERT_THAT_EXPECTED(ExpectedFS, Succeeded()); + auto FS = std::move(*ExpectedFS); + FS->print(llvm::errs(), vfs::FileSystem::PrintType::RecursiveContents); + ASSERT_TRUE(FS); + EXPECT_PATHS(*FS, "/vfs1/f", "/a/f", "/a/f"); + EXPECT_PATHS(*FS, "/vfs2/f", "/b/f", "/b/f"); + EXPECT_PATHS(*FS, "/vfs3/f", "/c/f", "/c/f"); + + // If redirect-only is given, any further overlays *must* be specified by it + ExpectedFS = vfs::getVFSFromYAMLs({"/ro.yaml", "/a/ft3.yaml"}, Real); + EXPECT_THAT_EXPECTED(ExpectedFS, Failed()); + + ExpectedFS = vfs::getVFSFromYAMLs({"/ro.yaml", "/vfs/ft3.yaml"}, Real); + ASSERT_THAT_EXPECTED(ExpectedFS, Succeeded()); + FS = std::move(*ExpectedFS); + ASSERT_TRUE(FS); + EXPECT_PATHS(*FS, "/vfs/f", "/a/f", "/a/f"); +}