xref: /llvm-project/clang-tools-extra/clangd/FS.cpp (revision 5b2772e1fad3fc8b44400b91de4d5bd0724bba75)
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 
PreambleFileStatusCache(llvm::StringRef MainFilePath)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(MainFileCanonical, /*remove_dot_dot=*/true);
23   this->MainFilePath = std::string(MainFileCanonical);
24 }
25 
update(const llvm::vfs::FileSystem & FS,llvm::vfs::Status S,llvm::StringRef File)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(PathStore))
32     return;
33   llvm::sys::path::remove_dots(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({PathStore, std::move(S)});
39 }
40 
41 std::optional<llvm::vfs::Status>
lookup(llvm::StringRef File) const42 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(PathLookup, /*remove_dot_dot=*/true);
47 
48   auto I = StatCache.find(PathLookup);
49   if (I != StatCache.end())
50     // Returned Status name should always match the requested File.
51     return llvm::vfs::Status::copyWithNewName(I->getValue(), File);
52   return std::nullopt;
53 }
54 
55 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>
getProducingFS(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)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(getUnderlyingFS(), std::move(*S), 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(getUnderlyingFS(), *S, 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>
getConsumingFS(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) const96 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(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 
removeDots(PathRef File)116 Path removeDots(PathRef File) {
117   llvm::SmallString<128> CanonPath(File);
118   llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true);
119   return CanonPath.str().str();
120 }
121 
122 } // namespace clangd
123 } // namespace clang
124