vsapsai created this revision.
vsapsai added reviewers: bruno, benlangmuir.
Herald added a subscriber: dexonsmith.

Default property value 'true' preserves current behavior. Value 'false' can be
used to create VFS "root", file system that gives better control over which
files compiler can use during compilation as there are no unpredictable
accesses to real file system.

Non-fallthrough use case changes how we treat multiple VFS overlay
files. Instead of all of them being at the same level just above a real
file system, now they are nested and subsequent overlays can refer to
files in previous overlays.

rdar://problem/39465552


https://reviews.llvm.org/D50539

Files:
  clang/lib/Basic/VirtualFileSystem.cpp
  clang/lib/Frontend/CompilerInvocation.cpp
  clang/test/VFS/Inputs/Broken.framework/Headers/Error.h
  clang/test/VFS/Inputs/Broken.framework/Modules/module.modulemap
  clang/test/VFS/Inputs/Broken.framework/VFSHeaders/A.h
  clang/test/VFS/Inputs/vfsroot.yaml
  clang/test/VFS/vfsroot-include.c
  clang/test/VFS/vfsroot-module.m
  clang/test/VFS/vfsroot-with-overlay.c
  clang/unittests/Basic/VirtualFileSystemTest.cpp

Index: clang/unittests/Basic/VirtualFileSystemTest.cpp
===================================================================
--- clang/unittests/Basic/VirtualFileSystemTest.cpp
+++ clang/unittests/Basic/VirtualFileSystemTest.cpp
@@ -1500,3 +1500,89 @@
 
   EXPECT_EQ(3, NumDiagnostics);
 }
+
+TEST_F(VFSFromYAMLTest, NonFallthroughDirectoryIteration) {
+  IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem());
+  Lower->addDirectory("//root/");
+  Lower->addRegularFile("//root/a");
+  Lower->addRegularFile("//root/b");
+  IntrusiveRefCntPtr<vfs::FileSystem> FS = getFromYAMLString(
+      "{ 'use-external-names': false,\n"
+      "  'fallthrough': false,\n"
+      "  'roots': [\n"
+      "{\n"
+      "  'type': 'directory',\n"
+      "  'name': '//root/',\n"
+      "  'contents': [ {\n"
+      "                  'type': 'file',\n"
+      "                  'name': 'c',\n"
+      "                  'external-contents': '//root/a'\n"
+      "                }\n"
+      "              ]\n"
+      "}\n"
+      "]\n"
+      "}",
+      Lower);
+  ASSERT_TRUE(FS.get() != nullptr);
+
+  std::error_code EC;
+  checkContents(FS->dir_begin("//root/", EC),
+                {"//root/c"});
+}
+
+TEST_F(VFSFromYAMLTest, DirectoryIterationWithDuplicates) {
+  IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem());
+  Lower->addDirectory("//root/");
+  Lower->addRegularFile("//root/a");
+  Lower->addRegularFile("//root/b");
+  IntrusiveRefCntPtr<vfs::FileSystem> FS = getFromYAMLString(
+      "{ 'use-external-names': false,\n"
+      "  'roots': [\n"
+      "{\n"
+      "  'type': 'directory',\n"
+      "  'name': '//root/',\n"
+      "  'contents': [ {\n"
+      "                  'type': 'file',\n"
+      "                  'name': 'a',\n"
+      "                  'external-contents': '//root/a'\n"
+      "                }\n"
+      "              ]\n"
+      "}\n"
+      "]\n"
+      "}",
+	  Lower);
+  ASSERT_TRUE(FS.get() != nullptr);
+
+  std::error_code EC;
+  checkContents(FS->dir_begin("//root/", EC),
+                {"//root/a", "//root/b"});
+}
+
+TEST_F(VFSFromYAMLTest, DirectoryIterationErrorInVFSLayer) {
+  IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem());
+  Lower->addDirectory("//root/");
+  Lower->addDirectory("//root/foo");
+  Lower->addRegularFile("//root/foo/a");
+  Lower->addRegularFile("//root/foo/b");
+  IntrusiveRefCntPtr<vfs::FileSystem> FS = getFromYAMLString(
+      "{ 'use-external-names': false,\n"
+      "  'roots': [\n"
+      "{\n"
+      "  'type': 'directory',\n"
+      "  'name': '//root/',\n"
+      "  'contents': [ {\n"
+      "                  'type': 'file',\n"
+      "                  'name': 'bar/a',\n"
+      "                  'external-contents': '//root/foo/a'\n"
+      "                }\n"
+      "              ]\n"
+      "}\n"
+      "]\n"
+      "}",
+      Lower);
+  ASSERT_TRUE(FS.get() != nullptr);
+
+  std::error_code EC;
+  checkContents(FS->dir_begin("//root/foo", EC),
+                {"//root/foo/a", "//root/foo/b"});
+}
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:OUT_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();
+}
Index: clang/test/VFS/vfsroot-module.m
===================================================================
--- /dev/null
+++ clang/test/VFS/vfsroot-module.m
@@ -0,0 +1,10 @@
+// REQUIRES: shell
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: sed -e "s:TEST_DIR:%S:g" -e "s:OUT_DIR:%t:g" %S/Inputs/vfsroot.yaml > %t.yaml
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fdisable-module-hash -fmodules-cache-path=%t/cache -ivfsoverlay %t.yaml -F %S/Inputs -fsyntax-only /tests/vfsroot-module.m
+
+// Test that a file missing from the VFS root is not found, even if it is
+// discoverable through the real file system at location that is a part of
+// the framework.
+@import Broken;
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" -e "s:OUT_DIR:%t: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/Inputs/vfsroot.yaml
===================================================================
--- /dev/null
+++ clang/test/VFS/Inputs/vfsroot.yaml
@@ -0,0 +1,56 @@
+{
+  'version': 0,
+  'ignore-non-existent-contents': false,
+  'use-external-names': 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': 'vfsroot-module.m', 'type': 'file',
+          'external-contents': 'TEST_DIR/vfsroot-module.m'
+        }
+      ]
+    },
+    { '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': 'OUT_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'
+        }
+      ]
+    },
+    { 'name': 'TEST_DIR/Inputs/Broken.framework', 'type': 'directory',
+      'contents': [
+        { 'name': 'Headers/A.h', 'type': 'file',
+          'external-contents': 'TEST_DIR/Inputs/Broken.framework/VFSHeaders/A.h'
+        },
+        { 'name': 'Modules/module.modulemap', 'type': 'file',
+          'external-contents': 'TEST_DIR/Inputs/Broken.framework/Modules/module.modulemap'
+        }
+      ]
+    },
+    # Locations for modules.
+    { 'name': 'OUT_DIR/cache', 'type': 'directory',
+      'contents': [
+        { 'name': 'Broken.pcm', 'type': 'file',
+          'external-contents': 'OUT_DIR/cache/Broken.pcm'
+        }
+      ]
+    }
+  ]
+}
Index: clang/test/VFS/Inputs/Broken.framework/VFSHeaders/A.h
===================================================================
--- /dev/null
+++ clang/test/VFS/Inputs/Broken.framework/VFSHeaders/A.h
@@ -0,0 +1 @@
+// A.h
Index: clang/test/VFS/Inputs/Broken.framework/Modules/module.modulemap
===================================================================
--- /dev/null
+++ clang/test/VFS/Inputs/Broken.framework/Modules/module.modulemap
@@ -0,0 +1,6 @@
+framework module Broken [extern_c] {
+  umbrella "Headers"
+  export *
+  module * { export * }
+}
+
Index: clang/test/VFS/Inputs/Broken.framework/Headers/Error.h
===================================================================
--- /dev/null
+++ clang/test/VFS/Inputs/Broken.framework/Headers/Error.h
@@ -0,0 +1,3 @@
+// Error.h
+
+#error Should not include this header in a module
Index: clang/lib/Frontend/CompilerInvocation.cpp
===================================================================
--- clang/lib/Frontend/CompilerInvocation.cpp
+++ clang/lib/Frontend/CompilerInvocation.cpp
@@ -3222,25 +3222,27 @@
   if (CI.getHeaderSearchOpts().VFSOverlayFiles.empty())
     return BaseFS;
 
