1 //===--- MarshallingTests.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 "../TestTU.h"
10 #include "TestFS.h"
11 #include "index/Index.h"
12 #include "index/Ref.h"
13 #include "index/Serialization.h"
14 #include "index/Symbol.h"
15 #include "index/SymbolID.h"
16 #include "index/remote/marshalling/Marshalling.h"
17 #include "clang/Index/IndexSymbol.h"
18 #include "llvm/ADT/SmallString.h"
19 #include "llvm/ADT/StringRef.h"
20 #include "llvm/Support/Path.h"
21 #include "llvm/Support/StringSaver.h"
22 #include "gmock/gmock.h"
23 #include "gtest/gtest.h"
24 #include <cstring>
25 
26 namespace clang {
27 namespace clangd {
28 namespace remote {
29 namespace {
30 
31 using llvm::sys::path::convert_to_slash;
32 
testPathURI(llvm::StringRef Path,llvm::UniqueStringSaver & Strings)33 const char *testPathURI(llvm::StringRef Path,
34                         llvm::UniqueStringSaver &Strings) {
35   auto URI = URI::createFile(testPath(Path));
36   return Strings.save(URI.toString()).begin();
37 }
38 
TEST(RemoteMarshallingTest,URITranslation)39 TEST(RemoteMarshallingTest, URITranslation) {
40   llvm::BumpPtrAllocator Arena;
41   llvm::UniqueStringSaver Strings(Arena);
42   clangd::Ref Original;
43   Original.Location.FileURI =
44       testPathURI("remote/machine/projects/llvm-project/clang-tools-extra/"
45                   "clangd/unittests/remote/MarshallingTests.cpp",
46                   Strings);
47   auto Serialized =
48       toProtobuf(Original, testPath("remote/machine/projects/llvm-project/"));
49   EXPECT_EQ(Serialized.location().file_path(),
50             "clang-tools-extra/clangd/unittests/remote/MarshallingTests.cpp");
51   const std::string LocalIndexPrefix = testPath("local/machine/project/");
52   auto Deserialized = fromProtobuf(Serialized, &Strings,
53                                    testPath("home/my-projects/llvm-project/"));
54   EXPECT_TRUE(Deserialized);
55   EXPECT_EQ(Deserialized->Location.FileURI,
56             testPathURI("home/my-projects/llvm-project/clang-tools-extra/"
57                         "clangd/unittests/remote/MarshallingTests.cpp",
58                         Strings));
59 
60   clangd::Ref WithInvalidURI;
61   // Invalid URI results in empty path.
62   WithInvalidURI.Location.FileURI = "This is not a URI";
63   Serialized = toProtobuf(WithInvalidURI, testPath("home/"));
64   EXPECT_EQ(Serialized.location().file_path(), "");
65 
66   // Can not use URIs with scheme different from "file".
67   auto UnittestURI =
68       URI::create(testPath("project/lib/HelloWorld.cpp"), "unittest");
69   EXPECT_TRUE(bool(UnittestURI));
70   WithInvalidURI.Location.FileURI =
71       Strings.save(UnittestURI->toString()).begin();
72   Serialized = toProtobuf(WithInvalidURI, testPath("project/lib/"));
73   EXPECT_EQ(Serialized.location().file_path(), "");
74 
75   Ref WithAbsolutePath;
76   *WithAbsolutePath.mutable_location()->mutable_file_path() =
77       "/usr/local/user/home/HelloWorld.cpp";
78   Deserialized = fromProtobuf(WithAbsolutePath, &Strings, LocalIndexPrefix);
79   // Paths transmitted over the wire can not be absolute, they have to be
80   // relative.
81   EXPECT_FALSE(Deserialized);
82 }
83 
TEST(RemoteMarshallingTest,SymbolSerialization)84 TEST(RemoteMarshallingTest, SymbolSerialization) {
85   clangd::Symbol Sym;
86 
87   auto ID = SymbolID::fromStr("057557CEBF6E6B2D");
88   EXPECT_TRUE(bool(ID));
89   Sym.ID = *ID;
90 
91   index::SymbolInfo Info;
92   Info.Kind = index::SymbolKind::Function;
93   Info.SubKind = index::SymbolSubKind::AccessorGetter;
94   Info.Lang = index::SymbolLanguage::CXX;
95   Info.Properties = static_cast<index::SymbolPropertySet>(
96       index::SymbolProperty::TemplateSpecialization);
97   Sym.SymInfo = Info;
98 
99   llvm::BumpPtrAllocator Arena;
100   llvm::UniqueStringSaver Strings(Arena);
101 
102   Sym.Name = Strings.save("Foo");
103   Sym.Scope = Strings.save("llvm::foo::bar::");
104 
105   clangd::SymbolLocation Location;
106   Location.Start.setLine(1);
107   Location.Start.setColumn(15);
108   Location.End.setLine(3);
109   Location.End.setColumn(121);
110   Location.FileURI = testPathURI("home/Definition.cpp", Strings);
111   Sym.Definition = Location;
112 
113   Location.Start.setLine(42);
114   Location.Start.setColumn(31);
115   Location.End.setLine(20);
116   Location.End.setColumn(400);
117   Location.FileURI = testPathURI("home/Declaration.h", Strings);
118   Sym.CanonicalDeclaration = Location;
119 
120   Sym.References = 9000;
121   Sym.Origin = clangd::SymbolOrigin::Static;
122   Sym.Signature = Strings.save("(int X, char Y, Type T)");
123   Sym.TemplateSpecializationArgs = Strings.save("<int, char, bool, Type>");
124   Sym.CompletionSnippetSuffix =
125       Strings.save("({1: int X}, {2: char Y}, {3: Type T})");
126   Sym.Documentation = Strings.save("This is my amazing Foo constructor!");
127   Sym.ReturnType = Strings.save("Foo");
128 
129   Sym.Flags = clangd::Symbol::SymbolFlag::IndexedForCodeCompletion;
130 
131   // Check that symbols are exactly the same if the path to indexed project is
132   // the same on indexing machine and the client.
133   auto Serialized = toProtobuf(Sym, testPath("home/"));
134   auto Deserialized = fromProtobuf(Serialized, &Strings, testPath("home/"));
135   EXPECT_TRUE(Deserialized);
136   EXPECT_EQ(toYAML(Sym), toYAML(*Deserialized));
137   // Serialized paths are relative and have UNIX slashes.
138   EXPECT_EQ(convert_to_slash(Serialized.definition().file_path(),
139                              llvm::sys::path::Style::posix),
140             Serialized.definition().file_path());
141   EXPECT_TRUE(
142       llvm::sys::path::is_relative(Serialized.definition().file_path()));
143 
144   // Fail with an invalid URI.
145   Location.FileURI = "Not A URI";
146   Sym.Definition = Location;
147   Serialized = toProtobuf(Sym, testPath("home/"));
148   Deserialized = fromProtobuf(Serialized, &Strings, testPath("home/"));
149   EXPECT_FALSE(Deserialized);
150 
151   // Schemes other than "file" can not be used.
152   auto UnittestURI = URI::create(testPath("home/SomePath.h"), "unittest");
153   EXPECT_TRUE(bool(UnittestURI));
154   Location.FileURI = Strings.save(UnittestURI->toString()).begin();
155   Sym.Definition = Location;
156   Serialized = toProtobuf(Sym, testPath("home/"));
157   Deserialized = fromProtobuf(Serialized, &Strings, testPath("home/"));
158   EXPECT_FALSE(Deserialized);
159 
160   // Passing root that is not prefix of the original file path.
161   Location.FileURI = testPathURI("home/File.h", Strings);
162   Sym.Definition = Location;
163   // Check that the symbol is valid and passing the correct path works.
164   Serialized = toProtobuf(Sym, testPath("home/"));
165   Deserialized = fromProtobuf(Serialized, &Strings, testPath("home/"));
166   EXPECT_TRUE(Deserialized);
167   EXPECT_EQ(Deserialized->Definition.FileURI,
168             testPathURI("home/File.h", Strings));
169   // Fail with a wrong root.
170   Serialized = toProtobuf(Sym, testPath("nothome/"));
171   Deserialized = fromProtobuf(Serialized, &Strings, testPath("home/"));
172   EXPECT_FALSE(Deserialized);
173 }
174 
TEST(RemoteMarshallingTest,RefSerialization)175 TEST(RemoteMarshallingTest, RefSerialization) {
176   clangd::Ref Ref;
177   Ref.Kind = clangd::RefKind::Spelled | clangd::RefKind::Declaration;
178 
179   llvm::BumpPtrAllocator Arena;
180   llvm::UniqueStringSaver Strings(Arena);
181 
182   clangd::SymbolLocation Location;
183   Location.Start.setLine(124);
184   Location.Start.setColumn(21);
185   Location.End.setLine(3213);
186   Location.End.setColumn(541);
187   Location.FileURI = testPathURI(
188       "llvm-project/llvm/clang-tools-extra/clangd/Protocol.h", Strings);
189   Ref.Location = Location;
190 
191   auto Serialized = toProtobuf(Ref, testPath("llvm-project/"));
192   auto Deserialized =
193       fromProtobuf(Serialized, &Strings, testPath("llvm-project/"));
194   EXPECT_TRUE(Deserialized);
195   EXPECT_EQ(toYAML(Ref), toYAML(*Deserialized));
196 }
197 
TEST(RemoteMarshallingTest,IncludeHeaderURIs)198 TEST(RemoteMarshallingTest, IncludeHeaderURIs) {
199   llvm::BumpPtrAllocator Arena;
200   llvm::UniqueStringSaver Strings(Arena);
201 
202   llvm::SmallVector<clangd::Symbol::IncludeHeaderWithReferences, 1>
203       ValidHeaders;
204   clangd::Symbol::IncludeHeaderWithReferences Header;
205   Header.IncludeHeader = Strings.save(
206       URI::createFile("/usr/local/user/home/project/Header.h").toString());
207   Header.References = 21;
208   ValidHeaders.push_back(Header);
209   Header.IncludeHeader = Strings.save("<iostream>");
210   Header.References = 100;
211   ValidHeaders.push_back(Header);
212   Header.IncludeHeader = Strings.save("\"cstdio\"");
213   Header.References = 200;
214   ValidHeaders.push_back(Header);
215 
216   llvm::SmallVector<clangd::Symbol::IncludeHeaderWithReferences, 1>
217       InvalidHeaders;
218   // This is an absolute path to a header: can not be transmitted over the wire.
219   Header.IncludeHeader = Strings.save(testPath("project/include/Common.h"));
220   Header.References = 42;
221   InvalidHeaders.push_back(Header);
222   // This is not a valid header: can not be transmitted over the wire;
223   Header.IncludeHeader = Strings.save("NotAHeader");
224   Header.References = 5;
225   InvalidHeaders.push_back(Header);
226 
227   clangd::Symbol Sym;
228   // Fill in definition and declaration, Symbool will be invalid otherwise.
229   clangd::SymbolLocation Location;
230   Location.Start.setLine(1);
231   Location.Start.setColumn(2);
232   Location.End.setLine(3);
233   Location.End.setColumn(4);
234   Location.FileURI = testPathURI("File.h", Strings);
235   Sym.Definition = Location;
236   Sym.CanonicalDeclaration = Location;
237 
238   // Try to serialize all headers but only valid ones will end up in Protobuf
239   // message.
240   auto AllHeaders = ValidHeaders;
241   AllHeaders.insert(AllHeaders.end(), InvalidHeaders.begin(),
242                     InvalidHeaders.end());
243   Sym.IncludeHeaders = AllHeaders;
244 
245   auto Serialized = toProtobuf(Sym, convert_to_slash("/"));
246   EXPECT_EQ(static_cast<size_t>(Serialized.headers_size()),
247             ValidHeaders.size());
248   auto Deserialized = fromProtobuf(Serialized, &Strings, convert_to_slash("/"));
249   EXPECT_TRUE(Deserialized);
250 
251   Sym.IncludeHeaders = ValidHeaders;
252   EXPECT_EQ(toYAML(Sym), toYAML(*Deserialized));
253 }
254 
TEST(RemoteMarshallingTest,FuzzyFindRequestSerialization)255 TEST(RemoteMarshallingTest, FuzzyFindRequestSerialization) {
256   clangd::FuzzyFindRequest Request;
257   Request.ProximityPaths = {testPath("remote/Header.h"),
258                             testPath("remote/subdir/OtherHeader.h"),
259                             testPath("notremote/File.h"), "Not a Path."};
260   auto Serialized = toProtobuf(Request, testPath("remote/"));
261   EXPECT_EQ(Serialized.proximity_paths_size(), 2);
262   auto Deserialized = fromProtobuf(&Serialized, testPath("home/"));
263   EXPECT_THAT(Deserialized.ProximityPaths,
264               testing::ElementsAre(testPath("home/Header.h"),
265                                    testPath("home/subdir/OtherHeader.h")));
266 }
267 
TEST(RemoteMarshallingTest,RelativePathToURITranslation)268 TEST(RemoteMarshallingTest, RelativePathToURITranslation) {
269   EXPECT_TRUE(relativePathToURI("lib/File.cpp", testPath("home/project/")));
270   // RelativePath can not be absolute.
271   EXPECT_FALSE(relativePathToURI("/lib/File.cpp", testPath("home/project/")));
272   // IndexRoot has to be absolute path.
273   EXPECT_FALSE(relativePathToURI("lib/File.cpp", "home/project/"));
274 }
275 
TEST(RemoteMarshallingTest,URIToRelativePathTranslation)276 TEST(RemoteMarshallingTest, URIToRelativePathTranslation) {
277   llvm::BumpPtrAllocator Arena;
278   llvm::UniqueStringSaver Strings(Arena);
279   EXPECT_TRUE(
280       uriToRelativePath(testPathURI("home/project/lib/File.cpp", Strings),
281                         testPath("home/project/")));
282   // IndexRoot has to be absolute path.
283   EXPECT_FALSE(uriToRelativePath(
284       testPathURI("home/project/lib/File.cpp", Strings), "home/project/"));
285   // IndexRoot has to be be a prefix of the file path.
286   EXPECT_FALSE(
287       uriToRelativePath(testPathURI("home/project/lib/File.cpp", Strings),
288                         testPath("home/other/project/")));
289 }
290 
291 } // namespace
292 } // namespace remote
293 } // namespace clangd
294 } // namespace clang
295