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