-  IntrusiveRefCntPtr<vfs::OverlayFileSystem> Overlay(
-      new vfs::OverlayFileSystem(BaseFS));
+  IntrusiveRefCntPtr<vfs::FileSystem> Result = BaseFS;
   // earlier vfs files are on the bottom
   for (const auto &File : CI.getHeaderSearchOpts().VFSOverlayFiles) {
     llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buffer =
-        BaseFS->getBufferForFile(File);
+        Result->getBufferForFile(File);
     if (!Buffer) {
       Diags.Report(diag::err_missing_vfs_overlay_file) << File;
       continue;
     }
 
-    IntrusiveRefCntPtr<vfs::FileSystem> FS = vfs::getVFSFromYAML(
-        std::move(Buffer.get()), /*DiagHandler*/ nullptr, File);
-    if (FS)
-      Overlay->pushOverlay(FS);
-    else
+    IntrusiveRefCntPtr<vfs::FileSystem> 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/lib/Basic/VirtualFileSystem.cpp
===================================================================
--- clang/lib/Basic/VirtualFileSystem.cpp
+++ clang/lib/Basic/VirtualFileSystem.cpp
@@ -932,14 +932,20 @@
   std::string Dir;
   RedirectingFileSystem &FS;
   RedirectingDirectoryEntry::iterator Current, End;
+  bool IsExternalFSCurrent;
+  FileSystem &ExternalFS;
+  directory_iterator ExternalDirIter;
+  llvm::StringSet<> SeenNames;
 
-  std::error_code incrementImpl();
+  std::error_code incrementExternal();
+  std::error_code incrementContent(bool IsFirstTime);
+  std::error_code incrementImpl(bool IsFirstTime);
 
 public:
   VFSFromYamlDirIterImpl(const Twine &Path, RedirectingFileSystem &FS,
                          RedirectingDirectoryEntry::iterator Begin,
                          RedirectingDirectoryEntry::iterator End,
-                         std::error_code &EC);
+                         FileSystem &ExternalFS, std::error_code &EC);
 
   std::error_code increment() override;
 };
@@ -965,6 +971,7 @@
 ///   'use-external-names': <boolean, default=true>
 ///   'overlay-relative': <boolean, default=false>
 ///   'ignore-non-existent-contents': <boolean, default=true>
+///   'fallthrough': <boolean, default=true>
 ///
 /// Virtual directories are represented as
 /// \verbatim
@@ -1036,6 +1043,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 "..",
@@ -1063,6 +1074,7 @@
 public:
   /// Looks up \p Path in \c Roots.
   ErrorOr<Entry *> lookupPath(const Twine &Path);
+  ErrorOr<Status> status(const Twine &Path, bool ShouldCheckExternalFS);
 
   /// Parses \p Buffer, which is expected to be in YAML format and
   /// returns a virtual file system representing its contents.
@@ -1082,10 +1094,12 @@
     return ExternalFS->setCurrentWorkingDirectory(Path);
   }
 
-  directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override{
+  directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override {
     ErrorOr<Entry *> E = lookupPath(Dir);
     if (!E) {
       EC = E.getError();
+      if (IsFallthrough && EC == errc::no_such_file_or_directory)
+        return ExternalFS->dir_begin(Dir, EC);
       return {};
     }
     ErrorOr<Status> S = status(Dir, *E);
@@ -1100,8 +1114,8 @@
     }
 
     auto *D = cast<RedirectingDirectoryEntry>(*E);
-    return directory_iterator(std::make_shared<VFSFromYamlDirIterImpl>(Dir,
-        *this, D->contents_begin(), D->contents_end(), EC));
+    return directory_iterator(std::make_shared<VFSFromYamlDirIterImpl>(
+        Dir, *this, D->contents_begin(), D->contents_end(), *ExternalFS, EC));
   }
 
   void setExternalContentsPrefixDir(StringRef PrefixDir) {
@@ -1116,6 +1130,10 @@
     return IgnoreNonExistentContents;
   }
 
+  bool isFallthrough() const {
+    return IsFallthrough;
+  }
+
 #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
 LLVM_DUMP_METHOD void dump() const {
     for (const auto &Root : Roots)
@@ -1489,6 +1507,7 @@
       KeyStatusPair("use-external-names", false),
       KeyStatusPair("overlay-relative", false),
       KeyStatusPair("ignore-non-existent-contents", false),
+      KeyStatusPair("fallthrough", false),
       KeyStatusPair("roots", true),
     };
 
@@ -1549,6 +1568,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");
       }
@@ -1712,13 +1734,23 @@
   }
 }
 
