simon_tatham created this revision.
Herald added a subscriber: cfe-commits.

This option interposes a wrapper implementation of VirtualFileSystem
in front of the one in the CompilerInstance. The wrapper filesystem
differs from the standard one in that it tolerates backslashes as a
path separator even if the native path syntax doesn't, and also looks
up filenames case-insensitively even if the native file system is
case-sensitive.

This is intended to be useful to anyone compiling source code on Unix
whose authors had never prepared it to be compiled on anything but
Windows, so that it uses backslashes and inconsistent filename case in
its #include directives.

One particular example is if you're running clang-cl as a cross-
compiler, to build a Windows application on a Unix build host; another
is if you're developing a Unix port of a Windows code base in a
downstream clone of their repository. In either case, if you can't get
the upstream authors to take patches that make their #include
directives more portable, you might instead use this option to get
clang to tolerate the non-portable ones.


Repository:
  rC Clang

https://reviews.llvm.org/D48626

Files:
  include/clang/Basic/VirtualFileSystem.h
  include/clang/Driver/Options.td
  include/clang/Lex/HeaderSearchOptions.h
  lib/Basic/VirtualFileSystem.cpp
  lib/Frontend/CompilerInvocation.cpp

Index: lib/Frontend/CompilerInvocation.cpp
===================================================================
--- lib/Frontend/CompilerInvocation.cpp
+++ lib/Frontend/CompilerInvocation.cpp
@@ -1846,6 +1846,9 @@
 
   for (const auto *A : Args.filtered(OPT_ivfsoverlay))
     Opts.AddVFSOverlayFile(A->getValue());
+
+  if (Args.hasArg(OPT_fwindows_filesystem))
+    Opts.WindowsPathnameEmulation = true;
 }
 
 void CompilerInvocation::setLangDefaults(LangOptions &Opts, InputKind IK,
@@ -3212,28 +3215,36 @@
 createVFSFromCompilerInvocation(const CompilerInvocation &CI,
                                 DiagnosticsEngine &Diags,
                                 IntrusiveRefCntPtr<vfs::FileSystem> BaseFS) {
-  if (CI.getHeaderSearchOpts().VFSOverlayFiles.empty())
-    return BaseFS;
-
-  IntrusiveRefCntPtr<vfs::OverlayFileSystem> Overlay(
-      new vfs::OverlayFileSystem(BaseFS));
-  // earlier vfs files are on the bottom
-  for (const auto &File : CI.getHeaderSearchOpts().VFSOverlayFiles) {
-    llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buffer =
+  IntrusiveRefCntPtr<vfs::FileSystem> FS = BaseFS;
+
+  if (!CI.getHeaderSearchOpts().VFSOverlayFiles.empty()) {
+    IntrusiveRefCntPtr<vfs::OverlayFileSystem> Overlay(
+      new vfs::OverlayFileSystem(FS));
+    // 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);
-    if (!Buffer) {
-      Diags.Report(diag::err_missing_vfs_overlay_file) << File;
-      continue;
-    }
+      if (!Buffer) {
+        Diags.Report(diag::err_missing_vfs_overlay_file) << File;
+        continue;
+      }
 
-    IntrusiveRefCntPtr<vfs::FileSystem> FS = vfs::getVFSFromYAML(
+      IntrusiveRefCntPtr<vfs::FileSystem> FS = vfs::getVFSFromYAML(
         std::move(Buffer.get()), /*DiagHandler*/ nullptr, File);
-    if (FS)
-      Overlay->pushOverlay(FS);
-    else
-      Diags.Report(diag::err_invalid_vfs_overlay) << File;
+      if (FS)
+        Overlay->pushOverlay(FS);
+      else
+        Diags.Report(diag::err_invalid_vfs_overlay) << File;
+    }
+
+    FS = std::move(Overlay);
   }
-  return Overlay;
+
+  if (CI.getHeaderSearchOpts().WindowsPathnameEmulation) {
+    FS = createWindowsFileSystemWrapper(std::move(FS));
+  }
+
+  return FS;
 }
 
 } // namespace clang
Index: lib/Basic/VirtualFileSystem.cpp
===================================================================
--- lib/Basic/VirtualFileSystem.cpp
+++ lib/Basic/VirtualFileSystem.cpp
@@ -2024,3 +2024,170 @@
 
   return *this;
 }
