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 "ParsedAST.h"
10#include "TestFS.h"
11#include "TestTU.h"
12#include "TestWorkspace.h"
13#include "XRefs.h"
14#include "llvm/Support/Path.h"
15#include "gmock/gmock.h"
16#include "gtest/gtest.h"
17
18namespace clang {
19namespace clangd {
20
21llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
22 const CallHierarchyItem &Item) {
23 return Stream << Item.name << "@" << Item.selectionRange;
24}
25
26llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
27 const CallHierarchyIncomingCall &Call) {
28 Stream << "{ from: " << Call.from << ", ranges: [";
29 for (const auto &R : Call.fromRanges) {
30 Stream << R;
31 Stream << ", ";
32 }
33 return Stream << "] }";
34}
35
36namespace {
37
38using ::testing::AllOf;
39using ::testing::ElementsAre;
40using ::testing::Field;
41using ::testing::IsEmpty;
42using ::testing::Matcher;
43using ::testing::UnorderedElementsAre;
44
45// Helpers for matching call hierarchy data structures.
46MATCHER_P(withName, N, "") { return arg.name == N; }
47MATCHER_P(withSelectionRange, R, "") { return arg.selectionRange == R; }
48
49template <class ItemMatcher>
50::testing::Matcher<CallHierarchyIncomingCall> from(ItemMatcher M) {
51 return Field(&CallHierarchyIncomingCall::from, M);
52}
53template <class... RangeMatchers>
54::testing::Matcher<CallHierarchyIncomingCall> fromRanges(RangeMatchers... M) {
55 return Field(&CallHierarchyIncomingCall::fromRanges,
56 UnorderedElementsAre(M...));
57}
58
59TEST(CallHierarchy, IncomingOneFileCpp) {
60 Annotations Source(R"cpp(
61 void call^ee(int);
62 void caller1() {
63 $Callee[[callee]](42);
64 }
65 void caller2() {
66 $Caller1A[[caller1]]();
67 $Caller1B[[caller1]]();
68 }
69 void caller3() {
70 $Caller1C[[caller1]]();
71 $Caller2[[caller2]]();
72 }
73 )cpp");
74 TestTU TU = TestTU::withCode(Code: Source.code());
75 auto AST = TU.build();
76 auto Index = TU.index();
77
78 std::vector<CallHierarchyItem> Items =
79 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
80 ASSERT_THAT(Items, ElementsAre(withName("callee")));
81 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: Index.get());
82 ASSERT_THAT(IncomingLevel1,
83 ElementsAre(AllOf(from(withName("caller1")),
84 fromRanges(Source.range("Callee")))));
85 auto IncomingLevel2 = incomingCalls(Item: IncomingLevel1[0].from, Index: Index.get());
86 ASSERT_THAT(IncomingLevel2,
87 ElementsAre(AllOf(from(withName("caller2")),
88 fromRanges(Source.range("Caller1A"),
89 Source.range("Caller1B"))),
90 AllOf(from(withName("caller3")),
91 fromRanges(Source.range("Caller1C")))));
92
93 auto IncomingLevel3 = incomingCalls(Item: IncomingLevel2[0].from, Index: Index.get());
94 ASSERT_THAT(IncomingLevel3,
95 ElementsAre(AllOf(from(withName("caller3")),
96 fromRanges(Source.range("Caller2")))));
97
98 auto IncomingLevel4 = incomingCalls(Item: IncomingLevel3[0].from, Index: Index.get());
99 EXPECT_THAT(IncomingLevel4, IsEmpty());
100}
101
102TEST(CallHierarchy, IncomingOneFileObjC) {
103 Annotations Source(R"objc(
104 @implementation MyClass {}
105 +(void)call^ee {}
106 +(void) caller1 {
107 [MyClass $Callee[[callee]]];
108 }
109 +(void) caller2 {
110 [MyClass $Caller1A[[caller1]]];
111 [MyClass $Caller1B[[caller1]]];
112 }
113 +(void) caller3 {
114 [MyClass $Caller1C[[caller1]]];
115 [MyClass $Caller2[[caller2]]];
116 }
117 @end
118 )objc");
119 TestTU TU = TestTU::withCode(Code: Source.code());
120 TU.Filename = "TestTU.m";
121 auto AST = TU.build();
122 auto Index = TU.index();
123 std::vector<CallHierarchyItem> Items =
124 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
125 ASSERT_THAT(Items, ElementsAre(withName("callee")));
126 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: Index.get());
127 ASSERT_THAT(IncomingLevel1,
128 ElementsAre(AllOf(from(withName("caller1")),
129 fromRanges(Source.range("Callee")))));
130 auto IncomingLevel2 = incomingCalls(Item: IncomingLevel1[0].from, Index: Index.get());
131 ASSERT_THAT(IncomingLevel2,
132 ElementsAre(AllOf(from(withName("caller2")),
133 fromRanges(Source.range("Caller1A"),
134 Source.range("Caller1B"))),
135 AllOf(from(withName("caller3")),
136 fromRanges(Source.range("Caller1C")))));
137
138 auto IncomingLevel3 = incomingCalls(Item: IncomingLevel2[0].from, Index: Index.get());
139 ASSERT_THAT(IncomingLevel3,
140 ElementsAre(AllOf(from(withName("caller3")),
141 fromRanges(Source.range("Caller2")))));
142
143 auto IncomingLevel4 = incomingCalls(Item: IncomingLevel3[0].from, Index: Index.get());
144 EXPECT_THAT(IncomingLevel4, IsEmpty());
145}
146
147TEST(CallHierarchy, MainFileOnlyRef) {
148 // In addition to testing that we store refs to main-file only symbols,
149 // this tests that anonymous namespaces do not interfere with the
150 // symbol re-identification process in callHierarchyItemToSymbo().
151 Annotations Source(R"cpp(
152 void call^ee(int);
153 namespace {
154 void caller1() {
155 $Callee[[callee]](42);
156 }
157 }
158 void caller2() {
159 $Caller1[[caller1]]();
160 }
161 )cpp");
162 TestTU TU = TestTU::withCode(Code: Source.code());
163 auto AST = TU.build();
164 auto Index = TU.index();
165
166 std::vector<CallHierarchyItem> Items =
167 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
168 ASSERT_THAT(Items, ElementsAre(withName("callee")));
169 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: Index.get());
170 ASSERT_THAT(IncomingLevel1,
171 ElementsAre(AllOf(from(withName("caller1")),
172 fromRanges(Source.range("Callee")))));
173
174 auto IncomingLevel2 = incomingCalls(Item: IncomingLevel1[0].from, Index: Index.get());
175 EXPECT_THAT(IncomingLevel2,
176 ElementsAre(AllOf(from(withName("caller2")),
177 fromRanges(Source.range("Caller1")))));
178}
179
180TEST(CallHierarchy, IncomingQualified) {
181 Annotations Source(R"cpp(
182 namespace ns {
183 struct Waldo {
184 void find();
185 };
186 void Waldo::find() {}
187 void caller1(Waldo &W) {
188 W.$Caller1[[f^ind]]();
189 }
190 void caller2(Waldo &W) {
191 W.$Caller2[[find]]();
192 }
193 }
194 )cpp");
195 TestTU TU = TestTU::withCode(Code: Source.code());
196 auto AST = TU.build();
197 auto Index = TU.index();
198
199 std::vector<CallHierarchyItem> Items =
200 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
201 ASSERT_THAT(Items, ElementsAre(withName("Waldo::find")));
202 auto Incoming = incomingCalls(Item: Items[0], Index: Index.get());
203 EXPECT_THAT(Incoming,
204 ElementsAre(AllOf(from(withName("caller1")),
205 fromRanges(Source.range("Caller1"))),
206 AllOf(from(withName("caller2")),
207 fromRanges(Source.range("Caller2")))));
208}
209
210TEST(CallHierarchy, IncomingMultiFileCpp) {
211 // The test uses a .hh suffix for header files to get clang
212 // to parse them in C++ mode. .h files are parsed in C mode
213 // by default, which causes problems because e.g. symbol
214 // USRs are different in C mode (do not include function signatures).
215
216 Annotations CalleeH(R"cpp(
217 void calle^e(int);
218 )cpp");
219 Annotations CalleeC(R"cpp(
220 #include "callee.hh"
221 void calle^e(int) {}
222 )cpp");
223 Annotations Caller1H(R"cpp(
224 void caller1();
225 )cpp");
226 Annotations Caller1C(R"cpp(
227 #include "callee.hh"
228 #include "caller1.hh"
229 void caller1() {
230 [[calle^e]](42);
231 }
232 )cpp");
233 Annotations Caller2H(R"cpp(
234 void caller2();
235 )cpp");
236 Annotations Caller2C(R"cpp(
237 #include "caller1.hh"
238 #include "caller2.hh"
239 void caller2() {
240 $A[[caller1]]();
241 $B[[caller1]]();
242 }
243 )cpp");
244 Annotations Caller3C(R"cpp(
245 #include "caller1.hh"
246 #include "caller2.hh"
247 void caller3() {
248 $Caller1[[caller1]]();
249 $Caller2[[caller2]]();
250 }
251 )cpp");
252
253 TestWorkspace Workspace;
254 Workspace.addSource(Filename: "callee.hh", Code: CalleeH.code());
255 Workspace.addSource(Filename: "caller1.hh", Code: Caller1H.code());
256 Workspace.addSource(Filename: "caller2.hh", Code: Caller2H.code());
257 Workspace.addMainFile(Filename: "callee.cc", Code: CalleeC.code());
258 Workspace.addMainFile(Filename: "caller1.cc", Code: Caller1C.code());
259 Workspace.addMainFile(Filename: "caller2.cc", Code: Caller2C.code());
260 Workspace.addMainFile(Filename: "caller3.cc", Code: Caller3C.code());
261
262 auto Index = Workspace.index();
263
264 auto CheckCallHierarchy = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
265 std::vector<CallHierarchyItem> Items =
266 prepareCallHierarchy(AST, Pos, TUPath);
267 ASSERT_THAT(Items, ElementsAre(withName("callee")));
268 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: Index.get());
269 ASSERT_THAT(IncomingLevel1,
270 ElementsAre(AllOf(from(withName("caller1")),
271 fromRanges(Caller1C.range()))));
272
273 auto IncomingLevel2 = incomingCalls(Item: IncomingLevel1[0].from, Index: Index.get());
274 ASSERT_THAT(
275 IncomingLevel2,
276 ElementsAre(AllOf(from(withName("caller2")),
277 fromRanges(Caller2C.range("A"), Caller2C.range("B"))),
278 AllOf(from(withName("caller3")),
279 fromRanges(Caller3C.range("Caller1")))));
280
281 auto IncomingLevel3 = incomingCalls(Item: IncomingLevel2[0].from, Index: Index.get());
282 ASSERT_THAT(IncomingLevel3,
283 ElementsAre(AllOf(from(withName("caller3")),
284 fromRanges(Caller3C.range("Caller2")))));
285
286 auto IncomingLevel4 = incomingCalls(Item: IncomingLevel3[0].from, Index: Index.get());
287 EXPECT_THAT(IncomingLevel4, IsEmpty());
288 };
289
290 // Check that invoking from a call site works.
291 auto AST = Workspace.openFile(Filename: "caller1.cc");
292 ASSERT_TRUE(bool(AST));
293 CheckCallHierarchy(*AST, Caller1C.point(), testPath(File: "caller1.cc"));
294
295 // Check that invoking from the declaration site works.
296 AST = Workspace.openFile(Filename: "callee.hh");
297 ASSERT_TRUE(bool(AST));
298 CheckCallHierarchy(*AST, CalleeH.point(), testPath(File: "callee.hh"));
299
300 // Check that invoking from the definition site works.
301 AST = Workspace.openFile(Filename: "callee.cc");
302 ASSERT_TRUE(bool(AST));
303 CheckCallHierarchy(*AST, CalleeC.point(), testPath(File: "callee.cc"));
304}
305
306TEST(CallHierarchy, IncomingMultiFileObjC) {
307 // The test uses a .mi suffix for header files to get clang
308 // to parse them in ObjC mode. .h files are parsed in C mode
309 // by default, which causes problems because e.g. symbol
310 // USRs are different in C mode (do not include function signatures).
311
312 Annotations CalleeH(R"objc(
313 @interface CalleeClass
314 +(void)call^ee;
315 @end
316 )objc");
317 Annotations CalleeC(R"objc(
318 #import "callee.mi"
319 @implementation CalleeClass {}
320 +(void)call^ee {}
321 @end
322 )objc");
323 Annotations Caller1H(R"objc(
324 @interface Caller1Class
325 +(void)caller1;
326 @end
327 )objc");
328 Annotations Caller1C(R"objc(
329 #import "callee.mi"
330 #import "caller1.mi"
331 @implementation Caller1Class {}
332 +(void)caller1 {
333 [CalleeClass [[calle^e]]];
334 }
335 @end
336 )objc");
337 Annotations Caller2H(R"objc(
338 @interface Caller2Class
339 +(void)caller2;
340 @end
341 )objc");
342 Annotations Caller2C(R"objc(
343 #import "caller1.mi"
344 #import "caller2.mi"
345 @implementation Caller2Class {}
346 +(void)caller2 {
347 [Caller1Class $A[[caller1]]];
348 [Caller1Class $B[[caller1]]];
349 }
350 @end
351 )objc");
352 Annotations Caller3C(R"objc(
353 #import "caller1.mi"
354 #import "caller2.mi"
355 @implementation Caller3Class {}
356 +(void)caller3 {
357 [Caller1Class $Caller1[[caller1]]];
358 [Caller2Class $Caller2[[caller2]]];
359 }
360 @end
361 )objc");
362
363 TestWorkspace Workspace;
364 Workspace.addSource(Filename: "callee.mi", Code: CalleeH.code());
365 Workspace.addSource(Filename: "caller1.mi", Code: Caller1H.code());
366 Workspace.addSource(Filename: "caller2.mi", Code: Caller2H.code());
367 Workspace.addMainFile(Filename: "callee.m", Code: CalleeC.code());
368 Workspace.addMainFile(Filename: "caller1.m", Code: Caller1C.code());
369 Workspace.addMainFile(Filename: "caller2.m", Code: Caller2C.code());
370 Workspace.addMainFile(Filename: "caller3.m", Code: Caller3C.code());
371 auto Index = Workspace.index();
372
373 auto CheckCallHierarchy = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
374 std::vector<CallHierarchyItem> Items =
375 prepareCallHierarchy(AST, Pos, TUPath);
376 ASSERT_THAT(Items, ElementsAre(withName("callee")));
377 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: Index.get());
378 ASSERT_THAT(IncomingLevel1,
379 ElementsAre(AllOf(from(withName("caller1")),
380 fromRanges(Caller1C.range()))));
381
382 auto IncomingLevel2 = incomingCalls(Item: IncomingLevel1[0].from, Index: Index.get());
383 ASSERT_THAT(
384 IncomingLevel2,
385 ElementsAre(AllOf(from(withName("caller2")),
386 fromRanges(Caller2C.range("A"), Caller2C.range("B"))),
387 AllOf(from(withName("caller3")),
388 fromRanges(Caller3C.range("Caller1")))));
389
390 auto IncomingLevel3 = incomingCalls(Item: IncomingLevel2[0].from, Index: Index.get());
391 ASSERT_THAT(IncomingLevel3,
392 ElementsAre(AllOf(from(withName("caller3")),
393 fromRanges(Caller3C.range("Caller2")))));
394
395 auto IncomingLevel4 = incomingCalls(Item: IncomingLevel3[0].from, Index: Index.get());
396 EXPECT_THAT(IncomingLevel4, IsEmpty());
397 };
398
399 // Check that invoking from a call site works.
400 auto AST = Workspace.openFile(Filename: "caller1.m");
401 ASSERT_TRUE(bool(AST));
402 CheckCallHierarchy(*AST, Caller1C.point(), testPath(File: "caller1.m"));
403
404 // Check that invoking from the declaration site works.
405 AST = Workspace.openFile(Filename: "callee.mi");
406 ASSERT_TRUE(bool(AST));
407 CheckCallHierarchy(*AST, CalleeH.point(), testPath(File: "callee.mi"));
408
409 // Check that invoking from the definition site works.
410 AST = Workspace.openFile(Filename: "callee.m");
411 ASSERT_TRUE(bool(AST));
412 CheckCallHierarchy(*AST, CalleeC.point(), testPath(File: "callee.m"));
413}
414
415TEST(CallHierarchy, CallInLocalVarDecl) {
416 // Tests that local variable declarations are not treated as callers
417 // (they're not indexed, so they can't be represented as call hierarchy
418 // items); instead, the caller should be the containing function.
419 // However, namespace-scope variable declarations should be treated as
420 // callers because those are indexed and there is no enclosing entity
421 // that would be a useful caller.
422 Annotations Source(R"cpp(
423 int call^ee();
424 void caller1() {
425 $call1[[callee]]();
426 }
427 void caller2() {
428 int localVar = $call2[[callee]]();
429 }
430 int caller3 = $call3[[callee]]();
431 )cpp");
432 TestTU TU = TestTU::withCode(Code: Source.code());
433 auto AST = TU.build();
434 auto Index = TU.index();
435
436 std::vector<CallHierarchyItem> Items =
437 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
438 ASSERT_THAT(Items, ElementsAre(withName("callee")));
439
440 auto Incoming = incomingCalls(Item: Items[0], Index: Index.get());
441 ASSERT_THAT(
442 Incoming,
443 ElementsAre(
444 AllOf(from(withName("caller1")), fromRanges(Source.range("call1"))),
445 AllOf(from(withName("caller2")), fromRanges(Source.range("call2"))),
446 AllOf(from(withName("caller3")), fromRanges(Source.range("call3")))));
447}
448
449} // namespace
450} // namespace clangd
451} // namespace clang
452

source code of clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp