Skip to content

[clangd] Re-land "support outgoing calls in call hierarchy" #117673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Dec 4, 2024
Merged
8 changes: 8 additions & 0 deletions clang-tools-extra/clangd/ClangdLSPServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,12 @@ void ClangdLSPServer::onInlayHint(const InlayHintsParams &Params,
std::move(Reply));
}

void ClangdLSPServer::onCallHierarchyOutgoingCalls(
const CallHierarchyOutgoingCallsParams &Params,
Callback<std::vector<CallHierarchyOutgoingCall>> Reply) {
Server->outgoingCalls(Params.item, std::move(Reply));
}

void ClangdLSPServer::applyConfiguration(
const ConfigurationSettings &Settings) {
// Per-file update to the compilation database.
Expand Down Expand Up @@ -1693,6 +1699,8 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
Bind.method("typeHierarchy/subtypes", this, &ClangdLSPServer::onSubTypes);
Bind.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy);
Bind.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls);
if (Opts.EnableOutgoingCalls)
Bind.method("callHierarchy/outgoingCalls", this, &ClangdLSPServer::onCallHierarchyOutgoingCalls);
Bind.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange);
Bind.method("textDocument/documentLink", this, &ClangdLSPServer::onDocumentLink);
Bind.method("textDocument/semanticTokens/full", this, &ClangdLSPServer::onSemanticTokens);
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/ClangdLSPServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
void onCallHierarchyIncomingCalls(
const CallHierarchyIncomingCallsParams &,
Callback<std::vector<CallHierarchyIncomingCall>>);
void onCallHierarchyOutgoingCalls(
const CallHierarchyOutgoingCallsParams &,
Callback<std::vector<CallHierarchyOutgoingCall>>);
void onClangdInlayHints(const InlayHintsParams &,
Callback<llvm::json::Value>);
void onInlayHint(const InlayHintsParams &, Callback<std::vector<InlayHint>>);
Expand Down
14 changes: 13 additions & 1 deletion clang-tools-extra/clangd/ClangdServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,9 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
const ThreadsafeFS &TFS, const Options &Opts,
Callbacks *Callbacks)
: FeatureModules(Opts.FeatureModules), CDB(CDB), TFS(TFS),
DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex() : nullptr),
DynamicIdx(Opts.BuildDynamicSymbolIndex
? new FileIndex(Opts.EnableOutgoingCalls)
: nullptr),
ModulesManager(Opts.ModulesManager),
ClangTidyProvider(Opts.ClangTidyProvider),
UseDirtyHeaders(Opts.UseDirtyHeaders),
Expand Down Expand Up @@ -256,6 +258,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
Callbacks->onBackgroundIndexProgress(S);
};
BGOpts.ContextProvider = Opts.ContextProvider;
BGOpts.SupportContainedRefs = Opts.EnableOutgoingCalls;
BackgroundIdx = std::make_unique<BackgroundIndex>(
TFS, CDB,
BackgroundIndexStorage::createDiskBackedStorageFactory(
Expand Down Expand Up @@ -912,6 +915,15 @@ void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
WorkScheduler->runWithAST("InlayHints", File, std::move(Action), Transient);
}

void ClangdServer::outgoingCalls(
const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyOutgoingCall>> CB) {
WorkScheduler->run("Outgoing Calls", "",
[CB = std::move(CB), Item, this]() mutable {
CB(clangd::outgoingCalls(Item, Index));
});
}

void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
// FIXME: Do nothing for now. This will be used for indexing and potentially
// invalidating other caches.
Expand Down
9 changes: 9 additions & 0 deletions clang-tools-extra/clangd/ClangdServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ class ClangdServer {
/// Cached preambles are potentially large. If false, store them on disk.
bool StorePreamblesInMemory = true;

/// Call hierarchy's outgoing calls feature requires additional index
/// serving structures which increase memory usage. If false, these are
/// not created and the feature is not enabled.
bool EnableOutgoingCalls = true;

/// This throttler controls which preambles may be built at a given time.
clangd::PreambleThrottler *PreambleThrottler = nullptr;

Expand Down Expand Up @@ -292,6 +297,10 @@ class ClangdServer {
void incomingCalls(const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyIncomingCall>>);

/// Resolve outgoing calls for a given call hierarchy item.
void outgoingCalls(const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyOutgoingCall>>);

/// Resolve inlay hints for a given document.
void inlayHints(PathRef File, std::optional<Range> RestrictRange,
Callback<std::vector<InlayHint>>);
Expand Down
59 changes: 59 additions & 0 deletions clang-tools-extra/clangd/XRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1702,6 +1702,7 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {

HierarchyItem HI;
HI.name = printName(Ctx, ND);
// FIXME: Populate HI.detail the way we do in symbolToHierarchyItem?
HI.kind = SK;
HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()),
sourceLocToPosition(SM, DeclRange->getEnd())};
Expand Down Expand Up @@ -1753,6 +1754,7 @@ static std::optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
}
HierarchyItem HI;
HI.name = std::string(S.Name);
HI.detail = (S.Scope + S.Name).str();
HI.kind = indexSymbolKindToSymbolKind(S.SymInfo.Kind);
HI.selectionRange = Loc->range;
// FIXME: Populate 'range' correctly
Expand Down Expand Up @@ -2319,6 +2321,63 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
return Results;
}

