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 | |