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