std::vector<CallHierarchyOutgoingCall>
outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
std::vector<CallHierarchyOutgoingCall> Results;
if (!Index || Item.data.empty())
return Results;
auto ID = SymbolID::fromStr(Item.data);
if (!ID) {
elog("outgoingCalls failed to find symbol: {0}", ID.takeError());
return Results;
}
// In this function, we find outgoing calls based on the index only.
ContainedRefsRequest Request;
Request.ID = *ID;
// Initially store the ranges in a map keyed by SymbolID of the callee.
// This allows us to group different calls to the same function
// into the same CallHierarchyOutgoingCall.
llvm::DenseMap<SymbolID, std::vector<Range>> CallsOut;
// We can populate the ranges based on a refs request only. As we do so, we
// also accumulate the callee IDs into a lookup request.
LookupRequest CallsOutLookup;
Index->containedRefs(Request, [&](const auto &R) {
auto Loc = indexToLSPLocation(R.Location, Item.uri.file());
if (!Loc) {
elog("outgoingCalls failed to convert location: {0}", Loc.takeError());
return;
}
auto It = CallsOut.try_emplace(R.Symbol, std::vector<Range>{}).first;
It->second.push_back(Loc->range);

CallsOutLookup.IDs.insert(R.Symbol);
});
// Perform the lookup request and combine its results with CallsOut to
// get complete CallHierarchyOutgoingCall objects.
Index->lookup(CallsOutLookup, [&](const Symbol &Callee) {
// The containedRefs request should only return symbols which are
// function-like, i.e. symbols for which references to them can be "calls".
using SK = index::SymbolKind;
auto Kind = Callee.SymInfo.Kind;
assert(Kind == SK::Function || Kind == SK::InstanceMethod ||
Kind == SK::ClassMethod || Kind == SK::StaticMethod ||
Kind == SK::Constructor || Kind == SK::Destructor ||
Kind == SK::ConversionFunction);

auto It = CallsOut.find(Callee.ID);
assert(It != CallsOut.end());
if (auto CHI = symbolToCallHierarchyItem(Callee, Item.uri.file()))
Results.push_back(
CallHierarchyOutgoingCall{std::move(*CHI), std::move(It->second)});
});
// Sort results by name of the callee.
llvm::sort(Results, [](const CallHierarchyOutgoingCall &A,
const CallHierarchyOutgoingCall &B) {
return A.to.name < B.to.name;
});
return Results;
}

llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
const FunctionDecl *FD) {
if (!FD->hasBody())
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/XRefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath);
std::vector<CallHierarchyIncomingCall>
incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);

std::vector<CallHierarchyOutgoingCall>
outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);

