Skip to content

Commit bea110d

Browse files
[clangd] Strip invalid fromRanges for outgoing calls (#134657)
`CallHierarchyOutgoingCall::fromRanges` are interpreted as ranges in the same file as the item for which 'outgoingCalls' was called. It's possible for outgoing calls to be in a different file than that item if the item is just a declaration (e.g. in a header file). Now, such calls are dropped instead of being returned to the client. This is the same as the change made in #111616, but now for outgoing calls. Fixes clangd/clangd#2350 --------- Co-authored-by: Nathan Ridge <[email protected]>
1 parent 886f119 commit bea110d

File tree

2 files changed

+35
-11
lines changed

2 files changed

+35
-11
lines changed

clang-tools-extra/clangd/XRefs.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2380,7 +2380,7 @@ outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
23802380
// Initially store the ranges in a map keyed by SymbolID of the callee.
23812381
// This allows us to group different calls to the same function
23822382
// into the same CallHierarchyOutgoingCall.
2383-
llvm::DenseMap<SymbolID, std::vector<Range>> CallsOut;
2383+
llvm::DenseMap<SymbolID, std::vector<Location>> CallsOut;
23842384
// We can populate the ranges based on a refs request only. As we do so, we
23852385
// also accumulate the callee IDs into a lookup request.
23862386
LookupRequest CallsOutLookup;
@@ -2390,8 +2390,8 @@ outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
23902390
elog("outgoingCalls failed to convert location: {0}", Loc.takeError());
23912391
return;
23922392
}
2393-
auto It = CallsOut.try_emplace(R.Symbol, std::vector<Range>{}).first;
2394-
It->second.push_back(Loc->range);
2393+
auto It = CallsOut.try_emplace(R.Symbol, std::vector<Location>{}).first;
2394+
It->second.push_back(*Loc);
23952395

23962396
CallsOutLookup.IDs.insert(R.Symbol);
23972397
});
@@ -2411,9 +2411,22 @@ outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
24112411

24122412
auto It = CallsOut.find(Callee.ID);
24132413
assert(It != CallsOut.end());
2414-
if (auto CHI = symbolToCallHierarchyItem(Callee, Item.uri.file()))
2414+
if (auto CHI = symbolToCallHierarchyItem(Callee, Item.uri.file())) {
2415+
std::vector<Range> FromRanges;
2416+
for (const Location &L : It->second) {
2417+
if (L.uri != Item.uri) {
2418+
// Call location not in same file as the item that outgoingCalls was
2419+
// requested for. This can happen when Item is a declaration separate
2420+
// from the implementation. There's not much we can do, since the
2421+
// protocol only allows returning ranges interpreted as being in
2422+
// Item's file.
2423+
continue;
2424+
}
2425+
FromRanges.push_back(L.range);
2426+
}
24152427
Results.push_back(
2416-
CallHierarchyOutgoingCall{std::move(*CHI), std::move(It->second)});
2428+
CallHierarchyOutgoingCall{std::move(*CHI), std::move(FromRanges)});
2429+
}
24172430
});
24182431
// Sort results by name of the callee.
24192432
llvm::sort(Results, [](const CallHierarchyOutgoingCall &A,

clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ using ::testing::UnorderedElementsAre;
4545
// Helpers for matching call hierarchy data structures.
4646
MATCHER_P(withName, N, "") { return arg.name == N; }
4747
MATCHER_P(withDetail, N, "") { return arg.detail == N; }
48+
MATCHER_P(withFile, N, "") { return arg.uri.file() == N; }
4849
MATCHER_P(withSelectionRange, R, "") { return arg.selectionRange == R; }
4950

5051
template <class ItemMatcher>
@@ -383,18 +384,28 @@ TEST(CallHierarchy, MultiFileCpp) {
383384
EXPECT_THAT(IncomingLevel4, IsEmpty());
384385
};
385386

386-
auto CheckOutgoingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
387+
auto CheckOutgoingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath,
388+
bool IsDeclaration) {
387389
std::vector<CallHierarchyItem> Items =
388390
prepareCallHierarchy(AST, Pos, TUPath);
389-
ASSERT_THAT(Items, ElementsAre(withName("caller3")));
391+
ASSERT_THAT(
392+
Items,
393+
ElementsAre(AllOf(
394+
withName("caller3"),
395+
withFile(testPath(IsDeclaration ? "caller3.hh" : "caller3.cc")))));
390396
auto OutgoingLevel1 = outgoingCalls(Items[0], Index.get());
391397
ASSERT_THAT(
392398
OutgoingLevel1,
399+
// fromRanges are interpreted in the context of Items[0]'s file.
400+
// If that's the header, we can't get ranges from the implementation
401+
// file!
393402
ElementsAre(
394403
AllOf(to(AllOf(withName("caller1"), withDetail("nsa::caller1"))),
395-
oFromRanges(Caller3C.range("Caller1"))),
404+
IsDeclaration ? oFromRanges()
405+
: oFromRanges(Caller3C.range("Caller1"))),
396406
AllOf(to(AllOf(withName("caller2"), withDetail("nsb::caller2"))),
397-
oFromRanges(Caller3C.range("Caller2")))));
407+
IsDeclaration ? oFromRanges()
408+
: oFromRanges(Caller3C.range("Caller2")))));
398409

399410
auto OutgoingLevel2 = outgoingCalls(OutgoingLevel1[1].to, Index.get());
400411
ASSERT_THAT(OutgoingLevel2,
@@ -423,15 +434,15 @@ TEST(CallHierarchy, MultiFileCpp) {
423434
CheckIncomingCalls(*AST, CalleeH.point(), testPath("callee.hh"));
424435
AST = Workspace.openFile("caller3.hh");
425436
ASSERT_TRUE(bool(AST));
426-
CheckOutgoingCalls(*AST, Caller3H.point(), testPath("caller3.hh"));
437+
CheckOutgoingCalls(*AST, Caller3H.point(), testPath("caller3.hh"), true);
427438

428439
// Check that invoking from the definition site works.
429440
AST = Workspace.openFile("callee.cc");
430441
ASSERT_TRUE(bool(AST));
431442
CheckIncomingCalls(*AST, CalleeC.point(), testPath("callee.cc"));
432443
AST = Workspace.openFile("caller3.cc");
433444
ASSERT_TRUE(bool(AST));
434-
CheckOutgoingCalls(*AST, Caller3C.point(), testPath("caller3.cc"));
445+
CheckOutgoingCalls(*AST, Caller3C.point(), testPath("caller3.cc"), false);
435446
}
436447

437448
TEST(CallHierarchy, IncomingMultiFileObjC) {

0 commit comments

Comments
 (0)