1 //===--- FS.cpp - File system related utils ----------------------*- C++-*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "FS.h" 10 #include "clang/Basic/LLVM.h" 11 #include "llvm/ADT/None.h" 12 #include "llvm/Support/Path.h" 13 #include "llvm/Support/VirtualFileSystem.h" 14 15 namespace clang { 16 namespace clangd { 17 18 PreambleFileStatusCache::PreambleFileStatusCache(llvm::StringRef MainFilePath){ 19 assert(llvm::sys::path::is_absolute(MainFilePath)); 20 llvm::SmallString<256> MainFileCanonical(MainFilePath); 21 llvm::sys::path::remove_dots(MainFileCanonical, /*remove_dot_dot=*/true); 22 this->MainFilePath = std::string(MainFileCanonical.str()); 23 } 24 25 void PreambleFileStatusCache::update(const llvm::vfs::FileSystem &FS, 26 llvm::vfs::Status S) { 27 // Canonicalize path for later lookup, which is usually by absolute path. 28 llvm::SmallString<32> PathStore(S.getName()); 29 if (FS.makeAbsolute(PathStore)) 30 return; 31 llvm::sys::path::remove_dots(PathStore, /*remove_dot_dot=*/true); 32 // Do not cache status for the main file. 33 if (PathStore == MainFilePath) 34 return; 35 // Stores the latest status in cache as it can change in a preamble build. 36 StatCache.insert({PathStore, std::move(S)}); 37 } 38 39 llvm::Optional<llvm::vfs::Status> 40 PreambleFileStatusCache::lookup(llvm::StringRef File) const { 41 // Canonicalize to match the cached form. 42 // Lookup tends to be first by absolute path, so no need to make absolute. 43 llvm::SmallString<256> PathLookup(File); 44 llvm::sys::path::remove_dots(PathLookup, /*remove_dot_dot=*/true); 45 46 auto I = StatCache.find(PathLookup); 47 if (I != StatCache.end()) 48 // Returned Status name should always match the requested File. 49 return llvm::vfs::Status::copyWithNewName(I->getValue(), File); 50 return None; 51 } 52 53 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> 54 PreambleFileStatusCache::getProducingFS( 55 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) { 56 // This invalidates old status in cache if files are re-`open()`ed or 57 // re-`stat()`ed in case file status has changed during preamble build. 58 class CollectFS : public llvm::vfs::ProxyFileSystem { 59 public: 60 CollectFS(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS, 61 PreambleFileStatusCache &StatCache) 62 : ProxyFileSystem(std::move(FS)), StatCache(StatCache) {} 63 64 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> 65 openFileForRead(const llvm::Twine &Path) override { 66 auto File = getUnderlyingFS().openFileForRead(Path); 67 if (!File || !*File) 68 return File; 69 // Eagerly stat opened file, as the followup `status` call on the file 70 // doesn't necessarily go through this FS. This puts some extra work on 71 // preamble build, but it should be worth it as preamble can be reused 72 // many times (e.g. code completion) and the repeated status call is 73 // likely to be cached in the underlying file system anyway. 74 if (auto S = File->get()->status()) 75 StatCache.update(getUnderlyingFS(), std::move(*S)); 76 return File; 77 } 78 79 llvm::ErrorOr<llvm::vfs::Status> status(const llvm::Twine &Path) override { 80 auto S = getUnderlyingFS().status(Path); 81 if (S) 82 StatCache.update(getUnderlyingFS(), *S); 83 return S; 84 } 85 86 private: 87 PreambleFileStatusCache &StatCache; 88 }; 89 return llvm::IntrusiveRefCntPtr<CollectFS>( 90 new CollectFS(std::move(FS), *this)); 91 } 92 93 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> 94 PreambleFileStatusCache::getConsumingFS( 95 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) const { 96 class CacheVFS : public llvm::vfs::ProxyFileSystem { 97 public: 98 CacheVFS(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS, 99 const PreambleFileStatusCache &StatCache) 100 : ProxyFileSystem(std::move(FS)), StatCache(StatCache) {} 101 102 llvm::ErrorOr<llvm::vfs::Status> status(const llvm::Twine &Path) override { 103 if (auto S = StatCache.lookup(Path.str())) 104 return *S; 105 return getUnderlyingFS().status(Path); 106 } 107 108 private: 109 const PreambleFileStatusCache &StatCache; 110 }; 111 return llvm::IntrusiveRefCntPtr<CacheVFS>(new CacheVFS(std::move(FS), *this)); 112 } 113 114 Path removeDots(PathRef File) { 115 llvm::SmallString<128> CanonPath(File); 116 llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true); 117 return CanonPath.str().str(); 118 } 119 120 } // namespace clangd 121 } // namespace clang 122