-ErrorOr<Status> RedirectingFileSystem::status(const Twine &Path) {
+ErrorOr<Status> RedirectingFileSystem::status(const Twine &Path,
+                                              bool ShouldCheckExternalFS) {
   ErrorOr<Entry *> Result = lookupPath(Path);
-  if (!Result)
+  if (!Result) {
+    if (ShouldCheckExternalFS &&
+        Result.getError() == llvm::errc::no_such_file_or_directory) {
+      return ExternalFS->status(Path);
+    }
     return Result.getError();
+  }
   return status(Path, *Result);
 }
 
+ErrorOr<Status> RedirectingFileSystem::status(const Twine &Path) {
+  return status(Path, IsFallthrough);
+}
+
 namespace {
 
 /// Provide a file wrapper with an overriden status.
@@ -1747,8 +1779,13 @@
 ErrorOr<std::unique_ptr<File>>
 RedirectingFileSystem::openFileForRead(const Twine &Path) {
   ErrorOr<Entry *> 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<RedirectingFileEntry>(*E);
   if (!F) // FIXME: errc::not_a_file?
@@ -1997,22 +2034,46 @@
 VFSFromYamlDirIterImpl::VFSFromYamlDirIterImpl(
     const Twine &_Path, RedirectingFileSystem &FS,
     RedirectingDirectoryEntry::iterator Begin,
-    RedirectingDirectoryEntry::iterator End, std::error_code &EC)
-    : Dir(_Path.str()), FS(FS), Current(Begin), End(End) {
-  EC = incrementImpl();
+    RedirectingDirectoryEntry::iterator End, FileSystem &ExternalFS,
+    std::error_code &EC)
+    : Dir(_Path.str()), FS(FS), Current(Begin), End(End),
+      IsExternalFSCurrent(false), ExternalFS(ExternalFS) {
+  EC = incrementImpl(true);
 }
 
 std::error_code VFSFromYamlDirIterImpl::increment() {
-  assert(Current != End && "cannot iterate past end");
-  ++Current;
-  return incrementImpl();
+  return incrementImpl(false);
+}
+
+std::error_code VFSFromYamlDirIterImpl::incrementExternal() {
+  assert(!(IsExternalFSCurrent && ExternalDirIter == directory_iterator()) &&
+         "incrementing past end");
+  std::error_code EC;
+  if (IsExternalFSCurrent) {
+    ExternalDirIter.increment(EC);
+  } else if (FS.isFallthrough()) {
+    ExternalDirIter = ExternalFS.dir_begin(Dir, EC);
+    IsExternalFSCurrent = true;
+    if (EC && EC != errc::no_such_file_or_directory)
+      return EC;
+    EC = {};
+  }
+  if (EC || ExternalDirIter == directory_iterator()) {
+    CurrentEntry = Status();
+  } else {
+    CurrentEntry = *ExternalDirIter;
+  }
+  return EC;
 }
 
-std::error_code VFSFromYamlDirIterImpl::incrementImpl() {
+std::error_code VFSFromYamlDirIterImpl::incrementContent(bool IsFirstTime) {
+  assert(IsFirstTime || Current != End && "cannot iterate past end");
+  if (!IsFirstTime)
+    ++Current;
   while (Current != End) {
     SmallString<128> PathStr(Dir);
     llvm::sys::path::append(PathStr, (*Current)->getName());
-    llvm::ErrorOr<vfs::Status> S = FS.status(PathStr);
+    llvm::ErrorOr<vfs::Status> S = FS.status(PathStr, false);
     if (!S) {
       // Skip entries which do not map to a reliable external content.
       if (FS.ignoreNonExistentContents() &&
@@ -2024,12 +2085,22 @@
       }
     }
     CurrentEntry = *S;
-    break;
+    return {};
   }
+  return incrementExternal();
+}
 
-  if (Current == End)
-    CurrentEntry = Status();
-  return {};
+std::error_code VFSFromYamlDirIterImpl::incrementImpl(bool IsFirstTime) {
+  while (true) {
+    std::error_code EC = IsExternalFSCurrent ? incrementExternal()
+                                             : incrementContent(IsFirstTime);
+    if (EC || !CurrentEntry.isStatusKnown())
+      return EC;
+    StringRef Name = llvm::sys::path::filename(CurrentEntry.getName());
+    if (SeenNames.insert(Name).second)
+      return EC; // name not seen before
+  }
+  llvm_unreachable("returned above");
 }
 
 vfs::recursive_directory_iterator::recursive_directory_iterator(FileSystem &FS_,
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D50539: [VFS] Add... Volodymyr Sapsai via Phabricator via cfe-commits

Reply via email to