clang-tools  11.0.0
ConfigProvider.cpp
Go to the documentation of this file.
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.
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.
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
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 
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>
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>
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 
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
clang::clangd::config::Fragment::parseYAML
static std::vector< Fragment > parseYAML(llvm::StringRef YAML, llvm::StringRef BufferName, DiagnosticCallback)
Parses fragments from a YAML file (one from each — delimited document).
Definition: ConfigYAML.cpp:198
E
const Expr * E
Definition: AvoidBindCheck.cpp:88
clang::clangd::config::Params::FreshTime
llvm::Optional< std::chrono::steady_clock::time_point > FreshTime
Hint that stale data is OK to improve performance (e.g.
Definition: ConfigProvider.h:41
clang::clangd::Path
std::string Path
A typedef to represent a file path.
Definition: Path.h:20
Tracer
std::unique_ptr< trace::EventTracer > Tracer
Definition: TraceTests.cpp:163
ConfigProvider.h
Trace.h
clang::clangd::Config
Settings that express user/project preferences and control clangd behavior.
Definition: Config.h:40
clang::clangd::config::Provider
A source of configuration fragments.
Definition: ConfigProvider.h:60
clang::clangd::TextDocumentSyncKind::None
Documents should not be synced at all.
clang::clangd::config::FileConfigCache
Definition: ConfigProvider.cpp:25
clang::clangd::config::FileConfigCache::read
void read(const ThreadsafeFS &TFS, DiagnosticCallback DC, llvm::Optional< std::chrono::steady_clock::time_point > FreshTime, std::vector< CompiledFragment > &Out)
Definition: ConfigProvider.cpp:69
ThreadsafeFS.h
clang::clangd::config::FileConfigCache::Path
std::string Path
Definition: ConfigProvider.cpp:61
FS
MockFS FS
Definition: ClangdLSPServerTests.cpp:66
clang::clangd::config::Provider::getConfig
Config getConfig(const Params &, DiagnosticCallback) const
Build a config based on this provider.
Definition: ConfigProvider.cpp:215
SPAN_ATTACH
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
Definition: Trace.h:154
clang::clangd::config::Provider::fromAncestorRelativeYAMLFiles
static std::unique_ptr< Provider > fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath, const ThreadsafeFS &)
Definition: ConfigProvider.cpp:131
Parent
const Node * Parent
Definition: ExtractFunction.cpp:148
Config.h
clang::clangd::config::Provider::combine
static std::unique_ptr< Provider > combine(std::vector< const Provider * >)
A provider that includes fragments from all the supplied providers.
Definition: ConfigProvider.cpp:196
clang::clangd::ThreadsafeFS
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
Definition: ThreadsafeFS.h:28
clang::clangd::ThreadsafeFS::view
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > view(llvm::NoneType CWD) const
Obtain a vfs::FileSystem with an arbitrary initial working directory.
Definition: ThreadsafeFS.h:34
clang::clangd::config::Params
Describes the context used to evaluate configuration fragments.
Definition: ConfigProvider.h:34
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::config::Params::Path
llvm::StringRef Path
Absolute path to a source file we're applying the config to.
Definition: ConfigProvider.h:37
clang::clangd::config::Fragment
A chunk of configuration obtained from a config file, LSP, or elsewhere.
Definition: ConfigFragment.h:64
Out
CompiledFragmentImpl & Out
Definition: ConfigCompile.cpp:70
ConfigFragment.h
clang::clangd::config::Provider::fromYAMLFile
static std::unique_ptr< Provider > fromYAMLFile(llvm::StringRef AbsPathPath, const ThreadsafeFS &)
Definition: ConfigProvider.cpp:107
clang::clangd::config::DiagnosticCallback
llvm::function_ref< void(const llvm::SMDiagnostic &)> DiagnosticCallback
Used to report problems in parsing or interpreting a config.
Definition: ConfigProvider.h:47
clang::clangd::trace::Span
Records an event whose duration is the lifetime of the Span object.
Definition: Trace.h:135