Index: clang/lib/Basic/VirtualFileSystem.cpp =================================================================== --- clang/lib/Basic/VirtualFileSystem.cpp +++ clang/lib/Basic/VirtualFileSystem.cpp @@ -905,6 +905,62 @@ std::error_code increment() override; }; +class CombinedFSDirIterImpl : public clang::vfs::detail::DirIterImpl { + FileSystem &ExtraFS; + std::string Path; + bool IsWorkingWithExtraFS; + directory_iterator CurrentDirIter; + llvm::StringSet<> SeenNames; + + std::error_code incrementFS() { + // assert(!IsWorkingWithExtraFS && "incrementing past end"); + if (IsWorkingWithExtraFS) + return {}; + IsWorkingWithExtraFS = true; + std::error_code EC; + CurrentDirIter = (ExtraFS).dir_begin(Path, EC); + if (EC && EC != errc::no_such_file_or_directory) + return EC; + return {}; + } + + std::error_code incrementDirIter(bool IsFirstTime) { + assert((IsFirstTime || CurrentDirIter != directory_iterator()) && + "incrementing past end"); + std::error_code EC; + if (!IsFirstTime) + CurrentDirIter.increment(EC); + if (!EC && CurrentDirIter == directory_iterator()) + EC = incrementFS(); + return EC; + } + + std::error_code incrementImpl(bool IsFirstTime) { + while (true) { + std::error_code EC = incrementDirIter(IsFirstTime); + if (EC || CurrentDirIter == directory_iterator()) { + CurrentEntry = Status(); + return EC; + } + CurrentEntry = *CurrentDirIter; + StringRef Name = llvm::sys::path::filename(CurrentEntry.getName()); + if (SeenNames.insert(Name).second) + return EC; // name not seen before + } + llvm_unreachable("returned above"); + } + +public: + CombinedFSDirIterImpl(const Twine &Path, FileSystem &FS, + directory_iterator DirIter, std::error_code &EC) + : ExtraFS(FS), Path(Path.str()), IsWorkingWithExtraFS(false), + CurrentDirIter(DirIter) { + EC = incrementImpl(true); + } + + std::error_code increment() override { return incrementImpl(false); } +}; + /// A virtual file system parsed from a YAML file. /// /// Currently, this class allows creating virtual directories and mapping @@ -926,6 +982,7 @@ /// 'use-external-names': /// 'overlay-relative': /// 'ignore-non-existent-contents': +/// 'fallthrough': /// /// Virtual directories are represented as /// \verbatim @@ -997,6 +1054,10 @@ /// paths in 'external-contents'. This global value is overridable on a /// per-file basis. bool IgnoreNonExistentContents = true; + + /// Whether to attempt a file lookup in external file system after it wasn't + /// found in VFS. + bool IsFallthrough = true; /// @} /// Virtual file paths and external files could be canonicalized without "..", @@ -1025,6 +1086,8 @@ /// Looks up \p Path in \c Roots. ErrorOr lookupPath(const Twine &Path); + ErrorOr statusImpl(const Twine &Path); + /// Parses \p Buffer, which is expected to be in YAML format and /// returns a virtual file system representing its contents. static RedirectingFileSystem * @@ -1043,7 +1106,7 @@ return ExternalFS->setCurrentWorkingDirectory(Path); } - directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override{ + directory_iterator dir_begin_impl(const Twine &Dir, std::error_code &EC) { ErrorOr E = lookupPath(Dir); if (!E) { EC = E.getError(); @@ -1065,6 +1128,11 @@ *this, D->contents_begin(), D->contents_end(), EC)); } + directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override { + return directory_iterator(std::make_shared( + Dir, *ExternalFS, dir_begin_impl(Dir, EC), EC)); + } + void setExternalContentsPrefixDir(StringRef PrefixDir) { ExternalContentsPrefixDir = PrefixDir.str(); } @@ -1439,6 +1507,7 @@ KeyStatusPair("use-external-names", false), KeyStatusPair("overlay-relative", false), KeyStatusPair("ignore-non-existent-contents", false), + KeyStatusPair("fallthrough", false), KeyStatusPair("roots", true), }; @@ -1498,6 +1567,9 @@ } else if (Key == "ignore-non-existent-contents") { if (!parseScalarBool(I.getValue(), FS->IgnoreNonExistentContents)) return false; + } else if (Key == "fallthrough") { + if (!parseScalarBool(I.getValue(), FS->IsFallthrough)) + return false; } else { llvm_unreachable("key missing from Keys"); } @@ -1661,13 +1733,21 @@ } } -ErrorOr RedirectingFileSystem::status(const Twine &Path) { +ErrorOr RedirectingFileSystem::statusImpl(const Twine &Path) { ErrorOr Result = lookupPath(Path); if (!Result) return Result.getError(); return status(Path, *Result); } +ErrorOr RedirectingFileSystem::status(const Twine &Path) { + ErrorOr Result = statusImpl(Path); + if (IsFallthrough && !Result && + Result.getError() == llvm::errc::no_such_file_or_directory) + return ExternalFS->status(Path); + return Result; +} + namespace { /// Provide a file wrapper with an overriden status. @@ -1696,8 +1776,13 @@ ErrorOr> RedirectingFileSystem::openFileForRead(const Twine &Path) { ErrorOr E = lookupPath(Path); - if (!E) + if (!E) { + if (IsFallthrough && + E.getError() == llvm::errc::no_such_file_or_directory) { + return ExternalFS->openFileForRead(Path); + } return E.getError(); + } auto *F = dyn_cast(*E); if (!F) // FIXME: errc::not_a_file? @@ -1951,7 +2036,7 @@ while (Current != End) { SmallString<128> PathStr(Dir); llvm::sys::path::append(PathStr, (*Current)->getName()); - llvm::ErrorOr S = FS.status(PathStr); + llvm::ErrorOr S = FS.statusImpl(PathStr); if (S) { CurrentEntry = *S; return; @@ -1973,7 +2058,7 @@ while (++Current != End) { SmallString<128> PathStr(Dir); llvm::sys::path::append(PathStr, (*Current)->getName()); - llvm::ErrorOr S = FS.status(PathStr); + llvm::ErrorOr S = FS.statusImpl(PathStr); if (!S) { // Skip entries which do not map to a reliable external content. if (FS.ignoreNonExistentContents() && Index: clang/lib/Frontend/CompilerInvocation.cpp =================================================================== --- clang/lib/Frontend/CompilerInvocation.cpp +++ clang/lib/Frontend/CompilerInvocation.cpp @@ -3223,25 +3223,27 @@ if (CI.getHeaderSearchOpts().VFSOverlayFiles.empty()) return BaseFS; - IntrusiveRefCntPtr Overlay( - new vfs::OverlayFileSystem(BaseFS)); + IntrusiveRefCntPtr Result = BaseFS; // earlier vfs files are on the bottom for (const auto &File : CI.getHeaderSearchOpts().VFSOverlayFiles) { llvm::ErrorOr> Buffer = - BaseFS->getBufferForFile(File); + Result->getBufferForFile(File); if (!Buffer) { Diags.Report(diag::err_missing_vfs_overlay_file) << File; continue; } - IntrusiveRefCntPtr FS = vfs::getVFSFromYAML( - std::move(Buffer.get()), /*DiagHandler*/ nullptr, File); - if (FS) - Overlay->pushOverlay(FS); - else + IntrusiveRefCntPtr FS = + 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; } - return Overlay; + return Result; } } // namespace clang Index: clang/test/VFS/Inputs/vfsroot.yaml =================================================================== --- /dev/null +++ clang/test/VFS/Inputs/vfsroot.yaml @@ -0,0 +1,34 @@ +{ + 'version': 0, + 'ignore-non-existent-contents': false, + 'fallthrough': false, + 'roots': [ + { 'name': '/tests', 'type': 'directory', + 'contents': [ + { 'name': 'vfsroot-include.c', 'type': 'file', + 'external-contents': 'TEST_DIR/vfsroot-include.c' + }, + { 'name': 'vfsroot-with-overlay.c', 'type': 'file', + 'external-contents': 'TEST_DIR/vfsroot-with-overlay.c' + } + ] + }, + { 'name': '/direct-vfs-root-files', 'type': 'directory', + 'contents': [ + { 'name': 'not_real.h', 'type': 'file', + 'external-contents': 'TEST_DIR/Inputs/actual_header.h' + }, + { 'name': 'vfsoverlay.yaml', 'type': 'file', + 'external-contents': 'TEMP_DIR/vfsoverlay.yaml' + } + ] + }, + { 'name': '/indirect-vfs-root-files', 'type': 'directory', + 'contents': [ + { 'name': 'actual_header.h', 'type': 'file', + 'external-contents': 'TEST_DIR/Inputs/actual_header.h' + } + ] + } + ] +} Index: clang/test/VFS/vfsroot-include.c =================================================================== --- /dev/null +++ clang/test/VFS/vfsroot-include.c @@ -0,0 +1,17 @@ +// REQUIRES: shell +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: sed -e "s:TEST_DIR:%S:g" %S/Inputs/vfsroot.yaml > %t.yaml +// RUN: not %clang_cc1 -Werror -ivfsoverlay %t.yaml -I %S/Inputs -I /direct-vfs-root-files -fsyntax-only /tests/vfsroot-include.c 2>&1 | FileCheck %s +// The line above tests that the compiler input file is looked up through VFS. + +// Test successful include through the VFS. +#include "not_real.h" + +// Test that a file missing from the VFS root is not found, even if it is +// discoverable through the real file system. Fatal error should be the last +// in the file as it hides other errors. +#include "actual_header.h" +// CHECK: fatal error: 'actual_header.h' file not found +// CHECK: 1 error generated. +// CHECK-NOT: error Index: clang/test/VFS/vfsroot-with-overlay.c =================================================================== --- /dev/null +++ clang/test/VFS/vfsroot-with-overlay.c @@ -0,0 +1,12 @@ +// REQUIRES: shell +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: sed -e "s:TEST_DIR:%S:g" -e "s:TEMP_DIR:%t: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: %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" + +void foo() { + bar(); +}