Author: Hampus Adolfsson
Date: 2025-04-24T03:28:35-04:00
New Revision: bea110db3ed1fa1215bb8e22d2057019fcbd2d16

URL: 
https://github.com/llvm/llvm-project/commit/bea110db3ed1fa1215bb8e22d2057019fcbd2d16
DIFF: 
https://github.com/llvm/llvm-project/commit/bea110db3ed1fa1215bb8e22d2057019fcbd2d16.diff

LOG: [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 <zeratul...@hotmail.com>

Added: 
    

Modified: 
    clang-tools-extra/clangd/XRefs.cpp
    clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/XRefs.cpp 
b/clang-tools-extra/clangd/XRefs.cpp
index 053e2c044c774..089f8158c9aa5 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -2380,7 +2380,7 @@ outgoingCalls(const CallHierarchyItem &Item, const 
SymbolIndex *Index) {
   // Initially store the ranges in a map keyed by SymbolID of the callee.
   // This allows us to group 
diff erent calls to the same function
   // into the same CallHierarchyOutgoingCall.
-  llvm::DenseMap<SymbolID, std::vector<Range>> CallsOut;
+  llvm::DenseMap<SymbolID, std::vector<Location>> 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;
@@ -2390,8 +2390,8 @@ outgoingCalls(const CallHierarchyItem &Item, const 
SymbolIndex *Index) {
       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);
+    auto It = CallsOut.try_emplace(R.Symbol, std::vector<Location>{}).first;
+    It->second.push_back(*Loc);
 
     CallsOutLookup.IDs.insert(R.Symbol);
   });
@@ -2411,9 +2411,22 @@ outgoingCalls(const CallHierarchyItem &Item, const 
SymbolIndex *Index) {
 
     auto It = CallsOut.find(Callee.ID);
     assert(It != CallsOut.end());
-    if (auto CHI = symbolToCallHierarchyItem(Callee, Item.uri.file()))
+    if (auto CHI = symbolToCallHierarchyItem(Callee, Item.uri.file())) {
+      std::vector<Range> FromRanges;
+      for (const Location &L : It->second) {
+        if (L.uri != Item.uri) {
+          // Call location not in same file as the item that outgoingCalls was
+          // requested for. This can happen when Item is a declaration separate
+          // from the implementation. There's not much we can do, since the
+          // protocol only allows returning ranges interpreted as being in
+          // Item's file.
+          continue;
+        }
+        FromRanges.push_back(L.range);
+      }
       Results.push_back(
-          CallHierarchyOutgoingCall{std::move(*CHI), std::move(It->second)});
+          CallHierarchyOutgoingCall{std::move(*CHI), std::move(FromRanges)});
+    }
   });
   // Sort results by name of the callee.
   llvm::sort(Results, [](const CallHierarchyOutgoingCall &A,

diff  --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp 
b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
index 316b94305c9ae..eb852ef5ee00b 100644
--- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
@@ -45,6 +45,7 @@ using ::testing::UnorderedElementsAre;
 // Helpers for matching call hierarchy data structures.
 MATCHER_P(withName, N, "") { return arg.name == N; }
 MATCHER_P(withDetail, N, "") { return arg.detail == N; }
+MATCHER_P(withFile, N, "") { return arg.uri.file() == N; }
 MATCHER_P(withSelectionRange, R, "") { return arg.selectionRange == R; }
 
 template <class ItemMatcher>
@@ -383,18 +384,28 @@ TEST(CallHierarchy, MultiFileCpp) {
     EXPECT_THAT(IncomingLevel4, IsEmpty());
   };
 
-  auto CheckOutgoingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
+  auto CheckOutgoingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath,
+                                bool IsDeclaration) {
     std::vector<CallHierarchyItem> Items =
         prepareCallHierarchy(AST, Pos, TUPath);
-    ASSERT_THAT(Items, ElementsAre(withName("caller3")));
+    ASSERT_THAT(
+        Items,
+        ElementsAre(AllOf(
+            withName("caller3"),
+            withFile(testPath(IsDeclaration ? "caller3.hh" : "caller3.cc")))));
     auto OutgoingLevel1 = outgoingCalls(Items[0], Index.get());
     ASSERT_THAT(
         OutgoingLevel1,
+        // fromRanges are interpreted in the context of Items[0]'s file.
+        // If that's the header, we can't get ranges from the implementation
+        // file!
         ElementsAre(
             AllOf(to(AllOf(withName("caller1"), withDetail("nsa::caller1"))),
-                  oFromRanges(Caller3C.range("Caller1"))),
+                  IsDeclaration ? oFromRanges()
+                                : oFromRanges(Caller3C.range("Caller1"))),
             AllOf(to(AllOf(withName("caller2"), withDetail("nsb::caller2"))),
-                  oFromRanges(Caller3C.range("Caller2")))));
+                  IsDeclaration ? oFromRanges()
+                                : oFromRanges(Caller3C.range("Caller2")))));
 
     auto OutgoingLevel2 = outgoingCalls(OutgoingLevel1[1].to, Index.get());
     ASSERT_THAT(OutgoingLevel2,
@@ -423,7 +434,7 @@ TEST(CallHierarchy, MultiFileCpp) {
   CheckIncomingCalls(*AST, CalleeH.point(), testPath("callee.hh"));
   AST = Workspace.openFile("caller3.hh");
   ASSERT_TRUE(bool(AST));
-  CheckOutgoingCalls(*AST, Caller3H.point(), testPath("caller3.hh"));
+  CheckOutgoingCalls(*AST, Caller3H.point(), testPath("caller3.hh"), true);
 
   // Check that invoking from the definition site works.
   AST = Workspace.openFile("callee.cc");
@@ -431,7 +442,7 @@ TEST(CallHierarchy, MultiFileCpp) {
   CheckIncomingCalls(*AST, CalleeC.point(), testPath("callee.cc"));
   AST = Workspace.openFile("caller3.cc");
   ASSERT_TRUE(bool(AST));
-  CheckOutgoingCalls(*AST, Caller3C.point(), testPath("caller3.cc"));
+  CheckOutgoingCalls(*AST, Caller3C.point(), testPath("caller3.cc"), false);
 }
 
 TEST(CallHierarchy, IncomingMultiFileObjC) {


        
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to