clang-tools  14.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 "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 
30 llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
31  const CallHierarchyItem &Item) {
32  return Stream << Item.name << "@" << Item.selectionRange;
33 }
34 
35 llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
36  const CallHierarchyIncomingCall &Call) {
37  Stream << "{ from: " << Call.from << ", ranges: [";
38  for (const auto &R : Call.fromRanges) {
39  Stream << R;
40  Stream << ", ";
41  }
42  return Stream << "] }";
43 }
44 
45 namespace {
46 
47 using ::testing::AllOf;
48 using ::testing::ElementsAre;
49 using ::testing::Field;
50 using ::testing::IsEmpty;
51 using ::testing::Matcher;
52 using ::testing::UnorderedElementsAre;
53 
54 // Helpers for matching call hierarchy data structures.
55 MATCHER_P(WithName, N, "") { return arg.name == N; }
56 MATCHER_P(WithSelectionRange, R, "") { return arg.selectionRange == R; }
57 
58 template <class ItemMatcher>
59 ::testing::Matcher<CallHierarchyIncomingCall> From(ItemMatcher M) {
61 }
62 template <class... RangeMatchers>
63 ::testing::Matcher<CallHierarchyIncomingCall> FromRanges(RangeMatchers... M) {
65  UnorderedElementsAre(M...));
66 }
67 
68 TEST(CallHierarchy, IncomingOneFile) {
69  Annotations Source(R"cpp(
70  void call^ee(int);
71  void caller1() {
72  $Callee[[callee]](42);
73  }
74  void caller2() {
75  $Caller1A[[caller1]]();
76  $Caller1B[[caller1]]();
77  }
78  void caller3() {
79  $Caller1C[[caller1]]();
80  $Caller2[[caller2]]();
81  }
82  )cpp");
83  TestTU TU = TestTU::withCode(Source.code());
84  auto AST = TU.build();
85  auto Index = TU.index();
86 
87  std::vector<CallHierarchyItem> Items =
88  prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
89  ASSERT_THAT(Items, ElementsAre(WithName("callee")));
90  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
91  ASSERT_THAT(IncomingLevel1,
92  ElementsAre(AllOf(From(WithName("caller1")),
93  FromRanges(Source.range("Callee")))));
94 
95  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
96  ASSERT_THAT(IncomingLevel2,
97  ElementsAre(AllOf(From(WithName("caller2")),
98  FromRanges(Source.range("Caller1A"),
99  Source.range("Caller1B"))),
100  AllOf(From(WithName("caller3")),
101  FromRanges(Source.range("Caller1C")))));
102 
103  auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
104  ASSERT_THAT(IncomingLevel3,
105  ElementsAre(AllOf(From(WithName("caller3")),
106  FromRanges(Source.range("Caller2")))));
107 
108  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
109  EXPECT_THAT(IncomingLevel4, IsEmpty());
110 }
111 
112 TEST(CallHierarchy, MainFileOnlyRef) {
113  // In addition to testing that we store refs to main-file only symbols,
114  // this tests that anonymous namespaces do not interfere with the
115  // symbol re-identification process in callHierarchyItemToSymbo().
116  Annotations Source(R"cpp(
117  void call^ee(int);
118  namespace {
119  void caller1() {
120  $Callee[[callee]](42);
121  }
122  }
123  void caller2() {
124  $Caller1[[caller1]]();
125  }
126  )cpp");
127  TestTU TU = TestTU::withCode(Source.code());
128  auto AST = TU.build();
129  auto Index = TU.index();
130 
131  std::vector<CallHierarchyItem> Items =
132  prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
133  ASSERT_THAT(Items, ElementsAre(WithName("callee")));
134  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
135  ASSERT_THAT(IncomingLevel1,
136  ElementsAre(AllOf(From(WithName("caller1")),
137  FromRanges(Source.range("Callee")))));
138 
139  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
140  EXPECT_THAT(IncomingLevel2,
141  ElementsAre(AllOf(From(WithName("caller2")),
142  FromRanges(Source.range("Caller1")))));
143 }
144 
145 TEST(CallHierarchy, IncomingQualified) {
146  Annotations Source(R"cpp(
147  namespace ns {
148  struct Waldo {
149  void find();
150  };
151  void Waldo::find() {}
152  void caller1(Waldo &W) {
153  W.$Caller1[[f^ind]]();
154  }
155  void caller2(Waldo &W) {
156  W.$Caller2[[find]]();
157  }
158  }
159  )cpp");
160  TestTU TU = TestTU::withCode(Source.code());
161  auto AST = TU.build();
162  auto Index = TU.index();
163 
164  std::vector<CallHierarchyItem> Items =
165  prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
166  ASSERT_THAT(Items, ElementsAre(WithName("Waldo::find")));
167  auto Incoming = incomingCalls(Items[0], Index.get());
168  EXPECT_THAT(Incoming,
169  ElementsAre(AllOf(From(WithName("caller1")),
170  FromRanges(Source.range("Caller1"))),
171  AllOf(From(WithName("caller2")),
172  FromRanges(Source.range("Caller2")))));
173 }
174 
175 TEST(CallHierarchy, IncomingMultiFile) {
176  // The test uses a .hh suffix for header files to get clang
177  // to parse them in C++ mode. .h files are parsed in C mode
178  // by default, which causes problems because e.g. symbol
179  // USRs are different in C mode (do not include function signatures).
180 
181  Annotations CalleeH(R"cpp(
182  void calle^e(int);
183  )cpp");
184  Annotations CalleeC(R"cpp(
185  #include "callee.hh"
186  void calle^e(int) {}
187  )cpp");
188  Annotations Caller1H(R"cpp(
189  void caller1();
190  )cpp");
191  Annotations Caller1C(R"cpp(
192  #include "callee.hh"
193  #include "caller1.hh"
194  void caller1() {
195  [[calle^e]](42);
196  }
197  )cpp");
198  Annotations Caller2H(R"cpp(
199  void caller2();
200  )cpp");
201  Annotations Caller2C(R"cpp(
202  #include "caller1.hh"
203  #include "caller2.hh"
204  void caller2() {
205  $A[[caller1]]();
206  $B[[caller1]]();
207  }
208  )cpp");
209  Annotations Caller3C(R"cpp(
210  #include "caller1.hh"
211  #include "caller2.hh"
212  void caller3() {
213  $Caller1[[caller1]]();
214  $Caller2[[caller2]]();
215  }
216  )cpp");
217 
218  TestWorkspace Workspace;
219  Workspace.addSource("callee.hh", CalleeH.code());
220  Workspace.addSource("caller1.hh", Caller1H.code());
221  Workspace.addSource("caller2.hh", Caller2H.code());
222  Workspace.addMainFile("callee.cc", CalleeC.code());
223  Workspace.addMainFile("caller1.cc", Caller1C.code());
224  Workspace.addMainFile("caller2.cc", Caller2C.code());
225  Workspace.addMainFile("caller3.cc", Caller3C.code());
226 
227  auto Index = Workspace.index();
228 
229  auto CheckCallHierarchy = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
230  std::vector<CallHierarchyItem> Items =
231  prepareCallHierarchy(AST, Pos, TUPath);
232  ASSERT_THAT(Items, ElementsAre(WithName("callee")));
233  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
234  ASSERT_THAT(IncomingLevel1,
235  ElementsAre(AllOf(From(WithName("caller1")),
236  FromRanges(Caller1C.range()))));
237 
238  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
239  ASSERT_THAT(
240  IncomingLevel2,
241  ElementsAre(AllOf(From(WithName("caller2")),
242  FromRanges(Caller2C.range("A"), Caller2C.range("B"))),
243  AllOf(From(WithName("caller3")),
244  FromRanges(Caller3C.range("Caller1")))));
245 
246  auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
247  ASSERT_THAT(IncomingLevel3,
248  ElementsAre(AllOf(From(WithName("caller3")),
249  FromRanges(Caller3C.range("Caller2")))));
250 
251  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
252  EXPECT_THAT(IncomingLevel4, IsEmpty());
253  };
254 
255  // Check that invoking from a call site works.
256  auto AST = Workspace.openFile("caller1.cc");
257  ASSERT_TRUE(bool(AST));
258  CheckCallHierarchy(*AST, Caller1C.point(), testPath("caller1.cc"));
259 
260  // Check that invoking from the declaration site works.
261  AST = Workspace.openFile("callee.hh");
262  ASSERT_TRUE(bool(AST));
263  CheckCallHierarchy(*AST, CalleeH.point(), testPath("callee.hh"));
264 
265  // Check that invoking from the definition site works.
266  AST = Workspace.openFile("callee.cc");
267  ASSERT_TRUE(bool(AST));
268  CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.cc"));
269 }
270 
271 TEST(CallHierarchy, CallInLocalVarDecl) {
272  // Tests that local variable declarations are not treated as callers
273  // (they're not indexed, so they can't be represented as call hierarchy
274  // items); instead, the caller should be the containing function.
275  // However, namespace-scope variable declarations should be treated as
276  // callers because those are indexed and there is no enclosing entity
277  // that would be a useful caller.
278  Annotations Source(R"cpp(
279  int call^ee();
280  void caller1() {
281  $call1[[callee]]();
282  }
283  void caller2() {
284  int localVar = $call2[[callee]]();
285  }
286  int caller3 = $call3[[callee]]();
287  )cpp");
288  TestTU TU = TestTU::withCode(Source.code());
289  auto AST = TU.build();
290  auto Index = TU.index();
291 
292  std::vector<CallHierarchyItem> Items =
293  prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
294  ASSERT_THAT(Items, ElementsAre(WithName("callee")));
295 
296  auto Incoming = incomingCalls(Items[0], Index.get());
297  ASSERT_THAT(
298  Incoming,
299  ElementsAre(
300  AllOf(From(WithName("caller1")), FromRanges(Source.range("call1"))),
301  AllOf(From(WithName("caller2")), FromRanges(Source.range("call2"))),
302  AllOf(From(WithName("caller3")), FromRanges(Source.range("call3")))));
303 }
304 
305 } // namespace
306 } // namespace clangd
307 } // 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:1459
XRefs.h
clang::clangd::incomingCalls
std::vector< CallHierarchyIncomingCall > incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index)
Definition: XRefs.cpp:1916
clang::clangd::TEST
TEST(BackgroundQueueTest, Priority)
Definition: BackgroundIndexTests.cpp:751
clang::clangd::testPath
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition: TestFS.cpp:82
clang::clangd::CompletionItemKind::Field
@ Field
TestTU.h
TestWorkspace.h
clang::clangd::CallHierarchyIncomingCall
Represents an incoming call, e.g. a caller of a method or constructor.
Definition: Protocol.h:1476
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:248
clang::clangd::CallHierarchyIncomingCall::fromRanges
std::vector< Range > fromRanges
The range at which the calls appear.
Definition: Protocol.h:1482
TestFS.h
SyncAPI.h
clang::clangd::TestTU::withCode
static TestTU withCode(llvm::StringRef Code)
Definition: TestTU.h:37
clang::clangd::operator<<
llvm::raw_ostream & operator<<(llvm::raw_ostream &OS, const CodeCompletion &C)
Definition: CodeComplete.cpp:2126
FileIndex.h
Annotations.h
Index
const SymbolIndex * Index
Definition: Dexp.cpp:99
Compiler.h
clang::clangd::PathRef
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition: Path.h:29
clang::clangd::MATCHER_P
MATCHER_P(Named, N, "")
Definition: BackgroundIndexTests.cpp:31
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:1436
clang::clangd::CallHierarchyIncomingCall::from
CallHierarchyItem from
The item that makes the call.
Definition: Protocol.h:1478
SymbolCollector.h
Pos
Position Pos
Definition: SourceCode.cpp:657
Matchers.h
clang::clangd::prepareCallHierarchy
std::vector< CallHierarchyItem > prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath)
Get call hierarchy information at Pos.
Definition: XRefs.cpp:1896
clang::clangd::CallHierarchyItem::name
std::string name
The name of this item.
Definition: Protocol.h:1438
ParsedAST.h