clang-tools  15.0.0git
CallHierarchyTests.cpp
Go to the documentation of this file.
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 
18 namespace clang {
19 namespace clangd {
20 
21 llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
22  const CallHierarchyItem &Item) {
23  return Stream << Item.name << "@" << Item.selectionRange;
24 }
25 
26 llvm::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 
36 namespace {
37 
38 using ::testing::AllOf;
39 using ::testing::ElementsAre;
41 using ::testing::IsEmpty;
42 using ::testing::Matcher;
43 using ::testing::UnorderedElementsAre;
44 
45 // Helpers for matching call hierarchy data structures.
46 MATCHER_P(withName, N, "") { return arg.name == N; }
47 MATCHER_P(withSelectionRange, R, "") { return arg.selectionRange == R; }
48 
49 template <class ItemMatcher>
50 ::testing::Matcher<CallHierarchyIncomingCall> from(ItemMatcher M) {
52 }
53 template <class... RangeMatchers>
54 ::testing::Matcher<CallHierarchyIncomingCall> fromRanges(RangeMatchers... M) {
56  UnorderedElementsAre(M...));
57 }
58 
59 TEST(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(Source.code());
75  auto AST = TU.build();
76  auto Index = TU.index();
77 
78  std::vector<CallHierarchyItem> Items =
79  prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
80  ASSERT_THAT(Items, ElementsAre(withName("callee")));
81  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
82  ASSERT_THAT(IncomingLevel1,
83  ElementsAre(AllOf(from(withName("caller1")),
84  fromRanges(Source.range("Callee")))));
85  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, 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(IncomingLevel2[0].from, Index.get());
94  ASSERT_THAT(IncomingLevel3,
95  ElementsAre(AllOf(from(withName("caller3")),
96  fromRanges(Source.range("Caller2")))));
97 
98  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
99  EXPECT_THAT(IncomingLevel4, IsEmpty());
100 }
101 
102 TEST(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(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, Source.point(), testPath(TU.Filename));
125  ASSERT_THAT(Items, ElementsAre(withName("callee")));
126  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
127  ASSERT_THAT(IncomingLevel1,
128  ElementsAre(AllOf(from(withName("caller1")),
129  fromRanges(Source.range("Callee")))));
130  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, 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(IncomingLevel2[0].from, Index.get());
139  ASSERT_THAT(IncomingLevel3,
140  ElementsAre(AllOf(from(withName("caller3")),
141  fromRanges(Source.range("Caller2")))));
142 
143  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
144  EXPECT_THAT(IncomingLevel4, IsEmpty());
145 }
146 
147 TEST(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(Source.code());
163  auto AST = TU.build();
164  auto Index = TU.index();
165 
166  std::vector<CallHierarchyItem> Items =
167  prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
168  ASSERT_THAT(Items, ElementsAre(withName("callee")));
169  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
170  ASSERT_THAT(IncomingLevel1,
171  ElementsAre(AllOf(from(withName("caller1")),
172  fromRanges(Source.range("Callee")))));
173 
174  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
175  EXPECT_THAT(IncomingLevel2,
176  ElementsAre(AllOf(from(withName("caller2")),
177  fromRanges(Source.range("Caller1")))));
178 }
179 
180 TEST(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(Source.code());
196  auto AST = TU.build();
197  auto Index = TU.index();
198 
199  std::vector<CallHierarchyItem> Items =
200  prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
201  ASSERT_THAT(Items, ElementsAre(withName("Waldo::find")));
202  auto Incoming = incomingCalls(Items[0], 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 
210 TEST(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("callee.hh", CalleeH.code());
255  Workspace.addSource("caller1.hh", Caller1H.code());
256  Workspace.addSource("caller2.hh", Caller2H.code());
257  Workspace.addMainFile("callee.cc", CalleeC.code());
258  Workspace.addMainFile("caller1.cc", Caller1C.code());
259  Workspace.addMainFile("caller2.cc", Caller2C.code());
260  Workspace.addMainFile("caller3.cc", 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(Items[0], Index.get());
269  ASSERT_THAT(IncomingLevel1,
270  ElementsAre(AllOf(from(withName("caller1")),
271  fromRanges(Caller1C.range()))));
272 
273  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, 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(IncomingLevel2[0].from, Index.get());
282  ASSERT_THAT(IncomingLevel3,
283  ElementsAre(AllOf(from(withName("caller3")),
284  fromRanges(Caller3C.range("Caller2")))));
285 
286  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
287  EXPECT_THAT(IncomingLevel4, IsEmpty());
288  };
289 
290  // Check that invoking from a call site works.
291  auto AST = Workspace.openFile("caller1.cc");
292  ASSERT_TRUE(bool(AST));
293  CheckCallHierarchy(*AST, Caller1C.point(), testPath("caller1.cc"));
294 
295  // Check that invoking from the declaration site works.
296  AST = Workspace.openFile("callee.hh");
297  ASSERT_TRUE(bool(AST));
298  CheckCallHierarchy(*AST, CalleeH.point(), testPath("callee.hh"));
299 
300  // Check that invoking from the definition site works.
301  AST = Workspace.openFile("callee.cc");
302  ASSERT_TRUE(bool(AST));
303  CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.cc"));
304 }
305 
306 TEST(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("callee.mi", CalleeH.code());
365  Workspace.addSource("caller1.mi", Caller1H.code());
366  Workspace.addSource("caller2.mi", Caller2H.code());
367  Workspace.addMainFile("callee.m", CalleeC.code());
368  Workspace.addMainFile("caller1.m", Caller1C.code());
369  Workspace.addMainFile("caller2.m", Caller2C.code());
370  Workspace.addMainFile("caller3.m", 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(Items[0], Index.get());
378  ASSERT_THAT(IncomingLevel1,
379  ElementsAre(AllOf(from(withName("caller1")),
380  fromRanges(Caller1C.range()))));
381 
382  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, 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(IncomingLevel2[0].from, Index.get());
391  ASSERT_THAT(IncomingLevel3,
392  ElementsAre(AllOf(from(withName("caller3")),
393  fromRanges(Caller3C.range("Caller2")))));
394 
395  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
396  EXPECT_THAT(IncomingLevel4, IsEmpty());
397  };
398 
399  // Check that invoking from a call site works.
400  auto AST = Workspace.openFile("caller1.m");
401  ASSERT_TRUE(bool(AST));
402  CheckCallHierarchy(*AST, Caller1C.point(), testPath("caller1.m"));
403 
404  // Check that invoking from the declaration site works.
405  AST = Workspace.openFile("callee.mi");
406  ASSERT_TRUE(bool(AST));
407  CheckCallHierarchy(*AST, CalleeH.point(), testPath("callee.mi"));
408 
409  // Check that invoking from the definition site works.
410  AST = Workspace.openFile("callee.m");
411  ASSERT_TRUE(bool(AST));
412  CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.m"));
413 }
414 
415 TEST(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(Source.code());
433  auto AST = TU.build();
434  auto Index = TU.index();
435 
436  std::vector<CallHierarchyItem> Items =
437  prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
438  ASSERT_THAT(Items, ElementsAre(withName("callee")));
439 
440  auto Incoming = incomingCalls(Items[0], 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
clang::clangd::CallHierarchyItem::selectionRange
Range selectionRange
The range that should be selected and revealed when this symbol is being picked, e....
Definition: Protocol.h:1479
XRefs.h
clang::clangd::incomingCalls
std::vector< CallHierarchyIncomingCall > incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index)
Definition: XRefs.cpp:2098
clang::clangd::TEST
TEST(BackgroundQueueTest, Priority)
Definition: BackgroundIndexTests.cpp:750
clang::clangd::testPath
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition: TestFS.cpp:94
clang::clangd::MATCHER_P
MATCHER_P(named, N, "")
Definition: BackgroundIndexTests.cpp:30
TestTU.h
TestWorkspace.h
clang::clangd::CallHierarchyIncomingCall
Represents an incoming call, e.g. a caller of a method or constructor.
Definition: Protocol.h:1496
Pos
size_t Pos
Definition: NoLintDirectiveHandler.cpp:97
M
const google::protobuf::Message & M
Definition: Server.cpp:309
clang::clangd::ParsedAST::build
static llvm::Optional< ParsedAST > build(llvm::StringRef Filename, const ParseInputs &Inputs, std::unique_ptr< clang::CompilerInvocation > CI, llvm::ArrayRef< Diag > CompilerInvocationDiags, std::shared_ptr< const PreambleData > Preamble)
Attempts to run Clang and store the parsed AST.
Definition: ParsedAST.cpp:342
clang::clangd::CallHierarchyIncomingCall::fromRanges
std::vector< Range > fromRanges
The range at which the calls appear.
Definition: Protocol.h:1502
TestFS.h
clang::clangd::TestTU::withCode
static TestTU withCode(llvm::StringRef Code)
Definition: TestTU.h:36
clang::clangd::operator<<
llvm::raw_ostream & operator<<(llvm::raw_ostream &OS, const CodeCompletion &C)
Definition: CodeComplete.cpp:2172
Annotations.h
Index
const SymbolIndex * Index
Definition: Dexp.cpp:98
clang::clangd::PathRef
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition: Path.h:29
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::CallHierarchyItem
Represents programming constructs like functions or constructors in the context of call hierarchy.
Definition: Protocol.h:1456
clang::clangd::CallHierarchyIncomingCall::from
CallHierarchyItem from
The item that makes the call.
Definition: Protocol.h:1498
Field
const FieldDecl * Field
Definition: MemberwiseConstructor.cpp:260
clang::clangd::prepareCallHierarchy
std::vector< CallHierarchyItem > prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath)
Get call hierarchy information at Pos.
Definition: XRefs.cpp:2076
clang::clangd::CallHierarchyItem::name
std::string name
The name of this item.
Definition: Protocol.h:1458
ParsedAST.h