Index: include/clang/Basic/VirtualFileSystem.h =================================================================== --- include/clang/Basic/VirtualFileSystem.h +++ include/clang/Basic/VirtualFileSystem.h @@ -391,7 +391,8 @@ Optional User, Optional Group, Optional Type, Optional Perms, - const detail::InMemoryFile *HardLinkTarget); + const detail::InMemoryFile *HardLinkTarget, + Optional StatOnlyFileSize=None); public: explicit InMemoryFileSystem(bool UseNormalizedPaths = true); @@ -409,6 +410,16 @@ Optional Type = None, Optional Perms = None); + // Add a file with the given size but with no contents. The buffer of this + // file must never be requested. + /// \return true if the file successfully added, false if the file already + /// exists in the file system with different size or is not a StatOnlyFile. + bool addStatOnlyFile(const Twine &Path, time_t ModificationTime, + size_t Size, Optional User = None, + Optional Group = None, + Optional Type = None, + Optional Perms = None); + /// Add a hard link to a file. /// Here hard links are not intended to be fully equivalent to the classical /// filesystem. Both the hard link and the file share the same buffer and Index: lib/Basic/VirtualFileSystem.cpp =================================================================== --- lib/Basic/VirtualFileSystem.cpp +++ lib/Basic/VirtualFileSystem.cpp @@ -492,11 +492,13 @@ class InMemoryFile : public InMemoryNode { Status Stat; std::unique_ptr Buffer; + bool StatOnlyFile; public: - InMemoryFile(Status Stat, std::unique_ptr Buffer) + InMemoryFile(Status Stat, std::unique_ptr Buffer, + bool StatOnlyFile) : InMemoryNode(Stat.getName(), IME_File), Stat(std::move(Stat)), - Buffer(std::move(Buffer)) {} + Buffer(std::move(Buffer)), StatOnlyFile(StatOnlyFile) {} /// Return the \p Status for this node. \p RequestedName should be the name /// through which the caller referred to this node. It will override @@ -504,8 +506,12 @@ Status getStatus(StringRef RequestedName) const { return Status::copyWithNewName(Stat, RequestedName); } - llvm::MemoryBuffer *getBuffer() const { return Buffer.get(); } + llvm::MemoryBuffer *getBuffer() const { + assert(!StatOnlyFile && "Cannot get buffer for StatOnlyFile"); + return Buffer.get(); + } + bool IsStatOnlyFile() const { return StatOnlyFile; } std::string toString(unsigned Indent) const override { return (std::string(Indent, ' ') + Stat.getName() + "\n").str(); } @@ -640,7 +646,8 @@ Optional Group, Optional Type, Optional Perms, - const detail::InMemoryFile *HardLinkTarget) { + const detail::InMemoryFile *HardLinkTarget, + Optional StatOnlyFileSize) { SmallString<128> Path; P.toVector(Path); @@ -661,7 +668,11 @@ const auto ResolvedGroup = Group.getValueOr(0); const auto ResolvedType = Type.getValueOr(sys::fs::file_type::regular_file); const auto ResolvedPerms = Perms.getValueOr(sys::fs::all_all); + assert(!(StatOnlyFileSize.hasValue() && Buffer) && + "StatOnlyFile cannot have a buffer"); assert(!(HardLinkTarget && Buffer) && "HardLink cannot have a buffer"); + const auto ResolvedFileSize = + StatOnlyFileSize.getValueOr(Buffer ? Buffer->getBufferSize() : 0); // Any intermediate directories we create should be accessible by // the owner, even if Perms says otherwise for the final path. const auto NewDirectoryPerms = ResolvedPerms | sys::fs::owner_all; @@ -679,13 +690,15 @@ // Create a new file or directory. Status Stat(P.str(), getNextVirtualUniqueID(), llvm::sys::toTimePoint(ModificationTime), ResolvedUser, - ResolvedGroup, Buffer->getBufferSize(), ResolvedType, + ResolvedGroup, ResolvedFileSize, ResolvedType, ResolvedPerms); if (ResolvedType == sys::fs::file_type::directory_file) { Child.reset(new detail::InMemoryDirectory(std::move(Stat))); } else { - Child.reset( - new detail::InMemoryFile(std::move(Stat), std::move(Buffer))); + // Create a new file. + Child.reset(new detail::InMemoryFile(std::move(Stat), + std::move(Buffer), + StatOnlyFileSize.hasValue())); } } Dir->addChild(Name, std::move(Child)); @@ -715,16 +728,33 @@ return false; // Return false only if the new file is different from the existing one. + const detail::InMemoryFile* File; if (auto Link = dyn_cast(Node)) { - return Link->getResolvedFile().getBuffer()->getBuffer() == - Buffer->getBuffer(); + File = &Link->getResolvedFile(); + } else { + File = cast(Node); + } + if (File->IsStatOnlyFile() != StatOnlyFileSize.hasValue()) { + return false; } - return cast(Node)->getBuffer()->getBuffer() == - Buffer->getBuffer(); + if (StatOnlyFileSize.hasValue()) { + return File->getStatus(Path).getSize() == StatOnlyFileSize.getValue(); + } + return File->getBuffer()->getBuffer() == Buffer->getBuffer(); } } } +bool InMemoryFileSystem::addStatOnlyFile( + const Twine &Path, time_t ModificationTime, size_t Size, + Optional User, Optional Group, + Optional Type, + Optional Perms) { + return addFile(Path, ModificationTime, + /*Buffer=*/nullptr, User, Group, Type, Perms, + /*HardLinkTarget=*/nullptr, Size); +} + bool InMemoryFileSystem::addFile(const Twine &P, time_t ModificationTime, std::unique_ptr Buffer, Optional User, @@ -2064,7 +2094,8 @@ } void YAMLVFSWriter::write(llvm::raw_ostream &OS) { - llvm::sort(Mappings, [](const YAMLVFSEntry &LHS, const YAMLVFSEntry &RHS) { + llvm::sort(Mappings.begin(), Mappings.end(), + [](const YAMLVFSEntry &LHS, const YAMLVFSEntry &RHS) { return LHS.VPath < RHS.VPath; }); Index: unittests/Basic/VirtualFileSystemTest.cpp =================================================================== --- unittests/Basic/VirtualFileSystemTest.cpp +++ unittests/Basic/VirtualFileSystemTest.cpp @@ -553,8 +553,8 @@ for (DirIter E; !EC && I != E; I.increment(EC)) InputToCheck.push_back(I->path()); - llvm::sort(InputToCheck); - llvm::sort(Expected); + llvm::sort(InputToCheck.begin(), InputToCheck.end()); + llvm::sort(Expected.begin(), Expected.end()); EXPECT_EQ(InputToCheck.size(), Expected.size()); unsigned LastElt = std::min(InputToCheck.size(), Expected.size()); @@ -1063,6 +1063,7 @@ TEST_F(InMemoryFileSystemTest, RecursiveIterationWithHardLink) { std::error_code EC; FS.addFile("/a/b", 0, MemoryBuffer::getMemBuffer("content string")); + FS.addStatOnlyFile("/a/d", 0, 10); EXPECT_TRUE(FS.addHardLink("/c/d", "/a/b")); auto I = vfs::recursive_directory_iterator(FS, "/", EC); ASSERT_FALSE(EC); @@ -1071,7 +1072,41 @@ I.increment(EC)) { Nodes.push_back(getPosixPath(I->path())); } - EXPECT_THAT(Nodes, testing::UnorderedElementsAre("/a", "/a/b", "/c", "/c/d")); + EXPECT_THAT(Nodes, testing::UnorderedElementsAre("/a", "/a/d", "/a/b", + "/c", "/c/d")); +} + +TEST_F(InMemoryFileSystemTest, AddStatOnlyFileWithCorrectSize) { + FS.addStatOnlyFile("/a/b", 0, 10); + auto Stat = FS.status("/a/b"); + EXPECT_EQ(Stat.get().getSize(), 10); +} + +TEST_F(InMemoryFileSystemTest, AddMultipleFilesUnderStatOnlyFile) { + FS.addStatOnlyFile("/a/b", 0, 10); + EXPECT_FALSE(FS.addStatOnlyFile("/a/b", 0, 11)); + EXPECT_TRUE(FS.addStatOnlyFile("/a/b", 0, 10)); + EXPECT_FALSE(FS.addFile("/a/b", 0, MemoryBuffer::getMemBuffer("content"))); +} + +TEST_F(InMemoryFileSystemTest, DeathWhenBufferRequestedForStatOnlyFile) { + FS.addStatOnlyFile("/a/b", 0, 10); + EXPECT_DEATH(FS.getBufferForFile("/a/b"), + "Cannot get buffer for StatOnlyFile"); +} + +TEST_F(InMemoryFileSystemTest, AddHardLinksToStatOnlyFile) { + FS.addStatOnlyFile("/target", 0, 10); + FS.addHardLink("/link1", "/target"); + FS.addHardLink("/link2", "/link1"); + EXPECT_THAT("/link1", IsHardLinkTo(&FS, "/target")); + EXPECT_THAT("/link2", IsHardLinkTo(&FS, "/target")); +} + +TEST_F(InMemoryFileSystemTest, AddFileUnderHardLinkToStatOnlyFile) { + FS.addStatOnlyFile("/target", 0, 10); + FS.addHardLink("/link", "/target"); + EXPECT_FALSE(FS.addFile("/link", 0, MemoryBuffer::getMemBuffer("content"))); } // NOTE: in the tests below, we use '//root/' as our root directory, since it is