1 //===--- ConfigProvider.cpp - Loading of user configuration ---------------===//
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 "ConfigProvider.h"
10 #include "Config.h"
11 #include "ConfigFragment.h"
12 #include "support/ThreadsafeFS.h"
13 #include "support/Trace.h"
14 #include "llvm/ADT/ScopeExit.h"
15 #include "llvm/ADT/StringMap.h"
16 #include "llvm/Support/Path.h"
17 #include <chrono>
18 #include <mutex>
19 
20 namespace clang {
21 namespace clangd {
22 namespace config {
23 
24 // Threadsafe cache around reading a YAML config file from disk.
25 class FileConfigCache {
26   std::mutex Mu;
27   std::chrono::steady_clock::time_point ValidTime = {};
28   llvm::SmallVector<CompiledFragment, 1> CachedValue;
29   llvm::sys::TimePoint<> MTime = {};
30   unsigned Size = -1;
31 
32   // Called once we are sure we want to read the file.
33   // REQUIRES: Cache keys are set. Mutex must be held.
fillCacheFromDisk(llvm::vfs::FileSystem & FS,DiagnosticCallback DC)34   void fillCacheFromDisk(llvm::vfs::FileSystem &FS, DiagnosticCallback DC) {
35     CachedValue.clear();
36 
37     auto Buf = FS.getBufferForFile(Path);
38     // If we failed to read (but stat succeeded), don't cache failure.
39     if (!Buf) {
40       Size = -1;
41       MTime = {};
42       return;
43     }
44 
45     // If file changed between stat and open, we don't know its mtime.
46     // For simplicity, don't cache the value in this case (use a bad key).
47     if (Buf->get()->getBufferSize() != Size) {
48       Size = -1;
49       MTime = {};
50     }
51 
52     // Finally parse and compile the actual fragments.
53     for (auto &Fragment :
54          Fragment::parseYAML(Buf->get()->getBuffer(), Path, DC))
55       CachedValue.push_back(std::move(Fragment).compile(DC));
56   }
57 
58 public:
59   // Must be set before the cache is used. Not a constructor param to allow
60   // computing ancestor-relative paths to be deferred.
61   std::string Path;
62 
63   // Retrieves up-to-date config fragments from disk.
64   // A cached result may be reused if the mtime and size are unchanged.
65   // (But several concurrent read()s can miss the cache after a single change).
66   // Future performance ideas:
67   // - allow caches to be reused based on short elapsed walltime
68   // - allow latency-sensitive operations to skip revalidating the cache
read(const ThreadsafeFS & TFS,DiagnosticCallback DC,llvm::Optional<std::chrono::steady_clock::time_point> FreshTime,std::vector<CompiledFragment> & Out)69   void read(const ThreadsafeFS &TFS, DiagnosticCallback DC,
70             llvm::Optional<std::chrono::steady_clock::time_point> FreshTime,
71             std::vector<CompiledFragment> &Out) {
72     std::lock_guard<std::mutex> Lock(Mu);
73     // We're going to update the cache and return whatever's in it.
74     auto Return = llvm::make_scope_exit(
75         [&] { llvm::copy(CachedValue, std::back_inserter(Out)); });
76 
77     // Return any sufficiently recent result without doing any further work.
78     if (FreshTime && ValidTime >= FreshTime)
79       return;
80 
81     // Ensure we bump the ValidTime at the end to allow for reuse.
82     auto MarkTime = llvm::make_scope_exit(
83         [&] { ValidTime = std::chrono::steady_clock::now(); });
84 
85     // Stat is cheaper than opening the file, it's usually unchanged.
86     assert(llvm::sys::path::is_absolute(Path));
87     auto FS = TFS.view(/*CWD=*/llvm::None);
88     auto Stat = FS->status(Path);
89     // If there's no file, the result is empty. Ensure we have an invalid key.
90     if (!Stat || !Stat->isRegularFile()) {
91       MTime = {};
92       Size = -1;
93       CachedValue.clear();
94       return;
95     }
96     // If the modified-time and size match, assume the content does too.
97     if (Size == Stat->getSize() && MTime == Stat->getLastModificationTime())
98       return;
99 
100     // OK, the file has actually changed. Update cache key, compute new value.
101     Size = Stat->getSize();
102     MTime = Stat->getLastModificationTime();
103     fillCacheFromDisk(*FS, DC);
104   }
105 };
106 
fromYAMLFile(llvm::StringRef AbsPath,const ThreadsafeFS & FS)107 std::unique_ptr<Provider> Provider::fromYAMLFile(llvm::StringRef AbsPath,
108                                                  const ThreadsafeFS &FS) {
109   class AbsFileProvider : public Provider {
110     mutable FileConfigCache Cache; // threadsafe
111     const ThreadsafeFS &FS;
112 
113     std::vector<CompiledFragment>
114     getFragments(const Params &P, DiagnosticCallback DC) const override {
115       std::vector<CompiledFragment> Result;
116       Cache.read(FS, DC, P.FreshTime, Result);
117       return Result;
118     };
119 
120   public:
121     AbsFileProvider(llvm::StringRef Path, const ThreadsafeFS &FS) : FS(FS) {
122       assert(llvm::sys::path::is_absolute(Path));
123       Cache.Path = Path.str();
124     }
125   };
126 
127   return std::make_unique<AbsFileProvider>(AbsPath, FS);
128 }
129 
130 std::unique_ptr<Provider>
fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath,const ThreadsafeFS & FS)131 Provider::fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath,
132                                         const ThreadsafeFS &FS) {
133   class RelFileProvider : public Provider {
134     std::string RelPath;
135     const ThreadsafeFS &FS;
136 
137     mutable std::mutex Mu;
138     // Keys are the ancestor directory, not the actual config path within it.
139     // We only insert into this map, so pointers to values are stable forever.
140     // Mutex guards the map itself, not the values (which are threadsafe).
141     mutable llvm::StringMap<FileConfigCache> Cache;
142 
143     std::vector<CompiledFragment>
144     getFragments(const Params &P, DiagnosticCallback DC) const override {
145       namespace path = llvm::sys::path;
146 
147       if (P.Path.empty())
148         return {};
149 
150       // Compute absolute paths to all ancestors (substrings of P.Path).
151       llvm::StringRef Parent = path::parent_path(P.Path);
152       llvm::SmallVector<llvm::StringRef, 8> Ancestors;
153       for (auto I = path::begin(Parent, path::Style::posix),
154                 E = path::end(Parent);
155            I != E; ++I) {
156         // Avoid weird non-substring cases like phantom "." components.
157         // In practice, Component is a substring for all "normal" ancestors.
158         if (I->end() < Parent.begin() && I->end() > Parent.end())
159           continue;
160         Ancestors.emplace_back(Parent.begin(), I->end() - Parent.begin());
161       }
162       // Ensure corresponding cache entries exist in the map.
163       llvm::SmallVector<FileConfigCache *, 8> Caches;
164       {
165         std::lock_guard<std::mutex> Lock(Mu);
166         for (llvm::StringRef Ancestor : Ancestors) {
167           auto R = Cache.try_emplace(Ancestor);
168           // Assemble the actual config file path only once.
169           if (R.second) {
170             llvm::SmallString<256> ConfigPath = Ancestor;
171             path::append(ConfigPath, RelPath);
172             R.first->second.Path = ConfigPath.str().str();
173           }
174           Caches.push_back(&R.first->second);
175         }
176       }
177       // Finally query each individual file.
178       // This will take a (per-file) lock for each file that actually exists.
179       std::vector<CompiledFragment> Result;
180       for (FileConfigCache *Cache : Caches)
181         Cache->read(FS, DC, P.FreshTime, Result);
182       return Result;
183     };
184 
185   public:
186     RelFileProvider(llvm::StringRef RelPath, const ThreadsafeFS &FS)
187         : RelPath(RelPath), FS(FS) {
188       assert(llvm::sys::path::is_relative(RelPath));
189     }
190   };
191 
192   return std::make_unique<RelFileProvider>(RelPath, FS);
193 }
194 
195 std::unique_ptr<Provider>
combine(std::vector<const Provider * > Providers)196 Provider::combine(std::vector<const Provider *> Providers) {
197   struct CombinedProvider : Provider {
198     std::vector<const Provider *> Providers;
199 
200     std::vector<CompiledFragment>
201     getFragments(const Params &P, DiagnosticCallback DC) const override {
202       std::vector<CompiledFragment> Result;
203       for (const auto &Provider : Providers) {
204         for (auto &Fragment : Provider->getFragments(P, DC))
205           Result.push_back(std::move(Fragment));
206       }
207       return Result;
208     }
209   };
210   auto Result = std::make_unique<CombinedProvider>();
211   Result->Providers = std::move(Providers);
212   return Result;
213 }
214 
getConfig(const Params & P,DiagnosticCallback DC) const215 Config Provider::getConfig(const Params &P, DiagnosticCallback DC) const {
216   trace::Span Tracer("getConfig");
217   if (!P.Path.empty())
218     SPAN_ATTACH(Tracer, "path", P.Path);
219   Config C;
220   for (const auto &Fragment : getFragments(P, DC))
221     Fragment(P, C);
222   return C;
223 }
224 
225 } // namespace config
226 } // namespace clangd
227 } // namespace clang
228