llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang-tools-extra @llvm/pr-subscribers-clangd Author: Christian Kandeler (ckandeler) <details> <summary>Changes</summary> --- Patch is 23.27 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/91191.diff 7 Files Affected: - (modified) clang-tools-extra/clangd/ClangdLSPServer.cpp (+7) - (modified) clang-tools-extra/clangd/ClangdLSPServer.h (+3) - (modified) clang-tools-extra/clangd/ClangdServer.cpp (+13) - (modified) clang-tools-extra/clangd/ClangdServer.h (+4) - (modified) clang-tools-extra/clangd/XRefs.cpp (+71) - (modified) clang-tools-extra/clangd/XRefs.h (+3) - (modified) clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp (+192-34) ``````````diff diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 7fd599d4e1a0b0..5820a644088e3e 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -1368,6 +1368,12 @@ void ClangdLSPServer::onCallHierarchyIncomingCalls( Server->incomingCalls(Params.item, std::move(Reply)); } +void ClangdLSPServer::onCallHierarchyOutgoingCalls( + const CallHierarchyOutgoingCallsParams &Params, + Callback<std::vector<CallHierarchyOutgoingCall>> Reply) { + Server->outgoingCalls(Params.item, std::move(Reply)); +} + void ClangdLSPServer::onClangdInlayHints(const InlayHintsParams &Params, Callback<llvm::json::Value> Reply) { // Our extension has a different representation on the wire than the standard. @@ -1688,6 +1694,7 @@ 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); + 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); diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index 8bcb29522509b7..4981027372cb57 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -153,6 +153,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>>); diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index 1c4c2a79b5c051..19d01dfbd873e2 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -898,6 +898,19 @@ void ClangdServer::incomingCalls( }); } +void ClangdServer::outgoingCalls( + const CallHierarchyItem &Item, + Callback<std::vector<CallHierarchyOutgoingCall>> CB) { + auto Action = [Item, + CB = std::move(CB)](Expected<InputsAndAST> InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::outgoingCalls(InpAST->AST, Item)); + }; + WorkScheduler->runWithAST("Outgoing Calls", Item.uri.file(), + std::move(Action)); +} + void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange, Callback<std::vector<InlayHint>> CB) { auto Action = [RestrictRange(std::move(RestrictRange)), diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 1661028be88b4e..4caef917c1ec16 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -288,6 +288,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>>); diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index cd909266489a85..b9bf944a7bba98 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -42,6 +42,7 @@ #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtVisitor.h" #include "clang/AST/Type.h" +#include "clang/Analysis/CallGraph.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" @@ -2303,6 +2304,76 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { return Results; } +std::vector<CallHierarchyOutgoingCall> +outgoingCalls(ParsedAST &AST, const CallHierarchyItem &Item) { + if (AST.tuPath() != Item.uri.file()) + return {}; + + const auto &SM = AST.getSourceManager(); + auto Loc = sourceLocationInMainFile(SM, Item.selectionRange.start); + if (!Loc) { + elog("outgoingCalls failed to convert position to source location: " + "{0}", + Loc.takeError()); + return {}; + } + + // For user convenience, we allow cursors on declarations, in which case + // we will try to find the definition. + std::optional<std::pair<const NamedDecl *, const NamedDecl *>> DeclAndDef; + for (const NamedDecl *Decl : getDeclAtPosition(AST, *Loc, {})) { + if (getSymbolID(Decl).str() != Item.data) + continue; + DeclAndDef = std::make_pair(Decl, getDefinition(Decl)); + break; + } + if (!DeclAndDef || !DeclAndDef->second) + return {}; + + // Collect calls. + CallGraph callGraph; + callGraph.addToCallGraph(const_cast<NamedDecl *>(DeclAndDef->second)); + llvm::DenseMap<NamedDecl *, std::vector<Range>> CallsOut; + for (const CallGraphNode::CallRecord &CallRecord : + callGraph.getRoot()->callees()) { + if (CallRecord.Callee->getDecl() != DeclAndDef->first) + continue; + for (const CallGraphNode::CallRecord &CallRecord : + CallRecord.Callee->callees()) { + SourceLocation BeginLoc = CallRecord.CallExpr->getBeginLoc(); + if (auto *M = dyn_cast_if_present<ObjCMessageExpr>(CallRecord.CallExpr)) { + BeginLoc = M->getSelectorStartLoc(); + } else if (auto *M = dyn_cast_if_present<CXXMemberCallExpr>( + CallRecord.CallExpr)) { + if (auto ME = dyn_cast_if_present<MemberExpr>(M->getCallee())) + BeginLoc = ME->getMemberLoc(); + } + BeginLoc = SM.getFileLoc(BeginLoc); + Position NameBegin = sourceLocToPosition(SM, BeginLoc); + Position NameEnd = sourceLocToPosition( + SM, Lexer::getLocForEndOfToken(BeginLoc, 0, SM, + AST.getASTContext().getLangOpts())); + CallsOut[static_cast<NamedDecl *>(CallRecord.Callee->getDecl())] + .push_back(Range{NameBegin, NameEnd}); + } + break; + } + + // Create and sort items. + std::vector<CallHierarchyOutgoingCall> Results; + for (auto It = CallsOut.begin(); It != CallsOut.end(); ++It) { + if (auto CHI = declToCallHierarchyItem(*It->first, Item.uri.file())) { + Results.push_back(CallHierarchyOutgoingCall{std::move(*CHI), It->second}); + } + } + 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()) diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h index df91dd15303c18..73c22ab7673ad0 100644 --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -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(ParsedAST &AST, const CallHierarchyItem &Item); + /// Returns all decls that are referenced in the \p FD except local symbols. llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST, const FunctionDecl *FD); diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp index 6fa76aa6094bf2..0ec96599ccc43f 100644 --- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp +++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp @@ -50,11 +50,21 @@ template <class ItemMatcher> ::testing::Matcher<CallHierarchyIncomingCall> from(ItemMatcher M) { return Field(&CallHierarchyIncomingCall::from, M); } +template <class ItemMatcher> +::testing::Matcher<CallHierarchyOutgoingCall> to(ItemMatcher M) { + return Field(&CallHierarchyOutgoingCall::to, M); +} template <class... RangeMatchers> -::testing::Matcher<CallHierarchyIncomingCall> fromRanges(RangeMatchers... M) { +::testing::Matcher<CallHierarchyIncomingCall> fromRangesIn(RangeMatchers... M) { return Field(&CallHierarchyIncomingCall::fromRanges, UnorderedElementsAre(M...)); } +template <class... RangeMatchers> +::testing::Matcher<CallHierarchyOutgoingCall> +fromRangesOut(RangeMatchers... M) { + return Field(&CallHierarchyOutgoingCall::fromRanges, + UnorderedElementsAre(M...)); +} TEST(CallHierarchy, IncomingOneFileCpp) { Annotations Source(R"cpp( @@ -81,24 +91,61 @@ TEST(CallHierarchy, IncomingOneFileCpp) { auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller1")), - fromRanges(Source.range("Callee"))))); + fromRangesIn(Source.range("Callee"))))); auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); ASSERT_THAT(IncomingLevel2, ElementsAre(AllOf(from(withName("caller2")), - fromRanges(Source.range("Caller1A"), - Source.range("Caller1B"))), + fromRangesIn(Source.range("Caller1A"), + Source.range("Caller1B"))), AllOf(from(withName("caller3")), - fromRanges(Source.range("Caller1C"))))); + fromRangesIn(Source.range("Caller1C"))))); auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); ASSERT_THAT(IncomingLevel3, ElementsAre(AllOf(from(withName("caller3")), - fromRanges(Source.range("Caller2"))))); + fromRangesIn(Source.range("Caller2"))))); auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get()); EXPECT_THAT(IncomingLevel4, IsEmpty()); } +TEST(CallHierarchy, OutgoingOneFileCpp) { + Annotations Source(R"cpp( + void calle^r1(); + void terminator1() {} + void terminator2() { struct S { S() { terminator1(); } }; } + void caller2() { + $Terminator1A[[terminator1]](); + $Terminator1B[[terminator1]](); + $Terminator2[[terminator2]](); + } + void caller1() { + $Caller2[[caller2]](); + } + )cpp"); + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + std::vector<CallHierarchyItem> Items = + prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); + ASSERT_THAT(Items, ElementsAre(withName("caller1"))); + auto OutgoingLevel1 = outgoingCalls(AST, Items[0]); + ASSERT_THAT(OutgoingLevel1, + ElementsAre(AllOf(to(withName("caller2")), + fromRangesOut(Source.range("Caller2"))))); + auto OutgoingLevel2 = outgoingCalls(AST, OutgoingLevel1[0].to); + ASSERT_THAT(OutgoingLevel2, + ElementsAre(AllOf(to(withName("terminator1")), + fromRangesOut(Source.range("Terminator1A"), + Source.range("Terminator1B"))), + AllOf(to(withName("terminator2")), + fromRangesOut(Source.range("Terminator2"))))); + auto OutgoingLevel3a = outgoingCalls(AST, OutgoingLevel2[0].to); + EXPECT_THAT(OutgoingLevel3a, IsEmpty()); + auto OutgoingLevel3b = outgoingCalls(AST, OutgoingLevel2[1].to); + EXPECT_THAT(OutgoingLevel3b, IsEmpty()); +} + TEST(CallHierarchy, IncomingOneFileObjC) { Annotations Source(R"objc( @implementation MyClass {} @@ -126,24 +173,66 @@ TEST(CallHierarchy, IncomingOneFileObjC) { auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller1")), - fromRanges(Source.range("Callee"))))); + fromRangesIn(Source.range("Callee"))))); auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); ASSERT_THAT(IncomingLevel2, ElementsAre(AllOf(from(withName("caller2")), - fromRanges(Source.range("Caller1A"), - Source.range("Caller1B"))), + fromRangesIn(Source.range("Caller1A"), + Source.range("Caller1B"))), AllOf(from(withName("caller3")), - fromRanges(Source.range("Caller1C"))))); + fromRangesIn(Source.range("Caller1C"))))); auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); ASSERT_THAT(IncomingLevel3, ElementsAre(AllOf(from(withName("caller3")), - fromRanges(Source.range("Caller2"))))); + fromRangesIn(Source.range("Caller2"))))); auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get()); EXPECT_THAT(IncomingLevel4, IsEmpty()); } +TEST(CallHierarchy, OutgoingOneFileObjC) { + Annotations Source(R"objc( + @implementation MyClass {} + +(void)callee {} + +(void) caller1 { + [MyClass $Callee[[callee]]]; + } + +(void) caller2 { + [MyClass $Caller1A[[caller1]]]; + [MyClass $Caller1B[[caller1]]]; + } + +(void) calle^r3 { + [MyClass $Caller1C[[caller1]]]; + [MyClass $Caller2[[caller2]]]; + } + @end + )objc"); + TestTU TU = TestTU::withCode(Source.code()); + TU.Filename = "TestTU.m"; + auto AST = TU.build(); + std::vector<CallHierarchyItem> Items = + prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); + ASSERT_THAT(Items, ElementsAre(withName("caller3"))); + auto OutgoingLevel1 = outgoingCalls(AST, Items[0]); + ASSERT_THAT(OutgoingLevel1, + ElementsAre(AllOf(to(withName("caller1")), + fromRangesOut(Source.range("Caller1C"))), + AllOf(to(withName("caller2")), + fromRangesOut(Source.range("Caller2"))))); + auto OutgoingLevel2a = outgoingCalls(AST, OutgoingLevel1[0].to); + ASSERT_THAT(OutgoingLevel2a, + ElementsAre(AllOf(to(withName("callee")), + fromRangesOut(Source.range("Callee"))))); + auto OutgoingLevel2b = outgoingCalls(AST, OutgoingLevel1[1].to); + ASSERT_THAT(OutgoingLevel2b, + ElementsAre(AllOf(to(withName("caller1")), + fromRangesOut(Source.range("Caller1A"), + Source.range("Caller1B"))))); + auto OutgoingLevel3 = outgoingCalls(AST, OutgoingLevel2a[0].to); + EXPECT_THAT(OutgoingLevel3, IsEmpty()); +} + TEST(CallHierarchy, MainFileOnlyRef) { // In addition to testing that we store refs to main-file only symbols, // this tests that anonymous namespaces do not interfere with the @@ -169,12 +258,12 @@ TEST(CallHierarchy, MainFileOnlyRef) { auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller1")), - fromRanges(Source.range("Callee"))))); + fromRangesIn(Source.range("Callee"))))); auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); EXPECT_THAT(IncomingLevel2, ElementsAre(AllOf(from(withName("caller2")), - fromRanges(Source.range("Caller1"))))); + fromRangesIn(Source.range("Caller1"))))); } TEST(CallHierarchy, IncomingQualified) { @@ -202,9 +291,39 @@ TEST(CallHierarchy, IncomingQualified) { auto Incoming = incomingCalls(Items[0], Index.get()); EXPECT_THAT(Incoming, ElementsAre(AllOf(from(withName("caller1")), - fromRanges(Source.range("Caller1"))), + fromRangesIn(Source.range("Caller1"))), AllOf(from(withName("caller2")), - fromRanges(Source.range("Caller2"))))); + fromRangesIn(Source.range("Caller2"))))); +} + +TEST(CallHierarchy, OutgoingQualified) { + Annotations Source(R"cpp( + namespace ns { + class Waldo { + public: + void find() { $Find[[locate]](); } + private: + void locate() {} + }; + void c^aller(Waldo &W) { + W.$Caller[[find]](); + } + } + )cpp"); + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + std::vector<CallHierarchyItem> Items = + prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); + ASSERT_THAT(Items, ElementsAre(withName("caller"))); + auto OutgoingLevel1 = outgoingCalls(AST, Items[0]); + EXPECT_THAT(OutgoingLevel1, + ElementsAre(AllOf(to(withName("find")), + fromRangesOut(Source.range("Caller"))))); + auto OutgoingLevel2 = outgoingCalls(AST, OutgoingLevel1[0].to); + EXPECT_THAT(OutgoingLevel2, + ElementsAre(AllOf(to(withName("locate")), + fromRangesOut(Source.range("Find"))))); } TEST(CallHierarchy, IncomingMultiFileCpp) { @@ -268,20 +387,20 @@ TEST(CallHierarchy, IncomingMultiFileCpp) { auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller1")), - fromRanges(Caller1C.range())))); + fromRangesIn(Caller1C.range())))); auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); - ASSERT_THAT( - IncomingLevel2, - ElementsAre(AllOf(from(withName("caller2")), - fromRanges(Caller2C.range("A"), Caller2C.range("B"))), - AllOf(from(withName("caller3")), - fromRanges(Caller3C.range("Caller1"))))); + ASSERT_THAT(IncomingLevel2, + ElementsAre(AllOf(from(withName("caller2")), + fromRangesIn(Caller2C.range("A"), + Caller2C.range("B"))), + AllOf(from(withName("caller3")), + fromRangesIn(Caller3C.range("Caller1"))))); auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); ASSERT_THAT(IncomingLevel3, ElementsAre(AllOf(from(withName("caller3")), - fromRanges(Caller3C.range("Caller2"))))); + fromRangesIn(Caller3C.range("Caller2"))))); auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get()); EXPECT_THAT(IncomingLevel4, IsEmpty()); @@ -303,6 +422,44 @@ TEST(CallHierarchy, IncomingMultiFileCpp) { CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.cc")); } +TEST(CallHierarchy, OutgoingMultiFileCpp) { + Annotations CalleeH(R"cpp( + void calleeFwd() { $CalleeFwd[[callee]](0); } + void callee(int); + )cpp"); + Annotations CalleeC(R"cpp( + #include "callee.hh" + void callee(int) {} + )cpp"); + Annotations Caller(R"cpp( + #include "callee.hh" + ... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/91191 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits