1 //===-- CallHierarchyTests.cpp ---------------------------*- C++ -*-------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 #include "Annotations.h"
9 #include "Compiler.h"
10 #include "Matchers.h"
11 #include "ParsedAST.h"
12 #include "SyncAPI.h"
13 #include "TestFS.h"
14 #include "TestTU.h"
15 #include "TestWorkspace.h"
16 #include "XRefs.h"
17 #include "index/FileIndex.h"
18 #include "index/SymbolCollector.h"
19 #include "clang/AST/DeclCXX.h"
20 #include "clang/AST/DeclTemplate.h"
21 #include "clang/Index/IndexingAction.h"
22 #include "llvm/Support/Path.h"
23 #include "llvm/Support/ScopedPrinter.h"
24 #include "gmock/gmock.h"
25 #include "gtest/gtest.h"
26
27 namespace clang {
28 namespace clangd {
29 namespace {
30
31 using ::testing::AllOf;
32 using ::testing::ElementsAre;
33 using ::testing::Field;
34 using ::testing::IsEmpty;
35 using ::testing::Matcher;
36 using ::testing::UnorderedElementsAre;
37
38 // Helpers for matching call hierarchy data structures.
39 MATCHER_P(WithName, N, "") { return arg.name == N; }
40 MATCHER_P(WithSelectionRange, R, "") { return arg.selectionRange == R; }
41
42 template <class ItemMatcher>
From(ItemMatcher M)43 ::testing::Matcher<CallHierarchyIncomingCall> From(ItemMatcher M) {
44 return Field(&CallHierarchyIncomingCall::from, M);
45 }
46 template <class... RangeMatchers>
FromRanges(RangeMatchers...M)47 ::testing::Matcher<CallHierarchyIncomingCall> FromRanges(RangeMatchers... M) {
48 return Field(&CallHierarchyIncomingCall::fromRanges,
49 UnorderedElementsAre(M...));
50 }
51
TEST(CallHierarchy,IncomingOneFile)52 TEST(CallHierarchy, IncomingOneFile) {
53 Annotations Source(R"cpp(
54 void call^ee(int);
55 void caller1() {
56 $Callee[[callee]](42);
57 }
58 void caller2() {
59 $Caller1A[[caller1]]();
60 $Caller1B[[caller1]]();
61 }
62 void caller3() {
63 $Caller1C[[caller1]]();
64 $Caller2[[caller2]]();
65 }
66 )cpp");
67 TestTU TU = TestTU::withCode(Source.code());
68 auto AST = TU.build();
69 auto Index = TU.index();
70
71 std::vector<CallHierarchyItem> Items =
72 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
73 ASSERT_THAT(Items, ElementsAre(WithName("callee")));
74 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
75 ASSERT_THAT(IncomingLevel1,
76 ElementsAre(AllOf(From(WithName("caller1")),
77 FromRanges(Source.range("Callee")))));
78
79 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
80 ASSERT_THAT(IncomingLevel2,
81 ElementsAre(AllOf(From(WithName("caller2")),
82 FromRanges(Source.range("Caller1A"),
83 Source.range("Caller1B"))),
84 AllOf(From(WithName("caller3")),
85 FromRanges(Source.range("Caller1C")))));
86
87 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
88 ASSERT_THAT(IncomingLevel3,
89 ElementsAre(AllOf(From(WithName("caller3")),
90 FromRanges(Source.range("Caller2")))));
91
92 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
93 EXPECT_THAT(IncomingLevel4, IsEmpty());
94 }
95
TEST(CallHierarchy,MainFileOnlyRef)96 TEST(CallHierarchy, MainFileOnlyRef) {
97 // In addition to testing that we store refs to main-file only symbols,
98 // this tests that anonymous namespaces do not interfere with the
99 // symbol re-identification process in callHierarchyItemToSymbo().
100 Annotations Source(R"cpp(
101 void call^ee(int);
102 namespace {
103 void caller1() {
104 $Callee[[callee]](42);
105 }
106 }
107 void caller2() {
108 $Caller1[[caller1]]();
109 }
110 )cpp");
111 TestTU TU = TestTU::withCode(Source.code());
112 auto AST = TU.build();
113 auto Index = TU.index();
114
115 std::vector<CallHierarchyItem> Items =
116 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
117 ASSERT_THAT(Items, ElementsAre(WithName("callee")));
118 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
119 ASSERT_THAT(IncomingLevel1,
120 ElementsAre(AllOf(From(WithName("caller1")),
121 FromRanges(Source.range("Callee")))));
122
123 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
124 EXPECT_THAT(IncomingLevel2,
125 ElementsAre(AllOf(From(WithName("caller2")),
126 FromRanges(Source.range("Caller1")))));
127 }
128
TEST(CallHierarchy,IncomingQualified)129 TEST(CallHierarchy, IncomingQualified) {
130 Annotations Source(R"cpp(
131 namespace ns {
132 struct Waldo {
133 void find();
134 };
135 void Waldo::find() {}
136 void caller1(Waldo &W) {
137 W.$Caller1[[f^ind]]();
138 }
139 void caller2(Waldo &W) {
140 W.$Caller2[[find]]();
141 }
142 }
143 )cpp");
144 TestTU TU = TestTU::withCode(Source.code());
145 auto AST = TU.build();
146 auto Index = TU.index();
147
148 std::vector<CallHierarchyItem> Items =
149 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
150 ASSERT_THAT(Items, ElementsAre(WithName("Waldo::find")));
151 auto Incoming = incomingCalls(Items[0], Index.get());
152 EXPECT_THAT(Incoming,
153 ElementsAre(AllOf(From(WithName("caller1")),
154 FromRanges(Source.range("Caller1"))),
155 AllOf(From(WithName("caller2")),
156 FromRanges(Source.range("Caller2")))));
157 }
158
TEST(CallHierarchy,IncomingMultiFile)159 TEST(CallHierarchy, IncomingMultiFile) {
160 // The test uses a .hh suffix for header files to get clang
161 // to parse them in C++ mode. .h files are parsed in C mode
162 // by default, which causes problems because e.g. symbol
163 // USRs are different in C mode (do not include function signatures).
164
165 Annotations CalleeH(R"cpp(
166 void calle^e(int);
167 )cpp");
168 Annotations CalleeC(R"cpp(
169 #include "callee.hh"
170 void calle^e(int) {}
171 )cpp");
172 Annotations Caller1H(R"cpp(
173 void caller1();
174 )cpp");
175 Annotations Caller1C(R"cpp(
176 #include "callee.hh"
177 #include "caller1.hh"
178 void caller1() {
179 [[calle^e]](42);
180 }
181 )cpp");
182 Annotations Caller2H(R"cpp(
183 void caller2();
184 )cpp");
185 Annotations Caller2C(R"cpp(
186 #include "caller1.hh"
187 #include "caller2.hh"
188 void caller2() {
189 $A[[caller1]]();
190 $B[[caller1]]();
191 }
192 )cpp");
193 Annotations Caller3C(R"cpp(
194 #include "caller1.hh"
195 #include "caller2.hh"
196 void caller3() {
197 $Caller1[[caller1]]();
198 $Caller2[[caller2]]();
199 }
200 )cpp");
201
202 TestWorkspace Workspace;
203 Workspace.addSource("callee.hh", CalleeH.code());
204 Workspace.addSource("caller1.hh", Caller1H.code());
205 Workspace.addSource("caller2.hh", Caller2H.code());
206 Workspace.addMainFile("callee.cc", CalleeC.code());
207 Workspace.addMainFile("caller1.cc", Caller1C.code());
208 Workspace.addMainFile("caller2.cc", Caller2C.code());
209 Workspace.addMainFile("caller3.cc", Caller3C.code());
210
211 auto Index = Workspace.index();
212
213 auto CheckCallHierarchy = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
214 std::vector<CallHierarchyItem> Items =
215 prepareCallHierarchy(AST, Pos, TUPath);
216 ASSERT_THAT(Items, ElementsAre(WithName("callee")));
217 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
218 ASSERT_THAT(IncomingLevel1,
219 ElementsAre(AllOf(From(WithName("caller1")),
220 FromRanges(Caller1C.range()))));
221
222 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
223 ASSERT_THAT(
224 IncomingLevel2,
225 ElementsAre(AllOf(From(WithName("caller2")),
226 FromRanges(Caller2C.range("A"), Caller2C.range("B"))),
227 AllOf(From(WithName("caller3")),
228 FromRanges(Caller3C.range("Caller1")))));
229
230 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
231 ASSERT_THAT(IncomingLevel3,
232 ElementsAre(AllOf(From(WithName("caller3")),
233 FromRanges(Caller3C.range("Caller2")))));
234
235 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
236 EXPECT_THAT(IncomingLevel4, IsEmpty());
237 };
238
239 // Check that invoking from a call site works.
240 auto AST = Workspace.openFile("caller1.cc");
241 ASSERT_TRUE(bool(AST));
242 CheckCallHierarchy(*AST, Caller1C.point(), testPath("caller1.cc"));
243
244 // Check that invoking from the declaration site works.
245 AST = Workspace.openFile("callee.hh");
246 ASSERT_TRUE(bool(AST));
247 CheckCallHierarchy(*AST, CalleeH.point(), testPath("callee.hh"));
248
249 // Check that invoking from the definition site works.
250 AST = Workspace.openFile("callee.cc");
251 ASSERT_TRUE(bool(AST));
252 CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.cc"));
253 }
254
255 } // namespace
256 } // namespace clangd
257 } // namespace clang
258