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