1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
|
//===--- Merge.cpp -----------------------------------------------*- C++-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Merge.h"
#include "index/Symbol.h"
#include "index/SymbolLocation.h"
#include "index/SymbolOrigin.h"
#include "support/Logger.h"
#include "support/Trace.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <iterator>
namespace clang {
namespace clangd {
namespace {
// Returns true if file defining/declaring \p S is covered by \p Index.
bool isIndexAuthoritative(const SymbolIndex::IndexedFiles &Index,
const Symbol &S) {
// We expect the definition to see the canonical declaration, so it seems to
// be enough to check only the definition if it exists.
const char *OwningFile =
S.Definition ? S.Definition.FileURI : S.CanonicalDeclaration.FileURI;
return (Index(OwningFile) & IndexContents::Symbols) != IndexContents::None;
}
} // namespace
bool MergedIndex::fuzzyFind(
const FuzzyFindRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const {
// We can't step through both sources in parallel. So:
// 1) query all dynamic symbols, slurping results into a slab
// 2) query the static symbols, for each one:
// a) if it's not in the dynamic slab, yield it directly
// b) if it's in the dynamic slab, merge it and yield the result
// 3) now yield all the dynamic symbols we haven't processed.
trace::Span Tracer("MergedIndex fuzzyFind");
bool More = false; // We'll be incomplete if either source was.
SymbolSlab::Builder DynB;
unsigned DynamicCount = 0;
unsigned StaticCount = 0;
unsigned MergedCount = 0;
// Number of results ignored due to staleness.
unsigned StaticDropped = 0;
More |= Dynamic->fuzzyFind(Req, [&](const Symbol &S) {
++DynamicCount;
DynB.insert(S);
});
SymbolSlab Dyn = std::move(DynB).build();
llvm::DenseSet<SymbolID> ReportedDynSymbols;
{
auto DynamicContainsFile = Dynamic->indexedFiles();
More |= Static->fuzzyFind(Req, [&](const Symbol &S) {
++StaticCount;
auto DynS = Dyn.find(S.ID);
// If symbol also exist in the dynamic index, just merge and report.
if (DynS != Dyn.end()) {
++MergedCount;
ReportedDynSymbols.insert(S.ID);
return Callback(mergeSymbol(*DynS, S));
}
// Otherwise, if the dynamic index owns the symbol's file, it means static
// index is stale just drop the symbol.
if (isIndexAuthoritative(DynamicContainsFile, S)) {
++StaticDropped;
return;
}
// If not just report the symbol from static index as is.
return Callback(S);
});
}
SPAN_ATTACH(Tracer, "dynamic", DynamicCount);
SPAN_ATTACH(Tracer, "static", StaticCount);
SPAN_ATTACH(Tracer, "static_dropped", StaticDropped);
SPAN_ATTACH(Tracer, "merged", MergedCount);
for (const Symbol &S : Dyn)
if (!ReportedDynSymbols.count(S.ID))
Callback(S);
return More;
}
void MergedIndex::lookup(
const LookupRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const {
trace::Span Tracer("MergedIndex lookup");
SymbolSlab::Builder B;
Dynamic->lookup(Req, [&](const Symbol &S) { B.insert(S); });
auto RemainingIDs = Req.IDs;
{
auto DynamicContainsFile = Dynamic->indexedFiles();
Static->lookup(Req, [&](const Symbol &S) {
// If we've seen the symbol before, just merge.
if (const Symbol *Sym = B.find(S.ID)) {
RemainingIDs.erase(S.ID);
return Callback(mergeSymbol(*Sym, S));
}
// If symbol is missing in dynamic index, and dynamic index owns the
// symbol's file. Static index is stale, just drop the symbol.
if (isIndexAuthoritative(DynamicContainsFile, S))
return;
// Dynamic index doesn't know about this file, just use the symbol from
// static index.
RemainingIDs.erase(S.ID);
Callback(S);
});
}
for (const auto &ID : RemainingIDs)
if (const Symbol *Sym = B.find(ID))
Callback(*Sym);
}
bool MergedIndex::refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const {
trace::Span Tracer("MergedIndex refs");
bool More = false;
uint32_t Remaining =
Req.Limit.getValueOr(std::numeric_limits<uint32_t>::max());
// We don't want duplicated refs from the static/dynamic indexes,
// and we can't reliably deduplicate them because offsets may differ slightly.
// We consider the dynamic index authoritative and report all its refs,
// and only report static index refs from other files.
More |= Dynamic->refs(Req, [&](const Ref &O) {
Callback(O);
assert(Remaining != 0);
--Remaining;
});
if (Remaining == 0 && More)
return More;
auto DynamicContainsFile = Dynamic->indexedFiles();
// We return less than Req.Limit if static index returns more refs for dirty
// files.
bool StaticHadMore = Static->refs(Req, [&](const Ref &O) {
if ((DynamicContainsFile(O.Location.FileURI) & IndexContents::References) !=
IndexContents::None)
return; // ignore refs that have been seen from dynamic index.
if (Remaining == 0) {
More = true;
return;
}
--Remaining;
Callback(O);
});
return More || StaticHadMore;
}
llvm::unique_function<IndexContents(llvm::StringRef) const>
MergedIndex::indexedFiles() const {
return [DynamicContainsFile{Dynamic->indexedFiles()},
StaticContainsFile{Static->indexedFiles()}](llvm::StringRef FileURI) {
return DynamicContainsFile(FileURI) | StaticContainsFile(FileURI);
};
}
void MergedIndex::relations(
const RelationsRequest &Req,
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
uint32_t Remaining =
Req.Limit.getValueOr(std::numeric_limits<uint32_t>::max());
// Return results from both indexes but avoid duplicates.
// We might return stale relations from the static index;
// we don't currently have a good way of identifying them.
llvm::DenseSet<std::pair<SymbolID, SymbolID>> SeenRelations;
Dynamic->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
Callback(Subject, Object);
SeenRelations.insert(std::make_pair(Subject, Object.ID));
--Remaining;
});
if (Remaining == 0)
return;
Static->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
if (Remaining > 0 &&
!SeenRelations.count(std::make_pair(Subject, Object.ID))) {
--Remaining;
Callback(Subject, Object);
}
});
}
// Returns true if \p L is (strictly) preferred to \p R (e.g. by file paths). If
// neither is preferred, this returns false.
static bool prefer(const SymbolLocation &L, const SymbolLocation &R) {
if (!L)
return false;
if (!R)
return true;
auto HasCodeGenSuffix = [](const SymbolLocation &Loc) {
constexpr static const char *CodegenSuffixes[] = {".proto"};
return std::any_of(std::begin(CodegenSuffixes), std::end(CodegenSuffixes),
[&](llvm::StringRef Suffix) {
return llvm::StringRef(Loc.FileURI).endswith(Suffix);
});
};
return HasCodeGenSuffix(L) && !HasCodeGenSuffix(R);
}
Symbol mergeSymbol(const Symbol &L, const Symbol &R) {
assert(L.ID == R.ID);
// We prefer information from TUs that saw the definition.
// Classes: this is the def itself. Functions: hopefully the header decl.
// If both did (or both didn't), continue to prefer L over R.
bool PreferR = R.Definition && !L.Definition;
// Merge include headers only if both have definitions or both have no
// definition; otherwise, only accumulate references of common includes.
assert(L.Definition.FileURI && R.Definition.FileURI);
bool MergeIncludes =
bool(*L.Definition.FileURI) == bool(*R.Definition.FileURI);
Symbol S = PreferR ? R : L; // The target symbol we're merging into.
const Symbol &O = PreferR ? L : R; // The "other" less-preferred symbol.
// Only use locations in \p O if it's (strictly) preferred.
if (prefer(O.CanonicalDeclaration, S.CanonicalDeclaration))
S.CanonicalDeclaration = O.CanonicalDeclaration;
if (prefer(O.Definition, S.Definition))
S.Definition = O.Definition;
S.References += O.References;
if (S.Signature == "")
S.Signature = O.Signature;
if (S.CompletionSnippetSuffix == "")
S.CompletionSnippetSuffix = O.CompletionSnippetSuffix;
if (S.Documentation == "") {
// Don't accept documentation from bare forward class declarations, if there
// is a definition and it didn't provide one. S is often an undocumented
// class, and O is a non-canonical forward decl preceded by an irrelevant
// comment.
bool IsClass = S.SymInfo.Kind == index::SymbolKind::Class ||
S.SymInfo.Kind == index::SymbolKind::Struct ||
S.SymInfo.Kind == index::SymbolKind::Union;
if (!IsClass || !S.Definition)
S.Documentation = O.Documentation;
}
if (S.ReturnType == "")
S.ReturnType = O.ReturnType;
if (S.Type == "")
S.Type = O.Type;
for (const auto &OI : O.IncludeHeaders) {
bool Found = false;
for (auto &SI : S.IncludeHeaders) {
if (SI.IncludeHeader == OI.IncludeHeader) {
Found = true;
SI.References += OI.References;
break;
}
}
if (!Found && MergeIncludes)
S.IncludeHeaders.emplace_back(OI.IncludeHeader, OI.References);
}
S.Origin |= O.Origin | SymbolOrigin::Merge;
S.Flags |= O.Flags;
return S;
}
} // namespace clangd
} // namespace clang
|