+
+//===-----------------------------------------------------------------------===/
+// WindowsFileSystemWrapper implementation
+//===-----------------------------------------------------------------------===/
+
+class WindowsFileSystemWrapper : public vfs::FileSystem {
+  using String = SmallString<256>;
+
+  /// Path name style, used to identify path separators.
+  sys::path::Style PathStyle = sys::path::Style::windows;
+
+  /// The file system we are wrapping.
+  IntrusiveRefCntPtr<FileSystem> BaseFS;
+
+  /// Data about the contents of a single directory.
+  struct DirectoryCaseMap {
+    /// Map whose keys are the lowercase versions of filenames in that
+    /// directory, and values are the version of the same name that
+    /// actually exists.
+    std::map<String, String> Filenames;
+
+    /// Flag indicating whether we've done a complete trawl of the
+    /// directory yet, or whether we've only filled in a subset of
+    /// filenames.
+    bool Complete { false };
+
+    /// If we couldn't even scan this directory due to a filesystem
+    /// error, retain that error so we can return it again on the next
+    /// attempt.
+    std::error_code EC;
+  };
+
+  static String Lowercase(StringRef &Str);
+
+  /// Directories we've explored. Two sets of them, one relative to
+  /// the cwd and one to the filesystem root.
+  using CaseMapCollection = std::map<String, DirectoryCaseMap>;
+  CaseMapCollection AbsCaseMaps, RelCaseMaps;
+
+  /// Translates a path into one acceptable to the base file system.
+  ErrorOr<String> TranslatePath(const Twine &Path);
+
+  ErrorOr<Status> status(const Twine &Path) override {
+    auto TranslatedPath = TranslatePath(Path);
+    if (!TranslatedPath)
+      return TranslatedPath.getError();
+    return BaseFS->status(TranslatedPath.get());
+  }
+
+  ErrorOr<std::unique_ptr<File>> openFileForRead(const Twine &Path) override {
+    auto TranslatedPath = TranslatePath(Path);
+    if (!TranslatedPath)
+      return TranslatedPath.getError();
+    return BaseFS->openFileForRead(TranslatedPath.get());
+  }
+
+  std::error_code setCurrentWorkingDirectory(const Twine &Path) override {
+    auto TranslatedPath = TranslatePath(Path);
+    if (!TranslatedPath)
+      return TranslatedPath.getError();
+    auto ToReturn = BaseFS->setCurrentWorkingDirectory(TranslatedPath.get());
+    if (!ToReturn)
+      RelCaseMaps.clear(); // cwd changed, invalidating the cwd-relative cache
+    return ToReturn;
+  }
+
+  ErrorOr<std::string> getCurrentWorkingDirectory() const override {
+    return BaseFS->getCurrentWorkingDirectory();
+  }
+
+  directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override {
+    auto TranslatedPath = TranslatePath(Dir);
+    if (!TranslatedPath) {
+      EC = TranslatedPath.getError();
+      return directory_iterator();
+    }
+    return BaseFS->dir_begin(TranslatedPath.get(), EC);
+  }
+
+public:
+  WindowsFileSystemWrapper(IntrusiveRefCntPtr<FileSystem> Base)
+      : BaseFS(std::move(Base)) {}
+};
+
+auto WindowsFileSystemWrapper::TranslatePath(const Twine &InPathTw)
+  -> ErrorOr<String>
+{
+  String InPath;
+  InPathTw.toVector(InPath);
+
+  if (sys::path::root_name(InPath, PathStyle) != "")
+    return llvm::errc::no_such_file_or_directory;
+  String OutPath = sys::path::root_path(InPath, PathStyle);
+
+  CaseMapCollection *CMCP;
+  if (OutPath != "") {
+    InPath = relative_path(InPath, PathStyle);
+    CMCP = &AbsCaseMaps;
+  } else {
+    OutPath = ".";
+    CMCP = &RelCaseMaps;
+  }
+  CaseMapCollection &CMC = *CMCP;
+
+  for (sys::path::const_iterator InIter = sys::path::begin(InPath, PathStyle),
+         InEnd = sys::path::end(InPath); InIter != InEnd; ++InIter) {
+    StringRef Component = *InIter;
+    String TranslatedComponent;
+
+    auto &ThisCaseMap = CMC[OutPath];
+    if (ThisCaseMap.EC)
+      return ThisCaseMap.EC;
+
+    String Key = StringRef(StringRef(Component).lower());
+    auto Found = ThisCaseMap.Filenames.find(Key);
+
+    if (Found != ThisCaseMap.Filenames.end()) {
+      TranslatedComponent = Found->second;
+    } else {
+      String TempPath = OutPath;
+      sys::path::append(TempPath, Component);
+      auto Status = BaseFS->status(TempPath);
+      if (Status && Status.get().getType() != file_type::file_not_found) {
+        // This component in its original case refers to a directory
+        // entry that exists, so we don't need to look further.
+        ThisCaseMap.Filenames[Key] = TranslatedComponent = Component;
+      } else {
+        if (!ThisCaseMap.Complete) {
+          // We have to scan the whole containing directory to see if
+          // anything is a case-transformed version of this name.
+
+          ThisCaseMap.Complete = true; // even if we fail, we'll never retry
+
+          auto dir_iter = BaseFS->dir_begin(OutPath, ThisCaseMap.EC);
+          if (ThisCaseMap.EC)
+            return ThisCaseMap.EC;
+
+          std::error_code IncrementEC;
+          for (; dir_iter != directory_iterator();
+               dir_iter.increment(IncrementEC)) {
+            String Name = sys::path::filename(dir_iter->getName());
+            String Key = StringRef(StringRef(Name).lower());
+            ThisCaseMap.Filenames[Key] = Name;
+          }
+
+          // Retry the lookup, now we've fully populated the map.
+          Found = ThisCaseMap.Filenames.find(Key);
+        }
+
+        if (Found == ThisCaseMap.Filenames.end()) {
+          return llvm::errc::no_such_file_or_directory;
+        }
+
+        TranslatedComponent = Found->second;
+      }
+    }
+
+    sys::path::append(OutPath, TranslatedComponent);
+  }
+  return OutPath;
+}
+
+IntrusiveRefCntPtr<vfs::FileSystem>
+vfs::createWindowsFileSystemWrapper(IntrusiveRefCntPtr<vfs::FileSystem> BaseFS)
+{
+  return new WindowsFileSystemWrapper(std::move(BaseFS));
+}
Index: include/clang/Lex/HeaderSearchOptions.h
===================================================================
--- include/clang/Lex/HeaderSearchOptions.h
+++ include/clang/Lex/HeaderSearchOptions.h
@@ -203,6 +203,9 @@
 
   unsigned ModulesHashContent : 1;
 