/// Returns all decls that are referenced in the \p FD except local symbols.
llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
const FunctionDecl *FD);
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/index/Background.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ BackgroundIndex::BackgroundIndex(
: SwapIndex(std::make_unique<MemIndex>()), TFS(TFS), CDB(CDB),
IndexingPriority(Opts.IndexingPriority),
ContextProvider(std::move(Opts.ContextProvider)),
IndexedSymbols(IndexContents::All),
IndexedSymbols(IndexContents::All, Opts.SupportContainedRefs),
Rebuilder(this, &IndexedSymbols, Opts.ThreadPoolSize),
IndexStorageFactory(std::move(IndexStorageFactory)),
Queue(std::move(Opts.OnProgress)),
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/index/Background.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ class BackgroundIndex : public SwapIndex {
// file. Called with the empty string for other tasks.
// (When called, the context from BackgroundIndex construction is active).
std::function<Context(PathRef)> ContextProvider = nullptr;
// Whether the index needs to support the containedRefs() operation.
// May use extra memory.
bool SupportContainedRefs = true;
};

/// Creates a new background index and starts its threads.
Expand Down
13 changes: 7 additions & 6 deletions clang-tools-extra/clangd/index/FileIndex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ SlabTuple indexHeaderSymbols(llvm::StringRef Version, ASTContext &AST,
/*CollectMainFileRefs=*/false);
}

FileSymbols::FileSymbols(IndexContents IdxContents)
: IdxContents(IdxContents) {}
FileSymbols::FileSymbols(IndexContents IdxContents, bool SupportContainedRefs)
: IdxContents(IdxContents), SupportContainedRefs(SupportContainedRefs) {}

void FileSymbols::update(llvm::StringRef Key,
std::unique_ptr<SymbolSlab> Symbols,
Expand Down Expand Up @@ -395,7 +395,7 @@ FileSymbols::buildIndex(IndexType Type, DuplicateHandling DuplicateHandle,
std::move(AllRelations), std::move(Files), IdxContents,
std::make_tuple(std::move(SymbolSlabs), std::move(RefSlabs),
std::move(RefsStorage), std::move(SymsStorage)),
StorageSize);
StorageSize, SupportContainedRefs);
}
llvm_unreachable("Unknown clangd::IndexType");
}
Expand All @@ -419,11 +419,12 @@ void FileSymbols::profile(MemoryTree &MT) const {
}
}

FileIndex::FileIndex()
FileIndex::FileIndex(bool SupportContainedRefs)
: MergedIndex(&MainFileIndex, &PreambleIndex),
PreambleSymbols(IndexContents::Symbols | IndexContents::Relations),
PreambleSymbols(IndexContents::Symbols | IndexContents::Relations,
SupportContainedRefs),
PreambleIndex(std::make_unique<MemIndex>()),
MainFileSymbols(IndexContents::All),
MainFileSymbols(IndexContents::All, SupportContainedRefs),
MainFileIndex(std::make_unique<MemIndex>()) {}

