1 | //===--- ThreadsafeFS.cpp -------------------------------------------------===// |
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 "support/ThreadsafeFS.h" |
10 | #include "Logger.h" |
11 | #include "llvm/ADT/SmallString.h" |
12 | #include "llvm/ADT/StringRef.h" |
13 | #include "llvm/Support/Path.h" |
14 | #include "llvm/Support/VirtualFileSystem.h" |
15 | #include <memory> |
16 | |
17 | namespace clang { |
18 | namespace clangd { |
19 | |
20 | namespace { |
21 | /// Always opens files in the underlying filesystem as "volatile", meaning they |
22 | /// won't be memory-mapped. Memory-mapping isn't desirable for clangd: |
23 | /// - edits to the underlying files change contents MemoryBuffers owned by |
24 | // SourceManager, breaking its invariants and leading to crashes |
25 | /// - it locks files on windows, preventing edits |
26 | class VolatileFileSystem : public llvm::vfs::ProxyFileSystem { |
27 | public: |
28 | explicit VolatileFileSystem(llvm::IntrusiveRefCntPtr<FileSystem> FS) |
29 | : ProxyFileSystem(std::move(FS)) {} |
30 | |
31 | llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> |
32 | openFileForRead(const llvm::Twine &InPath) override { |
33 | llvm::SmallString<128> Path; |
34 | InPath.toVector(Out&: Path); |
35 | |
36 | auto File = getUnderlyingFS().openFileForRead(Path); |
37 | if (!File) |
38 | return File; |
39 | // Try to guess preamble files, they can be memory-mapped even on Windows as |
40 | // clangd has exclusive access to those and nothing else should touch them. |
41 | llvm::StringRef FileName = llvm::sys::path::filename(path: Path); |
42 | if (FileName.starts_with(Prefix: "preamble-" ) && FileName.ends_with(Suffix: ".pch" )) |
43 | return File; |
44 | return std::unique_ptr<VolatileFile>(new VolatileFile(std::move(*File))); |
45 | } |
46 | |
47 | private: |
48 | class VolatileFile : public llvm::vfs::File { |
49 | public: |
50 | VolatileFile(std::unique_ptr<llvm::vfs::File> Wrapped) |
51 | : Wrapped(std::move(Wrapped)) { |
52 | assert(this->Wrapped); |
53 | } |
54 | |
55 | llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> |
56 | getBuffer(const llvm::Twine &Name, int64_t FileSize, |
57 | bool RequiresNullTerminator, bool /*IsVolatile*/) override { |
58 | return Wrapped->getBuffer(Name, FileSize, RequiresNullTerminator, |
59 | /*IsVolatile=*/true); |
60 | } |
61 | |
62 | llvm::ErrorOr<llvm::vfs::Status> status() override { |
63 | return Wrapped->status(); |
64 | } |
65 | llvm::ErrorOr<std::string> getName() override { return Wrapped->getName(); } |
66 | std::error_code close() override { return Wrapped->close(); } |
67 | |
68 | private: |
69 | std::unique_ptr<File> Wrapped; |
70 | }; |
71 | }; |
72 | } // namespace |
73 | |
74 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> |
75 | ThreadsafeFS::view(PathRef CWD) const { |
76 | auto FS = view(CWD: std::nullopt); |
77 | if (auto EC = FS->setCurrentWorkingDirectory(CWD)) |
78 | elog(Fmt: "VFS: failed to set CWD to {0}: {1}" , Vals&: CWD, Vals: EC.message()); |
79 | return FS; |
80 | } |
81 | |
82 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> |
83 | RealThreadsafeFS::viewImpl() const { |
84 | // Avoid using memory-mapped files. |
85 | // FIXME: Try to use a similar approach in Sema instead of relying on |
86 | // propagation of the 'isVolatile' flag through all layers. |
87 | return new VolatileFileSystem(llvm::vfs::createPhysicalFileSystem()); |
88 | } |
89 | } // namespace clangd |
90 | } // namespace clang |
91 | |