cameron314 updated the summary for this revision.
cameron314 removed rL LLVM as the repository for this revision.
cameron314 updated this revision to Diff 59577.
cameron314 added a comment.

It took some modifications to the ASTUnit to support a virtual file system with 
a PCH parse/reparse (preliminary VFS support had already been added in 
http://reviews.llvm.org/rL249410 but it did not support initial parses using 
PCHs, nor reparses), but I was finally able to write a test that checks that 
the reparse actually uses the PCH with my fix, and rejects the PCH (rereading 
everything and failing the test) without it.


http://reviews.llvm.org/D20338

Files:
  include/clang/Basic/FileManager.h
  include/clang/Frontend/ASTUnit.h
  lib/Basic/FileManager.cpp
  lib/Frontend/ASTUnit.cpp
  unittests/Frontend/CMakeLists.txt
  unittests/Frontend/PchPreambleTest.cpp

Index: unittests/Frontend/PchPreambleTest.cpp
===================================================================
--- /dev/null
+++ unittests/Frontend/PchPreambleTest.cpp
@@ -0,0 +1,155 @@
+//====-- unittests/Frontend/PchPreambleTest.cpp - FrontendAction tests ---====//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Frontend/CompilerInvocation.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Frontend/FrontendOptions.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/FileManager.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "gtest/gtest.h"
+#include <fstream>
+
+using namespace llvm;
+using namespace clang;
+
+namespace {
+
+class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem
+{
+  std::map<std::string, unsigned> ReadCounts;
+
+public:
+  ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override
+  {
+    SmallVector<char, 128> PathVec;
+    Path.toVector(PathVec);
+    llvm::sys::path::remove_dots(PathVec, true);
+    ++ReadCounts[std::string(PathVec.begin(), PathVec.end())];
+    return InMemoryFileSystem::openFileForRead(Path);
+  }
+
+  unsigned GetReadCount(const Twine &Path) const
+  {
+    auto it = ReadCounts.find(Path.str());
+    return it == ReadCounts.end() ? 0 : it->second;
+  }
+};
+
+class PchPreambleTest : public ::testing::Test {
+  IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS;
+  StringMap<std::string> RemappedFiles;
+  std::shared_ptr<PCHContainerOperations> PCHContainerOpts;
+  FileSystemOptions FSOpts;
+
+public:
+  void SetUp() override {
+    VFS = new ReadCountingInMemoryFileSystem();
+    // We need the working directory to be set to something absolute,
+    // otherwise it ends up being inadvertently set to the current
+    // working directory in the real file system due to a series of
+    // unfortunate conditions interacting badly.
+    // What's more, this path *must* be absolute on all (real)
+    // filesystems, so just '/' won't work (e.g. on Win32).
+    VFS->setCurrentWorkingDirectory("//./");
+  }
+
+  void TearDown() override {
+  }
+
+  void AddFile(const std::string &Filename, const std::string &Contents) {
+    ::time_t now;
+    ::time(&now);
+    VFS->addFile(Filename, now, MemoryBuffer::getMemBufferCopy(Contents, Filename));
+  }
+
+  void RemapFile(const std::string &Filename, const std::string &Contents) {
+    RemappedFiles[Filename] = Contents;
+  }
+
+  std::unique_ptr<ASTUnit> ParseAST(const std::string &EntryFile) {
+    PCHContainerOpts = std::make_shared<PCHContainerOperations>();
+    CompilerInvocation *CI = new CompilerInvocation;
+    CI->getFrontendOpts().Inputs.push_back(
+      FrontendInputFile(EntryFile, FrontendOptions::getInputKindForExtension(
+        llvm::sys::path::extension(EntryFile).substr(1))));
+
+    CI->getTargetOpts().Triple = "i386-unknown-linux-gnu";
+
+    CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles();
+
+    PreprocessorOptions &PPOpts = CI->getPreprocessorOpts();
+    PPOpts.RemappedFilesKeepOriginalName = true;
+
+    IntrusiveRefCntPtr<DiagnosticsEngine>
+      Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions));
+
+    FileManager *FileMgr = new FileManager(FSOpts, VFS);
+
+    std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
+      CI, PCHContainerOpts, Diags, FileMgr, false, false,
+      /*PrecompilePreambleAfterNParses=*/1);
+    return AST;
+  }
+
+  bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) {
+    FileManager *FileMgr = new FileManager(FSOpts, VFS);
+    bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), FileMgr);
+    return reparseFailed;
+  }
+
+  unsigned GetFileReadCount(const std::string &Filename) const {
+    return VFS->GetReadCount(Filename);
+  }
+
+private:
+  std::vector<std::pair<std::string, llvm::MemoryBuffer *>>
+  GetRemappedFiles() const {
+    std::vector<std::pair<std::string, llvm::MemoryBuffer *>> Remapped;
+    for (const auto &RemappedFile : RemappedFiles) {
+      std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy(
+        RemappedFile.second, RemappedFile.first());
+      Remapped.emplace_back(RemappedFile.first(), buf.release());
+    }
+    return Remapped;
+  }
+};
+
+TEST_F(PchPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) {
+  std::string Header1 = "//./header1.h";
+  std::string Header2 = "//./header2.h";
+  std::string MainName = "//./main.cpp";
+  AddFile(Header1, "");
+  AddFile(Header2, "#pragma once");
+  AddFile(MainName,
+    "#include \"//./foo/../header1.h\"\n"
+    "#include \"//./foo/../header2.h\"\n"
+    "int main() { return foo(); }");
+  RemapFile(Header1, "inline int foo2() { return 0; }");
+
+  std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
+  ASSERT_TRUE(AST.get());
+
+  unsigned initialCounts[] = {
+    GetFileReadCount(MainName),
+    GetFileReadCount(Header1),
+    GetFileReadCount(Header2)
+  };
+
+  ASSERT_FALSE(ReparseAST(AST));
+
+  ASSERT_NE(initialCounts[0], GetFileReadCount(MainName));
+  ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1));
+  ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2));
+}
+
+} // anonymous namespace
Index: unittests/Frontend/CMakeLists.txt
===================================================================
--- unittests/Frontend/CMakeLists.txt
+++ unittests/Frontend/CMakeLists.txt
@@ -5,6 +5,7 @@
 add_clang_unittest(FrontendTests
   FrontendActionTest.cpp
   CodeGenActionTest.cpp
+  PchPreambleTest.cpp
   )
 target_link_libraries(FrontendTests
   clangAST
Index: lib/Frontend/ASTUnit.cpp
===================================================================
--- lib/Frontend/ASTUnit.cpp
+++ lib/Frontend/ASTUnit.cpp
@@ -1032,7 +1032,8 @@
 /// \returns True if a failure occurred that causes the ASTUnit not to
 /// contain any translation-unit information, false otherwise.
 bool ASTUnit::Parse(std::shared_ptr<PCHContainerOperations> PCHContainerOps,
-                    std::unique_ptr<llvm::MemoryBuffer> OverrideMainBuffer) {
+                    std::unique_ptr<llvm::MemoryBuffer> OverrideMainBuffer,
+                    std::unique_ptr<FileManager> PCHFileMgr) {
   SavedMainFileBuffer.reset();
 
   if (!Invocation)
@@ -1078,6 +1079,8 @@
   // Configure the various subsystems.
   LangOpts = Clang->getInvocation().LangOpts;
   FileSystemOpts = Clang->getFileSystemOpts();
+  if (PCHFileMgr)
+    FileMgr = PCHFileMgr.release();
   if (!FileMgr) {
     Clang->createFileManager();
     FileMgr = &Clang->getFileManager();
@@ -1316,6 +1319,23 @@
   return OutDiag;
 }
 
+static IntrusiveRefCntPtr<vfs::FileSystem> createPreambleVFSOverlay(
+    IntrusiveRefCntPtr<vfs::FileSystem> RawPreambleVFS,
+    IntrusiveRefCntPtr<vfs::FileSystem> CustomVFS) {
+  IntrusiveRefCntPtr<vfs::OverlayFileSystem>
+    Overlay(new vfs::OverlayFileSystem(RawPreambleVFS));
+  llvm::ErrorOr<std::string> PrevWorkingDir
+      = CustomVFS->getCurrentWorkingDirectory();
+  Overlay->pushOverlay(CustomVFS);
+  // pushOverlay changes the working directory, but we can't assume that the
+  // two filesystems are compatible that way. We only underlay the other
+  // filesystem so that the PCH file can be written/read from it anyway,
+  // and for that we use an absolute path in the first place.
+  if (PrevWorkingDir)
+    CustomVFS->setCurrentWorkingDirectory(PrevWorkingDir.get());
+  return Overlay;
+}
+
 /// \brief Attempt to build or re-use a precompiled preamble when (re-)parsing
 /// the source file.
 ///
@@ -1339,8 +1359,9 @@
 std::unique_ptr<llvm::MemoryBuffer>
 ASTUnit::getMainBufferWithPrecompiledPreamble(
     std::shared_ptr<PCHContainerOperations> PCHContainerOps,
-    const CompilerInvocation &PreambleInvocationIn, bool AllowRebuild,
-    unsigned MaxLines) {
+    const CompilerInvocation &PreambleInvocationIn,
+    std::unique_ptr<FileManager> &PCHFileMgr,
+    bool AllowRebuild, unsigned MaxLines) {
 
   IntrusiveRefCntPtr<CompilerInvocation>
     PreambleInvocation(new CompilerInvocation(PreambleInvocationIn));
@@ -1448,6 +1469,11 @@
                               PreambleInvocation->getDiagnosticOpts());
         getDiagnostics().setNumWarnings(NumWarningsInPreamble);
 
+        IntrusiveRefCntPtr<vfs::FileSystem> VFS = vfs::getRealFileSystem();
+        if (VFS && FileMgr && FileMgr->getVirtualFileSystem() != VFS)
+          PCHFileMgr.reset(new FileManager(FileSystemOpts,
+              createPreambleVFSOverlay(VFS, FileMgr->getVirtualFileSystem())));
+
         return llvm::MemoryBuffer::getMemBufferCopy(
             NewPreamble.Buffer->getBuffer(), FrontendOpts.Inputs[0].getFile());
       }
@@ -1558,12 +1584,14 @@
   TopLevelDeclsInPreamble.clear();
   PreambleDiagnostics.clear();
 
-  IntrusiveRefCntPtr<vfs::FileSystem> VFS =
-      createVFSFromCompilerInvocation(Clang->getInvocation(), getDiagnostics());
+  // Create a file manager object to provide access to and cache the filesystem.
+  IntrusiveRefCntPtr<vfs::FileSystem> VFS
+      = createVFSFromCompilerInvocation(Clang->getInvocation(), getDiagnostics());
   if (!VFS)
     return nullptr;
-
-  // Create a file manager object to provide access to and cache the filesystem.
+  if (FileMgr && FileMgr->getVirtualFileSystem() != VFS)
+    VFS = createPreambleVFSOverlay(VFS, FileMgr->getVirtualFileSystem());
+  PCHFileMgr.reset(new FileManager(Clang->getFileSystemOpts(), VFS));
   Clang->setFileManager(new FileManager(Clang->getFileSystemOpts(), VFS));
   
   // Create the source manager.
@@ -1883,10 +1911,12 @@
   ProcessWarningOptions(getDiagnostics(), Invocation->getDiagnosticOpts());
 
   std::unique_ptr<llvm::MemoryBuffer> OverrideMainBuffer;
+  std::unique_ptr<FileManager> PCHFileMgr;
   if (PrecompilePreambleAfterNParses > 0) {
     PreambleRebuildCounter = PrecompilePreambleAfterNParses;
     OverrideMainBuffer =
-        getMainBufferWithPrecompiledPreamble(PCHContainerOps, *Invocation);
+        getMainBufferWithPrecompiledPreamble(PCHContainerOps, *Invocation,
+                                             PCHFileMgr);
   }
   
   SimpleTimer ParsingTimer(WantTiming);
@@ -1896,7 +1926,8 @@
   llvm::CrashRecoveryContextCleanupRegistrar<llvm::MemoryBuffer>
     MemBufferCleanup(OverrideMainBuffer.get());
 
-  return Parse(PCHContainerOps, std::move(OverrideMainBuffer));
+  return Parse(PCHContainerOps, std::move(OverrideMainBuffer),
+               std::move(PCHFileMgr));
 }
 
 std::unique_ptr<ASTUnit> ASTUnit::LoadFromCompilerInvocation(
@@ -2027,7 +2058,8 @@
 }
 
 bool ASTUnit::Reparse(std::shared_ptr<PCHContainerOperations> PCHContainerOps,
-                      ArrayRef<RemappedFile> RemappedFiles) {
+                      ArrayRef<RemappedFile> RemappedFiles,
+                      FileManager *FileMgr) {
   if (!Invocation)
     return true;
 
@@ -2049,13 +2081,18 @@
 
   // If we have a preamble file lying around, or if we might try to
   // build a precompiled preamble, do so now.
+  if (FileMgr)
+    this->FileMgr = FileMgr;
   std::unique_ptr<llvm::MemoryBuffer> OverrideMainBuffer;
+  std::unique_ptr<FileManager> PCHFileMgr;
   if (!getPreambleFile(this).empty() || PreambleRebuildCounter > 0)
     OverrideMainBuffer =
-        getMainBufferWithPrecompiledPreamble(PCHContainerOps, *Invocation);
+        getMainBufferWithPrecompiledPreamble(PCHContainerOps, *Invocation,
+                                             PCHFileMgr);
+  if (PCHFileMgr)
+    this->FileMgr = PCHFileMgr.release();
 
   // Clear out the diagnostics state.
-  FileMgr.reset();
   getDiagnostics().Reset();
   ProcessWarningOptions(getDiagnostics(), Invocation->getDiagnosticOpts());
   if (OverrideMainBuffer)
@@ -2424,10 +2461,11 @@
     if (!llvm::sys::fs::getUniqueID(CompleteFilePath, CompleteFileID)) {
       std::string MainPath(OriginalSourceFile);
       llvm::sys::fs::UniqueID MainID;
+      std::unique_ptr<FileManager> PCHFileMgr;
       if (!llvm::sys::fs::getUniqueID(MainPath, MainID)) {
         if (CompleteFileID == MainID && Line > 1)
           OverrideMainBuffer = getMainBufferWithPrecompiledPreamble(
-              PCHContainerOps, *CCInvocation, false, Line - 1);
+              PCHContainerOps, *CCInvocation, PCHFileMgr, false, Line - 1);
       }
     }
   }
