Skip to content

Commit 378e4ed

Browse files
authored
[clangd] Show callers of base functions in incomingCalls (#163024)
If call hierarchy incoming calls is invoked on a virtual function, clangd now returns the callers of base functions as well. The patch also introduces a protocol extension to annotate such calls differently (as they may or may not actually call the target function), so that clients can visualize these callers differently if they wish.
1 parent 0da6cca commit 378e4ed

File tree

17 files changed

+316
-45
lines changed

17 files changed

+316
-45
lines changed

clang-tools-extra/clangd/Protocol.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,6 +1627,12 @@ struct CallHierarchyIncomingCall {
16271627
/// The range at which the calls appear.
16281628
/// This is relative to the caller denoted by `From`.
16291629
std::vector<Range> fromRanges;
1630+
1631+
/// For the case of being a virtual function we also return calls
1632+
/// to the base function. This caller might be a false positive.
1633+
/// We currently have no way of discerning this.
1634+
/// This is a clangd extension.
1635+
bool mightNeverCall = false;
16301636
};
16311637
llvm::json::Value toJSON(const CallHierarchyIncomingCall &);
16321638

clang-tools-extra/clangd/XRefs.cpp

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "clang-include-cleaner/Types.h"
2222
#include "index/Index.h"
2323
#include "index/Merge.h"
24+
#include "index/Ref.h"
2425
#include "index/Relation.h"
2526
#include "index/SymbolCollector.h"
2627
#include "index/SymbolID.h"
@@ -56,6 +57,7 @@
5657
#include "clang/Tooling/Syntax/Tokens.h"
5758
#include "llvm/ADT/ArrayRef.h"
5859
#include "llvm/ADT/DenseMap.h"
60+
#include "llvm/ADT/DenseSet.h"
5961
#include "llvm/ADT/STLExtras.h"
6062
#include "llvm/ADT/ScopeExit.h"
6163
#include "llvm/ADT/SmallSet.h"
@@ -66,6 +68,7 @@
6668
#include "llvm/Support/ErrorHandling.h"
6769
#include "llvm/Support/Path.h"
6870
#include "llvm/Support/raw_ostream.h"
71+
#include <algorithm>
6972
#include <optional>
7073
#include <string>
7174
#include <vector>
@@ -2350,51 +2353,64 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
23502353
// to an AST node isn't cheap, particularly when the declaration isn't
23512354
// in the main file.
23522355
// FIXME: Consider also using AST information when feasible.
2353-
RefsRequest Request;
2354-
Request.IDs.insert(*ID);
2355-
Request.WantContainer = true;
2356-
// We could restrict more specifically to calls by introducing a new RefKind,
2357-
// but non-call references (such as address-of-function) can still be
2358-
// interesting as they can indicate indirect calls.
2359-
Request.Filter = RefKind::Reference;
2360-
// Initially store the ranges in a map keyed by SymbolID of the caller.
2361-
// This allows us to group different calls with the same caller
2362-
// into the same CallHierarchyIncomingCall.
2363-
llvm::DenseMap<SymbolID, std::vector<Location>> CallsIn;
2364-
// We can populate the ranges based on a refs request only. As we do so, we
2365-
// also accumulate the container IDs into a lookup request.
2366-
LookupRequest ContainerLookup;
2367-
Index->refs(Request, [&](const Ref &R) {
2368-
auto Loc = indexToLSPLocation(R.Location, Item.uri.file());
2369-
if (!Loc) {
2370-
elog("incomingCalls failed to convert location: {0}", Loc.takeError());
2371-
return;
2372-
}
2373-
CallsIn[R.Container].push_back(*Loc);
2356+
auto QueryIndex = [&](llvm::DenseSet<SymbolID> IDs, bool MightNeverCall) {
2357+
RefsRequest Request;
2358+
Request.IDs = std::move(IDs);
2359+
Request.WantContainer = true;
2360+
// We could restrict more specifically to calls by introducing a new
2361+
// RefKind, but non-call references (such as address-of-function) can still
2362+
// be interesting as they can indicate indirect calls.
2363+
Request.Filter = RefKind::Reference;
2364+
// Initially store the ranges in a map keyed by SymbolID of the caller.
2365+
// This allows us to group different calls with the same caller
2366+
// into the same CallHierarchyIncomingCall.
2367+
llvm::DenseMap<SymbolID, std::vector<Location>> CallsIn;
2368+
// We can populate the ranges based on a refs request only. As we do so, we
2369+
// also accumulate the container IDs into a lookup request.
2370+
LookupRequest ContainerLookup;
2371+
Index->refs(Request, [&](const Ref &R) {
2372+
auto Loc = indexToLSPLocation(R.Location, Item.uri.file());
2373+
if (!Loc) {
2374+
elog("incomingCalls failed to convert location: {0}", Loc.takeError());
2375+
return;
2376+
}
2377+
CallsIn[R.Container].push_back(*Loc);
23742378

2375-
ContainerLookup.IDs.insert(R.Container);
2376-
});
2377-
// Perform the lookup request and combine its results with CallsIn to
2378-
// get complete CallHierarchyIncomingCall objects.
2379-
Index->lookup(ContainerLookup, [&](const Symbol &Caller) {
2380-
auto It = CallsIn.find(Caller.ID);
2381-
assert(It != CallsIn.end());
2382-
if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) {
2383-
std::vector<Range> FromRanges;
2384-
for (const Location &L : It->second) {
2385-
if (L.uri != CHI->uri) {
2386-
// Call location not in same file as caller.
2387-
// This can happen in some edge cases. There's not much we can do,
2388-
// since the protocol only allows returning ranges interpreted as
2389-
// being in the caller's file.
2390-
continue;
2379+
ContainerLookup.IDs.insert(R.Container);
2380+
});
2381+
// Perform the lookup request and combine its results with CallsIn to
2382+
// get complete CallHierarchyIncomingCall objects.
2383+
Index->lookup(ContainerLookup, [&](const Symbol &Caller) {
2384+
auto It = CallsIn.find(Caller.ID);
2385+
assert(It != CallsIn.end());
2386+
if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) {
2387+
std::vector<Range> FromRanges;
2388+
for (const Location &L : It->second) {
2389+
if (L.uri != CHI->uri) {
2390+
// Call location not in same file as caller.
2391+
// This can happen in some edge cases. There's not much we can do,
2392+
// since the protocol only allows returning ranges interpreted as
2393+
// being in the caller's file.
2394+
continue;
2395+
}
2396+
FromRanges.push_back(L.range);
23912397
}
2392-
FromRanges.push_back(L.range);
2398+
Results.push_back(CallHierarchyIncomingCall{
2399+
std::move(*CHI), std::move(FromRanges), MightNeverCall});
23932400
}
2394-
Results.push_back(
2395-
CallHierarchyIncomingCall{std::move(*CHI), std::move(FromRanges)});
2396-
}
2397-
});
2401+
});
2402+
};
2403+
QueryIndex({ID.get()}, false);
2404+
// In the case of being a virtual function we also want to return
2405+
// potential calls through the base function.
2406+
if (Item.kind == SymbolKind::Method) {
2407+
llvm::DenseSet<SymbolID> IDs;
2408+
RelationsRequest Req{{ID.get()}, RelationKind::OverriddenBy, std::nullopt};
2409+
Index->reverseRelations(Req, [&](const SymbolID &, const Symbol &Caller) {
2410+
IDs.insert(Caller.ID);
2411+
});
2412+
QueryIndex(std::move(IDs), true);
2413+
}
23982414
// Sort results by name of container.
23992415
llvm::sort(Results, [](const CallHierarchyIncomingCall &A,
24002416
const CallHierarchyIncomingCall &B) {

clang-tools-extra/clangd/index/Index.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ void SwapIndex::relations(
7777
return snapshot()->relations(R, CB);
7878
}
7979

80+
void SwapIndex::reverseRelations(
81+
const RelationsRequest &R,
82+
llvm::function_ref<void(const SymbolID &, const Symbol &)> CB) const {
83+
return snapshot()->reverseRelations(R, CB);
84+
}
85+
8086
llvm::unique_function<IndexContents(llvm::StringRef) const>
8187
SwapIndex::indexedFiles() const {
8288
// The index snapshot should outlive this method return value.

clang-tools-extra/clangd/index/Index.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,14 @@ class SymbolIndex {
181181
llvm::function_ref<void(const SymbolID &Subject, const Symbol &Object)>
182182
Callback) const = 0;
183183

184+
/// Finds all relations (O, P, S) stored in the index such that S is among
185+
/// Req.Subjects and P is Req.Predicate, and invokes \p Callback for (S, O) in
186+
/// each. Currently only allows the OverriddenBy relation.
187+
virtual void reverseRelations(
188+
const RelationsRequest &Req,
189+
llvm::function_ref<void(const SymbolID &Subject, const Symbol &Object)>
190+
Callback) const = 0;
191+
184192
/// Returns function which checks if the specified file was used to build this
185193
/// index or not. The function must only be called while the index is alive.
186194
using IndexedFiles =
@@ -214,6 +222,11 @@ class SwapIndex : public SymbolIndex {
214222
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
215223
const override;
216224

225+
void
226+
reverseRelations(const RelationsRequest &,
227+
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
228+
const override;
229+
217230
llvm::unique_function<IndexContents(llvm::StringRef) const>
218231
indexedFiles() const override;
219232

clang-tools-extra/clangd/index/MemIndex.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,27 @@ void MemIndex::relations(
125125
}
126126
}
127127

128+
void MemIndex::reverseRelations(
129+
const RelationsRequest &Req,
130+
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
131+
assert(Req.Predicate == RelationKind::OverriddenBy);
132+
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
133+
for (const SymbolID &Subject : Req.Subjects) {
134+
LookupRequest LookupReq;
135+
auto It = ReverseRelations.find(
136+
std::make_pair(Subject, static_cast<uint8_t>(Req.Predicate)));
137+
if (It != ReverseRelations.end()) {
138+
for (const auto &Obj : It->second) {
139+
if (Remaining > 0) {
140+
--Remaining;
141+
LookupReq.IDs.insert(Obj);
142+
}
143+
}
144+
}
145+
lookup(LookupReq, [&](const Symbol &Object) { Callback(Subject, Object); });
146+
}
147+
}
148+
128149
llvm::unique_function<IndexContents(llvm::StringRef) const>
129150
MemIndex::indexedFiles() const {
130151
return [this](llvm::StringRef FileURI) {
@@ -134,7 +155,8 @@ MemIndex::indexedFiles() const {
134155

135156
size_t MemIndex::estimateMemoryUsage() const {
136157
return Index.getMemorySize() + Refs.getMemorySize() +
137-
Relations.getMemorySize() + BackingDataSize;
158+
Relations.getMemorySize() + ReverseRelations.getMemorySize() +
159+
BackingDataSize;
138160
}
139161

140162
} // namespace clangd

clang-tools-extra/clangd/index/MemIndex.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_MEMINDEX_H
1111

1212
#include "index/Index.h"
13+
#include "index/Relation.h"
1314
#include "llvm/ADT/StringSet.h"
1415
#include <mutex>
1516

@@ -27,10 +28,16 @@ class MemIndex : public SymbolIndex {
2728
Index[S.ID] = &S;
2829
for (const std::pair<SymbolID, llvm::ArrayRef<Ref>> &R : Refs)
2930
this->Refs.try_emplace(R.first, R.second.begin(), R.second.end());
30-
for (const Relation &R : Relations)
31+
for (const Relation &R : Relations) {
3132
this->Relations[std::make_pair(R.Subject,
3233
static_cast<uint8_t>(R.Predicate))]
3334
.push_back(R.Object);
35+
if (R.Predicate == RelationKind::OverriddenBy) {
36+
this->ReverseRelations[std::make_pair(
37+
R.Object, static_cast<uint8_t>(R.Predicate))]
38+
.push_back(R.Subject);
39+
}
40+
}
3441
}
3542
// Symbols are owned by BackingData, Index takes ownership.
3643
template <typename SymbolRange, typename RefRange, typename RelationRange,
@@ -80,6 +87,11 @@ class MemIndex : public SymbolIndex {
8087
llvm::function_ref<void(const SymbolID &, const Symbol &)>
8188
Callback) const override;
8289

90+
void
91+
reverseRelations(const RelationsRequest &Req,
92+
llvm::function_ref<void(const SymbolID &, const Symbol &)>
93+
Callback) const override;
94+
8395
llvm::unique_function<IndexContents(llvm::StringRef) const>
8496
indexedFiles() const override;
8597

@@ -94,6 +106,9 @@ class MemIndex : public SymbolIndex {
94106
static_assert(sizeof(RelationKind) == sizeof(uint8_t),
95107
"RelationKind should be of same size as a uint8_t");
96108
llvm::DenseMap<std::pair<SymbolID, uint8_t>, std::vector<SymbolID>> Relations;
109+
// Reverse relations, currently only for OverriddenBy
110+
llvm::DenseMap<std::pair<SymbolID, uint8_t>, std::vector<SymbolID>>
111+
ReverseRelations;
97112
// Set of files which were used during this index build.
98113
llvm::StringSet<> Files;
99114
// Contents of the index (symbols, references, etc.)

clang-tools-extra/clangd/index/Merge.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,32 @@ void MergedIndex::relations(
221221
});
222222
}
223223

224+
void MergedIndex::reverseRelations(
225+
const RelationsRequest &Req,
226+
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
227+
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
228+
// Return results from both indexes but avoid duplicates.
229+
// We might return stale relations from the static index;
230+
// we don't currently have a good way of identifying them.
231+
llvm::DenseSet<std::pair<SymbolID, SymbolID>> SeenRelations;
232+
Dynamic->reverseRelations(
233+
Req, [&](const SymbolID &Subject, const Symbol &Object) {
234+
Callback(Subject, Object);
235+
SeenRelations.insert(std::make_pair(Subject, Object.ID));
236+
--Remaining;
237+
});
238+
if (Remaining == 0)
239+
return;
240+
Static->reverseRelations(
241+
Req, [&](const SymbolID &Subject, const Symbol &Object) {
242+
if (Remaining > 0 &&
243+
!SeenRelations.count(std::make_pair(Subject, Object.ID))) {
244+
--Remaining;
245+
Callback(Subject, Object);
246+
}
247+
});
248+
}
249+
224250
// Returns true if \p L is (strictly) preferred to \p R (e.g. by file paths). If
225251
// neither is preferred, this returns false.
226252
static bool prefer(const SymbolLocation &L, const SymbolLocation &R) {

clang-tools-extra/clangd/index/Merge.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ class MergedIndex : public SymbolIndex {
4444
void relations(const RelationsRequest &,
4545
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
4646
const override;
47+
void
48+
reverseRelations(const RelationsRequest &,
49+
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
50+
const override;
4751
llvm::unique_function<IndexContents(llvm::StringRef) const>
4852
indexedFiles() const override;
4953
size_t estimateMemoryUsage() const override {

clang-tools-extra/clangd/index/ProjectAware.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ class ProjectAwareIndex : public SymbolIndex {
5151
llvm::function_ref<void(const SymbolID &, const Symbol &)>
5252
Callback) const override;
5353

54+
void
55+
reverseRelations(const RelationsRequest &,
56+
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
57+
const override;
58+
5459
llvm::unique_function<IndexContents(llvm::StringRef) const>
5560
indexedFiles() const override;
5661

@@ -124,6 +129,14 @@ void ProjectAwareIndex::relations(
124129
return Idx->relations(Req, Callback);
125130
}
126131

132+
void ProjectAwareIndex::reverseRelations(
133+
const RelationsRequest &Req,
134+
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
135+
trace::Span Tracer("ProjectAwareIndex::relations");
136+
if (auto *Idx = getIndex())
137+
return Idx->reverseRelations(Req, Callback);
138+
}
139+
127140
llvm::unique_function<IndexContents(llvm::StringRef) const>
128141
ProjectAwareIndex::indexedFiles() const {
129142
trace::Span Tracer("ProjectAwareIndex::indexedFiles");

clang-tools-extra/clangd/index/dex/Dex.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,28 @@ void Dex::relations(
379379
}
380380
}
381381

382+
void Dex::reverseRelations(
383+
const RelationsRequest &Req,
384+
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
385+
trace::Span Tracer("Dex reverseRelations");
386+
assert(Req.Predicate == RelationKind::OverriddenBy);
387+
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
388+
for (const SymbolID &Subject : Req.Subjects) {
389+
LookupRequest LookupReq;
390+
auto It = ReverseRelations.find(
391+
std::make_pair(Subject, static_cast<uint8_t>(Req.Predicate)));
392+
if (It != ReverseRelations.end()) {
393+
for (const auto &Obj : It->second) {
394+
if (Remaining > 0) {
395+
--Remaining;
396+
LookupReq.IDs.insert(Obj);
397+
}
398+
}
399+
}
400+
lookup(LookupReq, [&](const Symbol &Object) { Callback(Subject, Object); });
401+
}
402+
}
403+
382404
llvm::unique_function<IndexContents(llvm::StringRef) const>
383405
Dex::indexedFiles() const {
384406
return [this](llvm::StringRef FileURI) {
@@ -396,6 +418,7 @@ size_t Dex::estimateMemoryUsage() const {
396418
Bytes += Refs.getMemorySize();
397419
Bytes += RevRefs.size() * sizeof(RevRef);
398420
Bytes += Relations.getMemorySize();
421+
Bytes += ReverseRelations.getMemorySize();
399422
return Bytes + BackingDataSize;
400423
}
401424

0 commit comments

Comments
 (0)