+  /// Whether we are emulating Windows-style pathnames (-fwindows-filesystem)
+  unsigned WindowsPathnameEmulation : 1;
+
   HeaderSearchOptions(StringRef _Sysroot = "/")
       : Sysroot(_Sysroot), ModuleFormat("raw"), DisableModuleHash(false),
         ImplicitModuleMaps(false), ModuleMapFileHomeIsCwd(false),
Index: include/clang/Driver/Options.td
===================================================================
--- include/clang/Driver/Options.td
+++ include/clang/Driver/Options.td
@@ -1690,6 +1690,9 @@
 def fwhole_program_vtables : Flag<["-"], "fwhole-program-vtables">, Group<f_Group>,
   Flags<[CoreOption, CC1Option]>,
   HelpText<"Enables whole-program vtable optimization. Requires -flto">;
+def fwindows_filesystem : Flag<["-"], "fwindows-filesystem">, Group<f_Group>,
+  Flags<[CoreOption, CC1Option]>,
+  HelpText<"Permits case-insensitive filenames and \\ path separators">;
 def fno_whole_program_vtables : Flag<["-"], "fno-whole-program-vtables">, Group<f_Group>,
   Flags<[CoreOption]>;
 def fforce_emit_vtables : Flag<["-"], "fforce-emit-vtables">, Group<f_Group>,
Index: include/clang/Basic/VirtualFileSystem.h
===================================================================
--- include/clang/Basic/VirtualFileSystem.h
+++ include/clang/Basic/VirtualFileSystem.h
@@ -448,6 +448,12 @@
   void write(llvm::raw_ostream &OS);
 };
 
+/// Wrap an existing filesystem to create one that tolerates
+/// case-incorrect file names and backslash path separators, even if
+/// the underlying filesystem is not Windows-like.
+IntrusiveRefCntPtr<vfs::FileSystem>
+createWindowsFileSystemWrapper(IntrusiveRefCntPtr<vfs::FileSystem> BaseFS);
+
 } // namespace vfs
 } // namespace clang
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to