Index: lib/Basic/FileManager.cpp
===================================================================
--- lib/Basic/FileManager.cpp
+++ lib/Basic/FileManager.cpp
@@ -301,6 +301,11 @@
     return &UFE;
   }
 
+  if (UFE.isVirtual()) {
+    UFE.Name = InterndFileName;
+    return &UFE;
+  }
+
   // Otherwise, we don't have this file yet, add it.
   UFE.Name    = InterndFileName;
   UFE.Size = Data.Size;
@@ -381,6 +386,7 @@
   UFE->Dir     = DirInfo;
   UFE->UID     = NextFileUID++;
   UFE->File.reset();
+  UFE->IsVirtual = true;
   return UFE;
 }
 
Index: include/clang/Frontend/ASTUnit.h
===================================================================
--- include/clang/Frontend/ASTUnit.h
+++ include/clang/Frontend/ASTUnit.h
@@ -425,7 +425,8 @@
 
   void CleanTemporaryFiles();
   bool Parse(std::shared_ptr<PCHContainerOperations> PCHContainerOps,
-             std::unique_ptr<llvm::MemoryBuffer> OverrideMainBuffer);
+             std::unique_ptr<llvm::MemoryBuffer> OverrideMainBuffer,
+             std::unique_ptr<FileManager> PCHFileMgr = nullptr);
 
   struct ComputedPreamble {
     llvm::MemoryBuffer *Buffer;
@@ -446,8 +447,9 @@
 
   std::unique_ptr<llvm::MemoryBuffer> getMainBufferWithPrecompiledPreamble(
       std::shared_ptr<PCHContainerOperations> PCHContainerOps,
-      const CompilerInvocation &PreambleInvocationIn, bool AllowRebuild = true,
-      unsigned MaxLines = 0);
+      const CompilerInvocation &PreambleInvocationIn,
+      std::unique_ptr<FileManager> &PCHFileMgr,
+      bool AllowRebuild = true, unsigned MaxLines = 0);
   void RealizeTopLevelDeclsFromPreamble();
 
   /// \brief Transfers ownership of the objects (like SourceManager) from