void FileIndex::updatePreamble(IndexFileIn IF) {
Expand Down
5 changes: 3 additions & 2 deletions clang-tools-extra/clangd/index/FileIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ enum class DuplicateHandling {
/// locking when we swap or obtain references to snapshots.
class FileSymbols {
public:
FileSymbols(IndexContents IdxContents);
FileSymbols(IndexContents IdxContents, bool SupportContainedRefs);
/// Updates all slabs associated with the \p Key.
/// If either is nullptr, corresponding data for \p Key will be removed.
/// If CountReferences is true, \p Refs will be used for counting references
Expand All @@ -91,6 +91,7 @@ class FileSymbols {

private:
IndexContents IdxContents;
bool SupportContainedRefs;

struct RefSlabAndCountReferences {
std::shared_ptr<RefSlab> Slab;
Expand All @@ -108,7 +109,7 @@ class FileSymbols {
/// FIXME: Expose an interface to remove files that are closed.
class FileIndex : public MergedIndex {
public:
FileIndex();
FileIndex(bool SupportContainedRefs);

/// Update preamble symbols of file \p Path with all declarations in \p AST
/// and macros in \p PP.
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/clangd/index/Index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ bool SwapIndex::refs(const RefsRequest &R,
llvm::function_ref<void(const Ref &)> CB) const {
return snapshot()->refs(R, CB);
}
bool SwapIndex::containedRefs(
const ContainedRefsRequest &R,
llvm::function_ref<void(const ContainedRefsResult &)> CB) const {
return snapshot()->containedRefs(R, CB);
}
void SwapIndex::relations(
const RelationsRequest &R,
llvm::function_ref<void(const SymbolID &, const Symbol &)> CB) const {
Expand Down
35 changes: 35 additions & 0 deletions clang-tools-extra/clangd/index/Index.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,34 @@ struct RefsRequest {
bool WantContainer = false;
};

struct ContainedRefsRequest {
/// Note that RefKind::Call just restricts the matched SymbolKind to
/// functions, not the form of the reference (e.g. address-of-function,
/// which can indicate an indirect call, should still be caught).
static const RefKind SupportedRefKinds = RefKind::Call;

SymbolID ID;
/// If set, limit the number of refers returned from the index. The index may
/// choose to return less than this, e.g. it tries to avoid returning stale
/// results.
std::optional<uint32_t> Limit;
};

struct RelationsRequest {
llvm::DenseSet<SymbolID> Subjects;
RelationKind Predicate;
/// If set, limit the number of relations returned from the index.
std::optional<uint32_t> Limit;
};

struct ContainedRefsResult {
/// The source location where the symbol is named.
SymbolLocation Location;
RefKind Kind = RefKind::Unknown;
/// The ID of the symbol which is referred to
SymbolID Symbol;
};

/// Describes what data is covered by an index.
///
/// Indexes may contain symbols but not references from a file, etc.
Expand Down Expand Up @@ -141,6 +162,17 @@ class SymbolIndex {
virtual bool refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const = 0;

/// Find all symbols that are referenced by a symbol and apply
/// \p Callback on each result.
///
/// Results should be returned in arbitrary order.
/// The returned result must be deep-copied if it's used outside Callback.
///
/// Returns true if there will be more results (limited by Req.Limit);
virtual bool containedRefs(
const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)> Callback) const = 0;

/// Finds all relations (S, P, O) stored in the index such that S is among
/// Req.Subjects and P is Req.Predicate, and invokes \p Callback for (S, O) in
/// each.
Expand Down Expand Up @@ -175,6 +207,9 @@ class SwapIndex : public SymbolIndex {
llvm::function_ref<void(const Symbol &)>) const override;
bool refs(const RefsRequest &,
llvm::function_ref<void(const Ref &)>) const override;
bool containedRefs(
const ContainedRefsRequest &,
llvm::function_ref<void(const ContainedRefsResult &)>) const override;
void relations(const RelationsRequest &,
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
const override;
Expand Down
20 changes: 20 additions & 0 deletions clang-tools-extra/clangd/index/MemIndex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "MemIndex.h"
#include "FuzzyMatch.h"
#include "Quality.h"
#include "index/Index.h"
#include "support/Trace.h"

namespace clang {
Expand Down Expand Up @@ -85,6 +86,25 @@ bool MemIndex::refs(const RefsRequest &Req,
return false; // We reported all refs.
}

bool MemIndex::containedRefs(
const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)> Callback) const {
trace::Span Tracer("MemIndex refersTo");
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
for (const auto &Pair : Refs) {
for (const auto &R : Pair.second) {
if (!static_cast<int>(ContainedRefsRequest::SupportedRefKinds & R.Kind) ||
Req.ID != R.Container)
continue;
if (Remaining == 0)
return true; // More refs were available.
--Remaining;
Callback({R.Location, R.Kind, Pair.first});
}
}
return false; // We reported all refs.
}

void MemIndex::relations(
const RelationsRequest &Req,
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/clangd/index/MemIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ class MemIndex : public SymbolIndex {
bool refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const override;

bool containedRefs(const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)>
Callback) const override;

void relations(const RelationsRequest &Req,
llvm::function_ref<void(const SymbolID &, const Symbol &)>
Callback) const override;
Expand Down
Loading
Loading