blob: a9724548e5ee60e433b04eff65357b85399d3f78 [file] [log] [blame]
Sam McCall8dc9dbb2018-10-15 13:34:10 +00001//===-- Background.cpp - Build an index in a background thread ------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "index/Background.h"
11#include "ClangdUnit.h"
12#include "Compiler.h"
13#include "Logger.h"
Kadir Cetinkaya6675be82018-10-30 12:13:27 +000014#include "Threading.h"
Sam McCall8dc9dbb2018-10-15 13:34:10 +000015#include "Trace.h"
Eric Liuad588af2018-11-06 10:55:21 +000016#include "URI.h"
Sam McCall8dc9dbb2018-10-15 13:34:10 +000017#include "index/IndexAction.h"
18#include "index/MemIndex.h"
19#include "index/Serialization.h"
Eric Liuad588af2018-11-06 10:55:21 +000020#include "index/SymbolCollector.h"
21#include "clang/Basic/SourceLocation.h"
22#include "clang/Basic/SourceManager.h"
23#include "llvm/ADT/STLExtras.h"
24#include "llvm/ADT/StringMap.h"
25#include "llvm/ADT/StringRef.h"
Sam McCall8dc9dbb2018-10-15 13:34:10 +000026#include "llvm/Support/SHA1.h"
Kadir Cetinkayacb8407c2018-11-15 10:31:15 +000027#include <memory>
28#include <queue>
Sam McCall8dc9dbb2018-10-15 13:34:10 +000029#include <random>
Eric Liuad588af2018-11-06 10:55:21 +000030#include <string>
Sam McCall8dc9dbb2018-10-15 13:34:10 +000031
32using namespace llvm;
33namespace clang {
34namespace clangd {
35
Kadir Cetinkaya3e5a4752018-11-15 10:31:10 +000036namespace {
37
38static BackgroundIndex::FileDigest digest(StringRef Content) {
39 return SHA1::hash({(const uint8_t *)Content.data(), Content.size()});
40}
41
42static Optional<BackgroundIndex::FileDigest> digestFile(const SourceManager &SM,
43 FileID FID) {
44 bool Invalid = false;
45 StringRef Content = SM.getBufferData(FID, &Invalid);
46 if (Invalid)
47 return None;
48 return digest(Content);
49}
50
Kadir Cetinkaya8b9fed32018-11-15 10:34:35 +000051llvm::SmallString<128>
52getShardPathFromFilePath(llvm::SmallString<128> ShardRoot,
53 llvm::StringRef FilePath) {
54 sys::path::append(ShardRoot, sys::path::filename(FilePath) +
55 toHex(digest(FilePath)) + ".idx");
56 return ShardRoot;
Kadir Cetinkaya3e5a4752018-11-15 10:31:10 +000057}
58
59} // namespace
60
Kadir Cetinkayaed18e782018-11-15 10:34:39 +000061BackgroundIndex::BackgroundIndex(
62 Context BackgroundContext, StringRef ResourceDir,
63 const FileSystemProvider &FSProvider, ArrayRef<std::string> URISchemes,
64 std::unique_ptr<ShardStorage> IndexShardStorage, size_t ThreadPoolSize)
Sam McCallc008af62018-10-20 15:30:37 +000065 : SwapIndex(make_unique<MemIndex>()), ResourceDir(ResourceDir),
Sam McCall8dc9dbb2018-10-15 13:34:10 +000066 FSProvider(FSProvider), BackgroundContext(std::move(BackgroundContext)),
Kadir Cetinkayaed18e782018-11-15 10:34:39 +000067 URISchemes(URISchemes), IndexShardStorage(std::move(IndexShardStorage)) {
Kadir Cetinkaya6675be82018-10-30 12:13:27 +000068 assert(ThreadPoolSize > 0 && "Thread pool size can't be zero.");
69 while (ThreadPoolSize--) {
70 ThreadPool.emplace_back([this] { run(); });
71 // Set priority to low, since background indexing is a long running task we
72 // do not want to eat up cpu when there are any other high priority threads.
73 // FIXME: In the future we might want a more general way of handling this to
Kadir Cetinkaya3e5a4752018-11-15 10:31:10 +000074 // support tasks with various priorities.
Kadir Cetinkaya6675be82018-10-30 12:13:27 +000075 setThreadPriority(ThreadPool.back(), ThreadPriority::Low);
76 }
77}
Sam McCall8dc9dbb2018-10-15 13:34:10 +000078
79BackgroundIndex::~BackgroundIndex() {
80 stop();
Kadir Cetinkaya6675be82018-10-30 12:13:27 +000081 for (auto &Thread : ThreadPool)
82 Thread.join();
Sam McCall8dc9dbb2018-10-15 13:34:10 +000083}
84
85void BackgroundIndex::stop() {
86 {
87 std::lock_guard<std::mutex> Lock(QueueMu);
88 ShouldStop = true;
89 }
90 QueueCV.notify_all();
91}
92
93void BackgroundIndex::run() {
Kadir Cetinkaya6675be82018-10-30 12:13:27 +000094 WithContext Background(BackgroundContext.clone());
Sam McCall8dc9dbb2018-10-15 13:34:10 +000095 while (true) {
Sam McCallc008af62018-10-20 15:30:37 +000096 Optional<Task> Task;
Sam McCall8dc9dbb2018-10-15 13:34:10 +000097 {
98 std::unique_lock<std::mutex> Lock(QueueMu);
99 QueueCV.wait(Lock, [&] { return ShouldStop || !Queue.empty(); });
100 if (ShouldStop) {
101 Queue.clear();
102 QueueCV.notify_all();
103 return;
104 }
105 ++NumActiveTasks;
106 Task = std::move(Queue.front());
107 Queue.pop_front();
108 }
109 (*Task)();
110 {
111 std::unique_lock<std::mutex> Lock(QueueMu);
112 assert(NumActiveTasks > 0 && "before decrementing");
113 --NumActiveTasks;
114 }
115 QueueCV.notify_all();
116 }
117}
118
119void BackgroundIndex::blockUntilIdleForTest() {
120 std::unique_lock<std::mutex> Lock(QueueMu);
121 QueueCV.wait(Lock, [&] { return Queue.empty() && NumActiveTasks == 0; });
122}
123
124void BackgroundIndex::enqueue(StringRef Directory,
125 tooling::CompileCommand Cmd) {
Sam McCallbca624a2018-10-16 09:05:13 +0000126 {
127 std::lock_guard<std::mutex> Lock(QueueMu);
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000128 enqueueLocked(std::move(Cmd));
Sam McCallbca624a2018-10-16 09:05:13 +0000129 }
130 QueueCV.notify_all();
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000131}
132
133void BackgroundIndex::enqueueAll(StringRef Directory,
134 const tooling::CompilationDatabase &CDB) {
135 trace::Span Tracer("BackgroundIndexEnqueueCDB");
136 // FIXME: this function may be slow. Perhaps enqueue a task to re-read the CDB
137 // from disk and enqueue the commands asynchronously?
138 auto Cmds = CDB.getAllCompileCommands();
139 SPAN_ATTACH(Tracer, "commands", int64_t(Cmds.size()));
140 std::mt19937 Generator(std::random_device{}());
141 std::shuffle(Cmds.begin(), Cmds.end(), Generator);
142 log("Enqueueing {0} commands for indexing from {1}", Cmds.size(), Directory);
143 {
144 std::lock_guard<std::mutex> Lock(QueueMu);
145 for (auto &Cmd : Cmds)
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000146 enqueueLocked(std::move(Cmd));
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000147 }
148 QueueCV.notify_all();
149}
150
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000151void BackgroundIndex::enqueueLocked(tooling::CompileCommand Cmd) {
152 // Initialize storage to project root. Since Initialize is no-op for multiple
153 // calls we can simply call it for each file.
154 if (IndexShardStorage && !IndexShardStorage->initialize(Cmd.Directory)) {
155 elog("Failed to initialize shard storage");
156 IndexShardStorage.reset();
157 }
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000158 Queue.push_back(Bind(
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000159 [this](tooling::CompileCommand Cmd) {
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000160 std::string Filename = Cmd.Filename;
161 Cmd.CommandLine.push_back("-resource-dir=" + ResourceDir);
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000162 if (auto Error = index(std::move(Cmd)))
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000163 log("Indexing {0} failed: {1}", Filename, std::move(Error));
164 },
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000165 std::move(Cmd)));
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000166}
167
Eric Liuad588af2018-11-06 10:55:21 +0000168// Resolves URI to file paths with cache.
169class URIToFileCache {
170public:
171 URIToFileCache(llvm::StringRef HintPath) : HintPath(HintPath) {}
172
173 llvm::StringRef resolve(llvm::StringRef FileURI) {
174 auto I = URIToPathCache.try_emplace(FileURI);
175 if (I.second) {
176 auto U = URI::parse(FileURI);
177 if (!U) {
178 elog("Failed to parse URI {0}: {1}", FileURI, U.takeError());
179 assert(false && "Failed to parse URI");
180 return "";
181 }
182 auto Path = URI::resolve(*U, HintPath);
183 if (!Path) {
184 elog("Failed to resolve URI {0}: {1}", FileURI, Path.takeError());
185 assert(false && "Failed to resolve URI");
186 return "";
187 }
188 I.first->second = *Path;
189 }
190 return I.first->second;
191 }
192
193private:
194 std::string HintPath;
195 llvm::StringMap<std::string> URIToPathCache;
196};
197
198/// Given index results from a TU, only update files in \p FilesToUpdate.
199void BackgroundIndex::update(StringRef MainFile, SymbolSlab Symbols,
200 RefSlab Refs,
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000201 const StringMap<FileDigest> &FilesToUpdate) {
Eric Liuad588af2018-11-06 10:55:21 +0000202 // Partition symbols/references into files.
203 struct File {
204 DenseSet<const Symbol *> Symbols;
205 DenseSet<const Ref *> Refs;
206 };
207 StringMap<File> Files;
208 URIToFileCache URICache(MainFile);
209 for (const auto &Sym : Symbols) {
210 if (Sym.CanonicalDeclaration) {
211 auto DeclPath = URICache.resolve(Sym.CanonicalDeclaration.FileURI);
212 if (FilesToUpdate.count(DeclPath) != 0)
213 Files[DeclPath].Symbols.insert(&Sym);
214 }
215 // For symbols with different declaration and definition locations, we store
216 // the full symbol in both the header file and the implementation file, so
217 // that merging can tell the preferred symbols (from canonical headers) from
218 // other symbols (e.g. forward declarations).
219 if (Sym.Definition &&
220 Sym.Definition.FileURI != Sym.CanonicalDeclaration.FileURI) {
221 auto DefPath = URICache.resolve(Sym.Definition.FileURI);
222 if (FilesToUpdate.count(DefPath) != 0)
223 Files[DefPath].Symbols.insert(&Sym);
224 }
225 }
226 DenseMap<const Ref *, SymbolID> RefToIDs;
227 for (const auto &SymRefs : Refs) {
228 for (const auto &R : SymRefs.second) {
229 auto Path = URICache.resolve(R.Location.FileURI);
230 if (FilesToUpdate.count(Path) != 0) {
231 auto &F = Files[Path];
232 RefToIDs[&R] = SymRefs.first;
233 F.Refs.insert(&R);
234 }
235 }
236 }
237
238 // Build and store new slabs for each updated file.
239 for (const auto &F : Files) {
240 StringRef Path = F.first();
241 vlog("Update symbols in {0}", Path);
242 SymbolSlab::Builder Syms;
243 RefSlab::Builder Refs;
244 for (const auto *S : F.second.Symbols)
245 Syms.insert(*S);
246 for (const auto *R : F.second.Refs)
247 Refs.insert(RefToIDs[R], *R);
248
Kadir Cetinkaya3e5a4752018-11-15 10:31:10 +0000249 auto SS = llvm::make_unique<SymbolSlab>(std::move(Syms).build());
250 auto RS = llvm::make_unique<RefSlab>(std::move(Refs).build());
251
252 auto Hash = FilesToUpdate.lookup(Path);
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000253 // Put shards into storage for subsequent use.
Kadir Cetinkaya3e5a4752018-11-15 10:31:10 +0000254 // FIXME: Store Hash in the Shard.
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000255 if (IndexShardStorage) {
Kadir Cetinkaya3e5a4752018-11-15 10:31:10 +0000256 IndexFileOut Shard;
257 Shard.Symbols = SS.get();
258 Shard.Refs = RS.get();
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000259 IndexShardStorage->storeShard(Path, Shard);
Kadir Cetinkaya3e5a4752018-11-15 10:31:10 +0000260 }
261
Eric Liuad588af2018-11-06 10:55:21 +0000262 std::lock_guard<std::mutex> Lock(DigestsMu);
263 // This can override a newer version that is added in another thread,
264 // if this thread sees the older version but finishes later. This should be
265 // rare in practice.
Kadir Cetinkaya3e5a4752018-11-15 10:31:10 +0000266 IndexedFileDigests[Path] = Hash;
267 IndexedSymbols.update(Path, std::move(SS), std::move(RS));
Eric Liuad588af2018-11-06 10:55:21 +0000268 }
269}
270
271// Creates a filter to not collect index results from files with unchanged
272// digests.
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000273// \p FileDigests contains file digests for the current indexed files, and all changed files will be added to \p FilesToUpdate.
Eric Liuad588af2018-11-06 10:55:21 +0000274decltype(SymbolCollector::Options::FileFilter) createFileFilter(
275 const llvm::StringMap<BackgroundIndex::FileDigest> &FileDigests,
276 llvm::StringMap<BackgroundIndex::FileDigest> &FilesToUpdate) {
277 return [&FileDigests, &FilesToUpdate](const SourceManager &SM, FileID FID) {
278 StringRef Path;
279 if (const auto *F = SM.getFileEntryForID(FID))
280 Path = F->getName();
281 if (Path.empty())
282 return false; // Skip invalid files.
283 SmallString<128> AbsPath(Path);
284 if (std::error_code EC =
285 SM.getFileManager().getVirtualFileSystem()->makeAbsolute(AbsPath)) {
286 elog("Warning: could not make absolute file: {0}", EC.message());
287 return false; // Skip files without absolute path.
288 }
289 sys::path::remove_dots(AbsPath, /*remove_dot_dot=*/true);
290 auto Digest = digestFile(SM, FID);
291 if (!Digest)
292 return false;
293 auto D = FileDigests.find(AbsPath);
294 if (D != FileDigests.end() && D->second == Digest)
295 return false; // Skip files that haven't changed.
296
297 FilesToUpdate[AbsPath] = *Digest;
298 return true;
299 };
300}
301
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000302Error BackgroundIndex::index(tooling::CompileCommand Cmd) {
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000303 trace::Span Tracer("BackgroundIndex");
304 SPAN_ATTACH(Tracer, "file", Cmd.Filename);
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000305 SmallString<128> AbsolutePath;
306 if (sys::path::is_absolute(Cmd.Filename)) {
307 AbsolutePath = Cmd.Filename;
308 } else {
309 AbsolutePath = Cmd.Directory;
310 sys::path::append(AbsolutePath, Cmd.Filename);
311 }
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000312
313 auto FS = FSProvider.getFileSystem();
314 auto Buf = FS->getBufferForFile(AbsolutePath);
315 if (!Buf)
316 return errorCodeToError(Buf.getError());
Eric Liuad588af2018-11-06 10:55:21 +0000317 auto Hash = digest(Buf->get()->getBuffer());
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000318
Eric Liuad588af2018-11-06 10:55:21 +0000319 // Take a snapshot of the digests to avoid locking for each file in the TU.
320 llvm::StringMap<FileDigest> DigestsSnapshot;
321 {
322 std::lock_guard<std::mutex> Lock(DigestsMu);
323 if (IndexedFileDigests.lookup(AbsolutePath) == Hash) {
324 vlog("No need to index {0}, already up to date", AbsolutePath);
325 return Error::success();
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000326 } else if (IndexShardStorage) { // Check if shard storage has the index.
327 auto Shard = IndexShardStorage->retrieveShard(AbsolutePath, Hash);
328 if (Shard) {
329 // FIXME: We might still want to re-index headers.
330 IndexedFileDigests[AbsolutePath] = Hash;
331 IndexedSymbols.update(
332 AbsolutePath, make_unique<SymbolSlab>(std::move(*Shard->Symbols)),
333 make_unique<RefSlab>(std::move(*Shard->Refs)));
334
335 vlog("Loaded {0} from storage", AbsolutePath);
336 return Error::success();
337 }
Eric Liuad588af2018-11-06 10:55:21 +0000338 }
339
340 DigestsSnapshot = IndexedFileDigests;
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000341 }
342
343 log("Indexing {0}", Cmd.Filename, toHex(Hash));
344 ParseInputs Inputs;
345 Inputs.FS = std::move(FS);
346 Inputs.FS->setCurrentWorkingDirectory(Cmd.Directory);
347 Inputs.CompileCommand = std::move(Cmd);
348 auto CI = buildCompilerInvocation(Inputs);
349 if (!CI)
Sam McCallc008af62018-10-20 15:30:37 +0000350 return createStringError(inconvertibleErrorCode(),
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000351 "Couldn't build compiler invocation");
352 IgnoreDiagnostics IgnoreDiags;
353 auto Clang = prepareCompilerInstance(
354 std::move(CI), /*Preamble=*/nullptr, std::move(*Buf),
355 std::make_shared<PCHContainerOperations>(), Inputs.FS, IgnoreDiags);
356 if (!Clang)
Sam McCallc008af62018-10-20 15:30:37 +0000357 return createStringError(inconvertibleErrorCode(),
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000358 "Couldn't build compiler instance");
359
360 SymbolCollector::Options IndexOpts;
Eric Liuad588af2018-11-06 10:55:21 +0000361 IndexOpts.URISchemes = URISchemes;
362 StringMap<FileDigest> FilesToUpdate;
363 IndexOpts.FileFilter = createFileFilter(DigestsSnapshot, FilesToUpdate);
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000364 SymbolSlab Symbols;
365 RefSlab Refs;
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000366 auto Action = createStaticIndexingAction(
367 IndexOpts, [&](SymbolSlab S) { Symbols = std::move(S); },
368 [&](RefSlab R) { Refs = std::move(R); });
369
370 // We're going to run clang here, and it could potentially crash.
371 // We could use CrashRecoveryContext to try to make indexing crashes nonfatal,
372 // but the leaky "recovery" is pretty scary too in a long-running process.
373 // If crashes are a real problem, maybe we should fork a child process.
374
375 const FrontendInputFile &Input = Clang->getFrontendOpts().Inputs.front();
376 if (!Action->BeginSourceFile(*Clang, Input))
Sam McCallc008af62018-10-20 15:30:37 +0000377 return createStringError(inconvertibleErrorCode(),
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000378 "BeginSourceFile() failed");
379 if (!Action->Execute())
Sam McCallc008af62018-10-20 15:30:37 +0000380 return createStringError(inconvertibleErrorCode(), "Execute() failed");
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000381 Action->EndSourceFile();
382
383 log("Indexed {0} ({1} symbols, {2} refs)", Inputs.CompileCommand.Filename,
Haojian Wu6ece6e72018-10-18 15:33:20 +0000384 Symbols.size(), Refs.numRefs());
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000385 SPAN_ATTACH(Tracer, "symbols", int(Symbols.size()));
Haojian Wu6ece6e72018-10-18 15:33:20 +0000386 SPAN_ATTACH(Tracer, "refs", int(Refs.numRefs()));
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000387 update(AbsolutePath, std::move(Symbols), std::move(Refs), FilesToUpdate);
Eric Liuad588af2018-11-06 10:55:21 +0000388 {
389 // Make sure hash for the main file is always updated even if there is no
390 // index data in it.
391 std::lock_guard<std::mutex> Lock(DigestsMu);
392 IndexedFileDigests[AbsolutePath] = Hash;
393 }
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000394
395 // FIXME: this should rebuild once-in-a-while, not after every file.
396 // At that point we should use Dex, too.
397 vlog("Rebuilding automatic index");
Eric Liuad588af2018-11-06 10:55:21 +0000398 reset(IndexedSymbols.buildIndex(IndexType::Light, DuplicateHandling::Merge,
399 URISchemes));
400
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000401 return Error::success();
402}
403
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000404llvm::Expected<IndexFileIn>
405DiskShardStorage::retrieveShard(llvm::StringRef ShardIdentifier,
406 FileDigest Hash) const {
407 assert(Initialized && "Not initialized?");
408 llvm::SmallString<128> ShardPath;
409 {
410 std::lock_guard<std::mutex> Lock(DiskShardRootMu);
411 ShardPath = getShardPathFromFilePath(DiskShardRoot, ShardIdentifier);
412 }
413 auto Buffer = MemoryBuffer::getFile(ShardPath);
414 if (!Buffer) {
415 elog("Couldn't retrieve {0}: {1}", ShardPath, Buffer.getError().message());
416 return llvm::make_error<llvm::StringError>(Buffer.getError());
417 }
418 // FIXME: Change readIndexFile to also look at Hash of the source that
419 // generated index and skip if there is a mismatch.
420 return readIndexFile(Buffer->get()->getBuffer());
421}
422
423bool DiskShardStorage::storeShard(llvm::StringRef ShardIdentifier,
424 IndexFileOut Shard) const {
425 assert(Initialized && "Not initialized?");
426 llvm::SmallString<128> ShardPath;
427 {
428 std::lock_guard<std::mutex> Lock(DiskShardRootMu);
429 ShardPath = getShardPathFromFilePath(DiskShardRoot, ShardIdentifier);
430 }
431 std::error_code EC;
432 llvm::raw_fd_ostream OS(ShardPath, EC);
433 if (EC) {
434 elog("Failed to open {0} for writing: {1}", ShardPath, EC.message());
435 return false;
436 }
437 OS << Shard;
438 return true;
439}
440
441bool DiskShardStorage::initialize(llvm::StringRef Directory) {
442 if (Initialized)
443 return true;
444 std::lock_guard<std::mutex> Lock(DiskShardRootMu);
445 DiskShardRoot = Directory;
446 sys::path::append(DiskShardRoot, ".clangd-index/");
447 if (!llvm::sys::fs::exists(DiskShardRoot)) {
448 std::error_code OK;
449 std::error_code EC = llvm::sys::fs::create_directory(DiskShardRoot);
450 if (EC != OK) {
451 elog("Failed to create {0}: {1}", DiskShardRoot, EC.message());
452 return Initialized = false;
453 }
454 }
455 return Initialized = true;
456}
Kadir Cetinkaya3e5a4752018-11-15 10:31:10 +0000457
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000458} // namespace clangd
459} // namespace clang