@@ -860,7 +862,8 @@
   /// \returns True if a failure occurred that causes the ASTUnit not to
   /// contain any translation-unit information, false otherwise.
   bool Reparse(std::shared_ptr<PCHContainerOperations> PCHContainerOps,
-               ArrayRef<RemappedFile> RemappedFiles = None);
+               ArrayRef<RemappedFile> RemappedFiles = None,
+               FileManager *FileMgr = nullptr);
 
   /// \brief Perform code completion at the given file, line, and
   /// column within this translation unit.
Index: include/clang/Basic/FileManager.h
===================================================================
--- include/clang/Basic/FileManager.h
+++ include/clang/Basic/FileManager.h
@@ -60,6 +60,7 @@
   bool IsNamedPipe;
   bool InPCH;
   bool IsValid;               // Is this \c FileEntry initialized and valid?
+  bool IsVirtual;             // Is this \c FileEntry a virtual file?
 
   /// \brief The open file, if it is owned by the \p FileEntry.
   mutable std::unique_ptr<vfs::File> File;
@@ -69,20 +70,23 @@
 
 public:
   FileEntry()
-      : UniqueID(0, 0), IsNamedPipe(false), InPCH(false), IsValid(false)
+      : UniqueID(0, 0), IsNamedPipe(false), InPCH(false), IsValid(false),
+        IsVirtual(false)
   {}
 
   // FIXME: this is here to allow putting FileEntry in std::map.  Once we have
   // emplace, we shouldn't need a copy constructor anymore.
   /// Intentionally does not copy fields that are not set in an uninitialized
   /// \c FileEntry.
   FileEntry(const FileEntry &FE) : UniqueID(FE.UniqueID),
-      IsNamedPipe(FE.IsNamedPipe), InPCH(FE.InPCH), IsValid(FE.IsValid) {
+      IsNamedPipe(FE.IsNamedPipe), InPCH(FE.InPCH), IsValid(FE.IsValid),
+      IsVirtual(FE.IsVirtual) {
     assert(!isValid() && "Cannot copy an initialized FileEntry");
   }
 
   const char *getName() const { return Name; }
   bool isValid() const { return IsValid; }
+  bool isVirtual() const { return IsVirtual; }
   off_t getSize() const { return Size; }
   unsigned getUID() const { return UID; }
   const llvm::sys::fs::UniqueID &getUniqueID() const { return UniqueID; }
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to