1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 // * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 // * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 // * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 // Author: kenton@google.com (Kenton Varda)
32 // Based on original Protocol Buffers design by
33 // Sanjay Ghemawat, Jeff Dean, and others.
34
35 #include <google/protobuf/compiler/importer.h>
36
37 #include <google/protobuf/stubs/hash.h>
38 #include <memory>
39 #ifndef _SHARED_PTR_H
40 #include <google/protobuf/stubs/shared_ptr.h>
41 #endif
42
43 #include <google/protobuf/stubs/logging.h>
44 #include <google/protobuf/stubs/common.h>
45 #include <google/protobuf/testing/file.h>
46 #include <google/protobuf/testing/file.h>
47 #include <google/protobuf/testing/file.h>
48 #include <google/protobuf/io/zero_copy_stream_impl.h>
49 #include <google/protobuf/descriptor.h>
50 #include <google/protobuf/stubs/strutil.h>
51 #include <google/protobuf/stubs/substitute.h>
52 #include <google/protobuf/testing/googletest.h>
53 #include <gtest/gtest.h>
54 #include <google/protobuf/stubs/map_util.h>
55
56 namespace google {
57 namespace protobuf {
58 namespace compiler {
59
60 namespace {
61
FileExists(const string & path)62 bool FileExists(const string& path) {
63 return File::Exists(path);
64 }
65
66 #define EXPECT_SUBSTRING(needle, haystack) \
67 EXPECT_PRED_FORMAT2(testing::IsSubstring, (needle), (haystack))
68
69 class MockErrorCollector : public MultiFileErrorCollector {
70 public:
MockErrorCollector()71 MockErrorCollector() {}
~MockErrorCollector()72 ~MockErrorCollector() {}
73
74 string text_;
75 string warning_text_;
76
77 // implements ErrorCollector ---------------------------------------
AddError(const string & filename,int line,int column,const string & message)78 void AddError(const string& filename, int line, int column,
79 const string& message) {
80 strings::SubstituteAndAppend(&text_, "$0:$1:$2: $3\n",
81 filename, line, column, message);
82 }
83
AddWarning(const string & filename,int line,int column,const string & message)84 void AddWarning(const string& filename, int line, int column,
85 const string& message) {
86 strings::SubstituteAndAppend(&warning_text_, "$0:$1:$2: $3\n",
87 filename, line, column, message);
88 }
89 };
90
91 // -------------------------------------------------------------------
92
93 // A dummy implementation of SourceTree backed by a simple map.
94 class MockSourceTree : public SourceTree {
95 public:
MockSourceTree()96 MockSourceTree() {}
~MockSourceTree()97 ~MockSourceTree() {}
98
AddFile(const string & name,const char * contents)99 void AddFile(const string& name, const char* contents) {
100 files_[name] = contents;
101 }
102
103 // implements SourceTree -------------------------------------------
Open(const string & filename)104 io::ZeroCopyInputStream* Open(const string& filename) {
105 const char* contents = FindPtrOrNull(files_, filename);
106 if (contents == NULL) {
107 return NULL;
108 } else {
109 return new io::ArrayInputStream(contents, strlen(contents));
110 }
111 }
112
GetLastErrorMessage()113 string GetLastErrorMessage() {
114 return "File not found.";
115 }
116
117 private:
118 hash_map<string, const char*> files_;
119 };
120
121 // ===================================================================
122
123 class ImporterTest : public testing::Test {
124 protected:
ImporterTest()125 ImporterTest()
126 : importer_(&source_tree_, &error_collector_) {}
127
AddFile(const string & filename,const char * text)128 void AddFile(const string& filename, const char* text) {
129 source_tree_.AddFile(filename, text);
130 }
131
132 // Return the collected error text
error() const133 string error() const { return error_collector_.text_; }
warning() const134 string warning() const { return error_collector_.warning_text_; }
135
136 MockErrorCollector error_collector_;
137 MockSourceTree source_tree_;
138 Importer importer_;
139 };
140
TEST_F(ImporterTest,Import)141 TEST_F(ImporterTest, Import) {
142 // Test normal importing.
143 AddFile("foo.proto",
144 "syntax = \"proto2\";\n"
145 "message Foo {}\n");
146
147 const FileDescriptor* file = importer_.Import("foo.proto");
148 EXPECT_EQ("", error_collector_.text_);
149 ASSERT_TRUE(file != NULL);
150
151 ASSERT_EQ(1, file->message_type_count());
152 EXPECT_EQ("Foo", file->message_type(0)->name());
153
154 // Importing again should return same object.
155 EXPECT_EQ(file, importer_.Import("foo.proto"));
156 }
157
TEST_F(ImporterTest,ImportNested)158 TEST_F(ImporterTest, ImportNested) {
159 // Test that importing a file which imports another file works.
160 AddFile("foo.proto",
161 "syntax = \"proto2\";\n"
162 "import \"bar.proto\";\n"
163 "message Foo {\n"
164 " optional Bar bar = 1;\n"
165 "}\n");
166 AddFile("bar.proto",
167 "syntax = \"proto2\";\n"
168 "message Bar {}\n");
169
170 // Note that both files are actually parsed by the first call to Import()
171 // here, since foo.proto imports bar.proto. The second call just returns
172 // the same ProtoFile for bar.proto which was constructed while importing
173 // foo.proto. We test that this is the case below by checking that bar
174 // is among foo's dependencies (by pointer).
175 const FileDescriptor* foo = importer_.Import("foo.proto");
176 const FileDescriptor* bar = importer_.Import("bar.proto");
177 EXPECT_EQ("", error_collector_.text_);
178 ASSERT_TRUE(foo != NULL);
179 ASSERT_TRUE(bar != NULL);
180
181 // Check that foo's dependency is the same object as bar.
182 ASSERT_EQ(1, foo->dependency_count());
183 EXPECT_EQ(bar, foo->dependency(0));
184
185 // Check that foo properly cross-links bar.
186 ASSERT_EQ(1, foo->message_type_count());
187 ASSERT_EQ(1, bar->message_type_count());
188 ASSERT_EQ(1, foo->message_type(0)->field_count());
189 ASSERT_EQ(FieldDescriptor::TYPE_MESSAGE,
190 foo->message_type(0)->field(0)->type());
191 EXPECT_EQ(bar->message_type(0),
192 foo->message_type(0)->field(0)->message_type());
193 }
194
TEST_F(ImporterTest,FileNotFound)195 TEST_F(ImporterTest, FileNotFound) {
196 // Error: Parsing a file that doesn't exist.
197 EXPECT_TRUE(importer_.Import("foo.proto") == NULL);
198 EXPECT_EQ(
199 "foo.proto:-1:0: File not found.\n",
200 error_collector_.text_);
201 }
202
TEST_F(ImporterTest,ImportNotFound)203 TEST_F(ImporterTest, ImportNotFound) {
204 // Error: Importing a file that doesn't exist.
205 AddFile("foo.proto",
206 "syntax = \"proto2\";\n"
207 "import \"bar.proto\";\n");
208
209 EXPECT_TRUE(importer_.Import("foo.proto") == NULL);
210 EXPECT_EQ(
211 "bar.proto:-1:0: File not found.\n"
212 "foo.proto:-1:0: Import \"bar.proto\" was not found or had errors.\n",
213 error_collector_.text_);
214 }
215
TEST_F(ImporterTest,RecursiveImport)216 TEST_F(ImporterTest, RecursiveImport) {
217 // Error: Recursive import.
218 AddFile("recursive1.proto",
219 "syntax = \"proto2\";\n"
220 "import \"recursive2.proto\";\n");
221 AddFile("recursive2.proto",
222 "syntax = \"proto2\";\n"
223 "import \"recursive1.proto\";\n");
224
225 EXPECT_TRUE(importer_.Import("recursive1.proto") == NULL);
226 EXPECT_EQ(
227 "recursive1.proto:-1:0: File recursively imports itself: recursive1.proto "
228 "-> recursive2.proto -> recursive1.proto\n"
229 "recursive2.proto:-1:0: Import \"recursive1.proto\" was not found "
230 "or had errors.\n"
231 "recursive1.proto:-1:0: Import \"recursive2.proto\" was not found "
232 "or had errors.\n",
233 error_collector_.text_);
234 }
235
236
237 // ===================================================================
238
239 class DiskSourceTreeTest : public testing::Test {
240 protected:
SetUp()241 virtual void SetUp() {
242 dirnames_.push_back(TestTempDir() + "/test_proto2_import_path_1");
243 dirnames_.push_back(TestTempDir() + "/test_proto2_import_path_2");
244
245 for (int i = 0; i < dirnames_.size(); i++) {
246 if (FileExists(dirnames_[i])) {
247 File::DeleteRecursively(dirnames_[i], NULL, NULL);
248 }
249 GOOGLE_CHECK_OK(File::CreateDir(dirnames_[i], 0777));
250 }
251 }
252
TearDown()253 virtual void TearDown() {
254 for (int i = 0; i < dirnames_.size(); i++) {
255 if (FileExists(dirnames_[i])) {
256 File::DeleteRecursively(dirnames_[i], NULL, NULL);
257 }
258 }
259 }
260
AddFile(const string & filename,const char * contents)261 void AddFile(const string& filename, const char* contents) {
262 GOOGLE_CHECK_OK(File::SetContents(filename, contents, true));
263 }
264
AddSubdir(const string & dirname)265 void AddSubdir(const string& dirname) {
266 GOOGLE_CHECK_OK(File::CreateDir(dirname, 0777));
267 }
268
ExpectFileContents(const string & filename,const char * expected_contents)269 void ExpectFileContents(const string& filename,
270 const char* expected_contents) {
271 google::protobuf::scoped_ptr<io::ZeroCopyInputStream> input(source_tree_.Open(filename));
272
273 ASSERT_FALSE(input == NULL);
274
275 // Read all the data from the file.
276 string file_contents;
277 const void* data;
278 int size;
279 while (input->Next(&data, &size)) {
280 file_contents.append(reinterpret_cast<const char*>(data), size);
281 }
282
283 EXPECT_EQ(expected_contents, file_contents);
284 }
285
ExpectCannotOpenFile(const string & filename,const string & error_message)286 void ExpectCannotOpenFile(const string& filename,
287 const string& error_message) {
288 google::protobuf::scoped_ptr<io::ZeroCopyInputStream> input(source_tree_.Open(filename));
289 EXPECT_TRUE(input == NULL);
290 EXPECT_EQ(error_message, source_tree_.GetLastErrorMessage());
291 }
292
293 DiskSourceTree source_tree_;
294
295 // Paths of two on-disk directories to use during the test.
296 vector<string> dirnames_;
297 };
298
TEST_F(DiskSourceTreeTest,MapRoot)299 TEST_F(DiskSourceTreeTest, MapRoot) {
300 // Test opening a file in a directory that is mapped to the root of the
301 // source tree.
302 AddFile(dirnames_[0] + "/foo", "Hello World!");
303 source_tree_.MapPath("", dirnames_[0]);
304
305 ExpectFileContents("foo", "Hello World!");
306 ExpectCannotOpenFile("bar", "File not found.");
307 }
308
TEST_F(DiskSourceTreeTest,MapDirectory)309 TEST_F(DiskSourceTreeTest, MapDirectory) {
310 // Test opening a file in a directory that is mapped to somewhere other
311 // than the root of the source tree.
312
313 AddFile(dirnames_[0] + "/foo", "Hello World!");
314 source_tree_.MapPath("baz", dirnames_[0]);
315
316 ExpectFileContents("baz/foo", "Hello World!");
317 ExpectCannotOpenFile("baz/bar", "File not found.");
318 ExpectCannotOpenFile("foo", "File not found.");
319 ExpectCannotOpenFile("bar", "File not found.");
320
321 // Non-canonical file names should not work.
322 ExpectCannotOpenFile("baz//foo",
323 "Backslashes, consecutive slashes, \".\", or \"..\" are "
324 "not allowed in the virtual path");
325 ExpectCannotOpenFile("baz/../baz/foo",
326 "Backslashes, consecutive slashes, \".\", or \"..\" are "
327 "not allowed in the virtual path");
328 ExpectCannotOpenFile("baz/./foo",
329 "Backslashes, consecutive slashes, \".\", or \"..\" are "
330 "not allowed in the virtual path");
331 ExpectCannotOpenFile("baz/foo/", "File not found.");
332 }
333
TEST_F(DiskSourceTreeTest,NoParent)334 TEST_F(DiskSourceTreeTest, NoParent) {
335 // Test that we cannot open files in a parent of a mapped directory.
336
337 AddFile(dirnames_[0] + "/foo", "Hello World!");
338 AddSubdir(dirnames_[0] + "/bar");
339 AddFile(dirnames_[0] + "/bar/baz", "Blah.");
340 source_tree_.MapPath("", dirnames_[0] + "/bar");
341
342 ExpectFileContents("baz", "Blah.");
343 ExpectCannotOpenFile("../foo",
344 "Backslashes, consecutive slashes, \".\", or \"..\" are "
345 "not allowed in the virtual path");
346 ExpectCannotOpenFile("../bar/baz",
347 "Backslashes, consecutive slashes, \".\", or \"..\" are "
348 "not allowed in the virtual path");
349 }
350
TEST_F(DiskSourceTreeTest,MapFile)351 TEST_F(DiskSourceTreeTest, MapFile) {
352 // Test opening a file that is mapped directly into the source tree.
353
354 AddFile(dirnames_[0] + "/foo", "Hello World!");
355 source_tree_.MapPath("foo", dirnames_[0] + "/foo");
356
357 ExpectFileContents("foo", "Hello World!");
358 ExpectCannotOpenFile("bar", "File not found.");
359 }
360
TEST_F(DiskSourceTreeTest,SearchMultipleDirectories)361 TEST_F(DiskSourceTreeTest, SearchMultipleDirectories) {
362 // Test mapping and searching multiple directories.
363
364 AddFile(dirnames_[0] + "/foo", "Hello World!");
365 AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
366 AddFile(dirnames_[1] + "/bar", "Goodbye World!");
367 source_tree_.MapPath("", dirnames_[0]);
368 source_tree_.MapPath("", dirnames_[1]);
369
370 ExpectFileContents("foo", "Hello World!");
371 ExpectFileContents("bar", "Goodbye World!");
372 ExpectCannotOpenFile("baz", "File not found.");
373 }
374
TEST_F(DiskSourceTreeTest,OrderingTrumpsSpecificity)375 TEST_F(DiskSourceTreeTest, OrderingTrumpsSpecificity) {
376 // Test that directories are always searched in order, even when a latter
377 // directory is more-specific than a former one.
378
379 // Create the "bar" directory so we can put a file in it.
380 GOOGLE_CHECK_OK(File::CreateDir(dirnames_[0] + "/bar", 0777));
381
382 // Add files and map paths.
383 AddFile(dirnames_[0] + "/bar/foo", "Hello World!");
384 AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
385 source_tree_.MapPath("", dirnames_[0]);
386 source_tree_.MapPath("bar", dirnames_[1]);
387
388 // Check.
389 ExpectFileContents("bar/foo", "Hello World!");
390 }
391
TEST_F(DiskSourceTreeTest,DiskFileToVirtualFile)392 TEST_F(DiskSourceTreeTest, DiskFileToVirtualFile) {
393 // Test DiskFileToVirtualFile.
394
395 AddFile(dirnames_[0] + "/foo", "Hello World!");
396 AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
397 source_tree_.MapPath("bar", dirnames_[0]);
398 source_tree_.MapPath("bar", dirnames_[1]);
399
400 string virtual_file;
401 string shadowing_disk_file;
402
403 EXPECT_EQ(DiskSourceTree::NO_MAPPING,
404 source_tree_.DiskFileToVirtualFile(
405 "/foo", &virtual_file, &shadowing_disk_file));
406
407 EXPECT_EQ(DiskSourceTree::SHADOWED,
408 source_tree_.DiskFileToVirtualFile(
409 dirnames_[1] + "/foo", &virtual_file, &shadowing_disk_file));
410 EXPECT_EQ("bar/foo", virtual_file);
411 EXPECT_EQ(dirnames_[0] + "/foo", shadowing_disk_file);
412
413 EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
414 source_tree_.DiskFileToVirtualFile(
415 dirnames_[1] + "/baz", &virtual_file, &shadowing_disk_file));
416 EXPECT_EQ("bar/baz", virtual_file);
417
418 EXPECT_EQ(DiskSourceTree::SUCCESS,
419 source_tree_.DiskFileToVirtualFile(
420 dirnames_[0] + "/foo", &virtual_file, &shadowing_disk_file));
421 EXPECT_EQ("bar/foo", virtual_file);
422 }
423
TEST_F(DiskSourceTreeTest,DiskFileToVirtualFileCanonicalization)424 TEST_F(DiskSourceTreeTest, DiskFileToVirtualFileCanonicalization) {
425 // Test handling of "..", ".", etc. in DiskFileToVirtualFile().
426
427 source_tree_.MapPath("dir1", "..");
428 source_tree_.MapPath("dir2", "../../foo");
429 source_tree_.MapPath("dir3", "./foo/bar/.");
430 source_tree_.MapPath("dir4", ".");
431 source_tree_.MapPath("", "/qux");
432 source_tree_.MapPath("dir5", "/quux/");
433
434 string virtual_file;
435 string shadowing_disk_file;
436
437 // "../.." should not be considered to be under "..".
438 EXPECT_EQ(DiskSourceTree::NO_MAPPING,
439 source_tree_.DiskFileToVirtualFile(
440 "../../baz", &virtual_file, &shadowing_disk_file));
441
442 // "/foo" is not mapped (it should not be misintepreted as being under ".").
443 EXPECT_EQ(DiskSourceTree::NO_MAPPING,
444 source_tree_.DiskFileToVirtualFile(
445 "/foo", &virtual_file, &shadowing_disk_file));
446
447 #ifdef WIN32
448 // "C:\foo" is not mapped (it should not be misintepreted as being under ".").
449 EXPECT_EQ(DiskSourceTree::NO_MAPPING,
450 source_tree_.DiskFileToVirtualFile(
451 "C:\\foo", &virtual_file, &shadowing_disk_file));
452 #endif // WIN32
453
454 // But "../baz" should be.
455 EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
456 source_tree_.DiskFileToVirtualFile(
457 "../baz", &virtual_file, &shadowing_disk_file));
458 EXPECT_EQ("dir1/baz", virtual_file);
459
460 // "../../foo/baz" is under "../../foo".
461 EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
462 source_tree_.DiskFileToVirtualFile(
463 "../../foo/baz", &virtual_file, &shadowing_disk_file));
464 EXPECT_EQ("dir2/baz", virtual_file);
465
466 // "foo/./bar/baz" is under "./foo/bar/.".
467 EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
468 source_tree_.DiskFileToVirtualFile(
469 "foo/bar/baz", &virtual_file, &shadowing_disk_file));
470 EXPECT_EQ("dir3/baz", virtual_file);
471
472 // "bar" is under ".".
473 EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
474 source_tree_.DiskFileToVirtualFile(
475 "bar", &virtual_file, &shadowing_disk_file));
476 EXPECT_EQ("dir4/bar", virtual_file);
477
478 // "/qux/baz" is under "/qux".
479 EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
480 source_tree_.DiskFileToVirtualFile(
481 "/qux/baz", &virtual_file, &shadowing_disk_file));
482 EXPECT_EQ("baz", virtual_file);
483
484 // "/quux/bar" is under "/quux".
485 EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
486 source_tree_.DiskFileToVirtualFile(
487 "/quux/bar", &virtual_file, &shadowing_disk_file));
488 EXPECT_EQ("dir5/bar", virtual_file);
489 }
490
TEST_F(DiskSourceTreeTest,VirtualFileToDiskFile)491 TEST_F(DiskSourceTreeTest, VirtualFileToDiskFile) {
492 // Test VirtualFileToDiskFile.
493
494 AddFile(dirnames_[0] + "/foo", "Hello World!");
495 AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
496 AddFile(dirnames_[1] + "/quux", "This file should not be hidden.");
497 source_tree_.MapPath("bar", dirnames_[0]);
498 source_tree_.MapPath("bar", dirnames_[1]);
499
500 // Existent files, shadowed and non-shadowed case.
501 string disk_file;
502 EXPECT_TRUE(source_tree_.VirtualFileToDiskFile("bar/foo", &disk_file));
503 EXPECT_EQ(dirnames_[0] + "/foo", disk_file);
504 EXPECT_TRUE(source_tree_.VirtualFileToDiskFile("bar/quux", &disk_file));
505 EXPECT_EQ(dirnames_[1] + "/quux", disk_file);
506
507 // Nonexistent file in existent directory and vice versa.
508 string not_touched = "not touched";
509 EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("bar/baz", ¬_touched));
510 EXPECT_EQ("not touched", not_touched);
511 EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("baz/foo", ¬_touched));
512 EXPECT_EQ("not touched", not_touched);
513
514 // Accept NULL as output parameter.
515 EXPECT_TRUE(source_tree_.VirtualFileToDiskFile("bar/foo", NULL));
516 EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("baz/foo", NULL));
517 }
518
519 } // namespace
520
521 } // namespace compiler
522 } // namespace protobuf
523 } // namespace google
524