1 //===------ IndexActionTests.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 
9 #include "Headers.h"
10 #include "TestFS.h"
11 #include "index/IndexAction.h"
12 #include "index/Serialization.h"
13 #include "clang/Tooling/Tooling.h"
14 #include "gmock/gmock.h"
15 #include "gtest/gtest.h"
16 
17 namespace clang {
18 namespace clangd {
19 namespace {
20 
21 using ::testing::AllOf;
22 using ::testing::ElementsAre;
23 using ::testing::EndsWith;
24 using ::testing::Not;
25 using ::testing::Pair;
26 using ::testing::UnorderedElementsAre;
27 using ::testing::UnorderedPointwise;
28 
toUri(llvm::StringRef Path)29 std::string toUri(llvm::StringRef Path) { return URI::create(Path).toString(); }
30 
31 MATCHER(isTU, "") { return arg.Flags & IncludeGraphNode::SourceFlag::IsTU; }
32 
33 MATCHER_P(hasDigest, Digest, "") { return arg.Digest == Digest; }
34 
35 MATCHER_P(hasName, Name, "") { return arg.Name == Name; }
36 
37 MATCHER(hasSameURI, "") {
38   llvm::StringRef URI = ::testing::get<0>(arg);
39   const std::string &Path = ::testing::get<1>(arg);
40   return toUri(Path) == URI;
41 }
42 
43 ::testing::Matcher<const IncludeGraphNode &>
includesAre(const std::vector<std::string> & Includes)44 includesAre(const std::vector<std::string> &Includes) {
45   return ::testing::Field(&IncludeGraphNode::DirectIncludes,
46                           UnorderedPointwise(hasSameURI(), Includes));
47 }
48 
checkNodesAreInitialized(const IndexFileIn & IndexFile,const std::vector<std::string> & Paths)49 void checkNodesAreInitialized(const IndexFileIn &IndexFile,
50                               const std::vector<std::string> &Paths) {
51   ASSERT_TRUE(IndexFile.Sources);
52   EXPECT_THAT(Paths.size(), IndexFile.Sources->size());
53   for (llvm::StringRef Path : Paths) {
54     auto URI = toUri(Path);
55     const auto &Node = IndexFile.Sources->lookup(URI);
56     // Uninitialized nodes will have an empty URI.
57     EXPECT_EQ(Node.URI.data(), IndexFile.Sources->find(URI)->getKeyData());
58   }
59 }
60 
toMap(const IncludeGraph & IG)61 std::map<std::string, const IncludeGraphNode &> toMap(const IncludeGraph &IG) {
62   std::map<std::string, const IncludeGraphNode &> Nodes;
63   for (auto &I : IG)
64     Nodes.emplace(std::string(I.getKey()), I.getValue());
65   return Nodes;
66 }
67 
68 class IndexActionTest : public ::testing::Test {
69 public:
IndexActionTest()70   IndexActionTest() : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem) {}
71 
72   IndexFileIn
runIndexingAction(llvm::StringRef MainFilePath,const std::vector<std::string> & ExtraArgs={})73   runIndexingAction(llvm::StringRef MainFilePath,
74                     const std::vector<std::string> &ExtraArgs = {}) {
75     IndexFileIn IndexFile;
76     llvm::IntrusiveRefCntPtr<FileManager> Files(
77         new FileManager(FileSystemOptions(), InMemoryFileSystem));
78 
79     auto Action = createStaticIndexingAction(
__anonae56ce760202(SymbolSlab S) 80         Opts, [&](SymbolSlab S) { IndexFile.Symbols = std::move(S); },
__anonae56ce760302(RefSlab R) 81         [&](RefSlab R) { IndexFile.Refs = std::move(R); },
__anonae56ce760402(RelationSlab R) 82         [&](RelationSlab R) { IndexFile.Relations = std::move(R); },
__anonae56ce760502(IncludeGraph IG) 83         [&](IncludeGraph IG) { IndexFile.Sources = std::move(IG); });
84 
85     std::vector<std::string> Args = {"index_action", "-fsyntax-only",
86                                      "-xc++",        "-std=c++11",
87                                      "-iquote",      testRoot()};
88     Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end());
89     Args.push_back(std::string(MainFilePath));
90 
91     tooling::ToolInvocation Invocation(
92         Args, std::move(Action), Files.get(),
93         std::make_shared<PCHContainerOperations>());
94 
95     Invocation.run();
96 
97     checkNodesAreInitialized(IndexFile, FilePaths);
98     return IndexFile;
99   }
100 
addFile(llvm::StringRef Path,llvm::StringRef Content)101   void addFile(llvm::StringRef Path, llvm::StringRef Content) {
102     InMemoryFileSystem->addFile(Path, 0,
103                                 llvm::MemoryBuffer::getMemBufferCopy(Content));
104     FilePaths.push_back(std::string(Path));
105   }
106 
107 protected:
108   SymbolCollector::Options Opts;
109   std::vector<std::string> FilePaths;
110   llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem;
111 };
112 
TEST_F(IndexActionTest,CollectIncludeGraph)113 TEST_F(IndexActionTest, CollectIncludeGraph) {
114   std::string MainFilePath = testPath("main.cpp");
115   std::string MainCode = "#include \"level1.h\"";
116   std::string Level1HeaderPath = testPath("level1.h");
117   std::string Level1HeaderCode = "#include \"level2.h\"";
118   std::string Level2HeaderPath = testPath("level2.h");
119   std::string Level2HeaderCode = "";
120 
121   addFile(MainFilePath, MainCode);
122   addFile(Level1HeaderPath, Level1HeaderCode);
123   addFile(Level2HeaderPath, Level2HeaderCode);
124 
125   IndexFileIn IndexFile = runIndexingAction(MainFilePath);
126   auto Nodes = toMap(*IndexFile.Sources);
127 
128   EXPECT_THAT(Nodes,
129               UnorderedElementsAre(
130                   Pair(toUri(MainFilePath),
131                        AllOf(isTU(), includesAre({Level1HeaderPath}),
132                              hasDigest(digest(MainCode)))),
133                   Pair(toUri(Level1HeaderPath),
134                        AllOf(Not(isTU()), includesAre({Level2HeaderPath}),
135                              hasDigest(digest(Level1HeaderCode)))),
136                   Pair(toUri(Level2HeaderPath),
137                        AllOf(Not(isTU()), includesAre({}),
138                              hasDigest(digest(Level2HeaderCode))))));
139 }
140 
TEST_F(IndexActionTest,IncludeGraphSelfInclude)141 TEST_F(IndexActionTest, IncludeGraphSelfInclude) {
142   std::string MainFilePath = testPath("main.cpp");
143   std::string MainCode = "#include \"header.h\"";
144   std::string HeaderPath = testPath("header.h");
145   std::string HeaderCode = R"cpp(
146       #ifndef _GUARD_
147       #define _GUARD_
148       #include "header.h"
149       #endif)cpp";
150 
151   addFile(MainFilePath, MainCode);
152   addFile(HeaderPath, HeaderCode);
153 
154   IndexFileIn IndexFile = runIndexingAction(MainFilePath);
155   auto Nodes = toMap(*IndexFile.Sources);
156 
157   EXPECT_THAT(
158       Nodes,
159       UnorderedElementsAre(
160           Pair(toUri(MainFilePath), AllOf(isTU(), includesAre({HeaderPath}),
161                                           hasDigest(digest(MainCode)))),
162           Pair(toUri(HeaderPath), AllOf(Not(isTU()), includesAre({HeaderPath}),
163                                         hasDigest(digest(HeaderCode))))));
164 }
165 
TEST_F(IndexActionTest,IncludeGraphSkippedFile)166 TEST_F(IndexActionTest, IncludeGraphSkippedFile) {
167   std::string MainFilePath = testPath("main.cpp");
168   std::string MainCode = R"cpp(
169       #include "common.h"
170       #include "header.h"
171       )cpp";
172 
173   std::string CommonHeaderPath = testPath("common.h");
174   std::string CommonHeaderCode = R"cpp(
175       #ifndef _GUARD_
176       #define _GUARD_
177       void f();
178       #endif)cpp";
179 
180   std::string HeaderPath = testPath("header.h");
181   std::string HeaderCode = R"cpp(
182       #include "common.h"
183       void g();)cpp";
184 
185   addFile(MainFilePath, MainCode);
186   addFile(HeaderPath, HeaderCode);
187   addFile(CommonHeaderPath, CommonHeaderCode);
188 
189   IndexFileIn IndexFile = runIndexingAction(MainFilePath);
190   auto Nodes = toMap(*IndexFile.Sources);
191 
192   EXPECT_THAT(
193       Nodes, UnorderedElementsAre(
194                  Pair(toUri(MainFilePath),
195                       AllOf(isTU(), includesAre({HeaderPath, CommonHeaderPath}),
196                             hasDigest(digest(MainCode)))),
197                  Pair(toUri(HeaderPath),
198                       AllOf(Not(isTU()), includesAre({CommonHeaderPath}),
199                             hasDigest(digest(HeaderCode)))),
200                  Pair(toUri(CommonHeaderPath),
201                       AllOf(Not(isTU()), includesAre({}),
202                             hasDigest(digest(CommonHeaderCode))))));
203 }
204 
TEST_F(IndexActionTest,IncludeGraphDynamicInclude)205 TEST_F(IndexActionTest, IncludeGraphDynamicInclude) {
206   std::string MainFilePath = testPath("main.cpp");
207   std::string MainCode = R"cpp(
208       #ifndef FOO
209       #define FOO "main.cpp"
210       #else
211       #define FOO "header.h"
212       #endif
213 
214       #include FOO)cpp";
215   std::string HeaderPath = testPath("header.h");
216   std::string HeaderCode = "";
217 
218   addFile(MainFilePath, MainCode);
219   addFile(HeaderPath, HeaderCode);
220 
221   IndexFileIn IndexFile = runIndexingAction(MainFilePath);
222   auto Nodes = toMap(*IndexFile.Sources);
223 
224   EXPECT_THAT(
225       Nodes,
226       UnorderedElementsAre(
227           Pair(toUri(MainFilePath),
228                AllOf(isTU(), includesAre({MainFilePath, HeaderPath}),
229                      hasDigest(digest(MainCode)))),
230           Pair(toUri(HeaderPath), AllOf(Not(isTU()), includesAre({}),
231                                         hasDigest(digest(HeaderCode))))));
232 }
233 
TEST_F(IndexActionTest,NoWarnings)234 TEST_F(IndexActionTest, NoWarnings) {
235   std::string MainFilePath = testPath("main.cpp");
236   std::string MainCode = R"cpp(
237       void foo(int x) {
238         if (x = 1) // -Wparentheses
239           return;
240         if (x = 1) // -Wparentheses
241           return;
242       }
243       void bar() {}
244   )cpp";
245   addFile(MainFilePath, MainCode);
246   // We set -ferror-limit so the warning-promoted-to-error would be fatal.
247   // This would cause indexing to stop (if warnings weren't disabled).
248   IndexFileIn IndexFile = runIndexingAction(
249       MainFilePath, {"-ferror-limit=1", "-Wparentheses", "-Werror"});
250   ASSERT_TRUE(IndexFile.Sources);
251   ASSERT_NE(0u, IndexFile.Sources->size());
252   EXPECT_THAT(*IndexFile.Symbols, ElementsAre(hasName("foo"), hasName("bar")));
253 }
254 
TEST_F(IndexActionTest,SkipFiles)255 TEST_F(IndexActionTest, SkipFiles) {
256   std::string MainFilePath = testPath("main.cpp");
257   addFile(MainFilePath, R"cpp(
258     // clang-format off
259     #include "good.h"
260     #include "bad.h"
261     // clang-format on
262   )cpp");
263   addFile(testPath("good.h"), R"cpp(
264     struct S { int s; };
265     void f1() { S f; }
266     auto unskippable1() { return S(); }
267   )cpp");
268   addFile(testPath("bad.h"), R"cpp(
269     struct T { S t; };
270     void f2() { S f; }
271     auto unskippable2() { return S(); }
272   )cpp");
273   Opts.FileFilter = [](const SourceManager &SM, FileID F) {
274     return !SM.getFileEntryRefForID(F)->getName().endswith("bad.h");
275   };
276   IndexFileIn IndexFile = runIndexingAction(MainFilePath, {"-std=c++14"});
277   EXPECT_THAT(*IndexFile.Symbols,
278               UnorderedElementsAre(hasName("S"), hasName("s"), hasName("f1"),
279                                    hasName("unskippable1")));
280   for (const auto &Pair : *IndexFile.Refs)
281     for (const auto &Ref : Pair.second)
282       EXPECT_THAT(Ref.Location.FileURI, EndsWith("good.h"));
283 }
284 
TEST_F(IndexActionTest,SkipNestedSymbols)285 TEST_F(IndexActionTest, SkipNestedSymbols) {
286   std::string MainFilePath = testPath("main.cpp");
287   addFile(MainFilePath, R"cpp(
288   namespace ns1 {
289   namespace ns2 {
290   namespace ns3 {
291   namespace ns4 {
292   namespace ns5 {
293   namespace ns6 {
294   namespace ns7 {
295   namespace ns8 {
296   namespace ns9 {
297   class Bar {};
298   void foo() {
299     class Baz {};
300   }
301   }
302   }
303   }
304   }
305   }
306   }
307   }
308   }
309   })cpp");
310   IndexFileIn IndexFile = runIndexingAction(MainFilePath, {"-std=c++14"});
311   EXPECT_THAT(*IndexFile.Symbols, testing::Contains(hasName("foo")));
312   EXPECT_THAT(*IndexFile.Symbols, testing::Contains(hasName("Bar")));
313   EXPECT_THAT(*IndexFile.Symbols, Not(testing::Contains(hasName("Baz"))));
314 }
315 } // namespace
316 } // namespace clangd
317 } // namespace clang
318