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. 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> 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> 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 // FIXME: This is a workaround for a bug in older versions of clang (< 3.9) 213 // The constructor that is supposed to allow for Derived to Base 214 // conversion does not work. Remove this if we drop support for such 215 // configurations. 216 return std::unique_ptr<Provider>(Result.release()); 217 } 218 219 Config Provider::getConfig(const Params &P, DiagnosticCallback DC) const { 220 trace::Span Tracer("getConfig"); 221 if (!P.Path.empty()) 222 SPAN_ATTACH(Tracer, "path", P.Path); 223 Config C; 224 for (const auto &Fragment : getFragments(P, DC)) 225 Fragment(P, C); 226 return C; 227 } 228 229 } // namespace config 230 } // namespace clangd 231 } // namespace clang 232