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
56namespace google {
57namespace protobuf {
58namespace compiler {
59
60namespace {
61
62bool 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
69class MockErrorCollector : public MultiFileErrorCollector {
70 public:
71  MockErrorCollector() {}
72  ~MockErrorCollector() {}
73
74  string text_;
75  string warning_text_;
76
77  // implements ErrorCollector ---------------------------------------
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
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.
94class MockSourceTree : public SourceTree {
95 public:
96  MockSourceTree() {}
97  ~MockSourceTree() {}
98
99  void AddFile(const string& name, const char* contents) {
100    files_[name] = contents;
101  }
102
103  // implements SourceTree -------------------------------------------
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
113  string GetLastErrorMessage() {
114    return "File not found.";
115  }
116
117 private:
118  hash_map<string, const char*> files_;
119};
120
121// ===================================================================
122
123class ImporterTest : public testing::Test {
124 protected:
125  ImporterTest()
126    : importer_(&source_tree_, &error_collector_) {}
127
128  void AddFile(const string& filename, const char* text) {
129    source_tree_.AddFile(filename, text);
130  }
131
132  // Return the collected error text
133  string error() const { return error_collector_.text_; }
134  string warning() const { return error_collector_.warning_text_; }
135
136  MockErrorCollector error_collector_;
137  MockSourceTree source_tree_;
138  Importer importer_;
139};
140
141TEST_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
158TEST_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
195TEST_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
203TEST_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
216TEST_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
239class DiskSourceTreeTest : public testing::Test {
240 protected:
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
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
261  void AddFile(const string& filename, const char* contents) {
262    GOOGLE_CHECK_OK(File::SetContents(filename, contents, true));
263  }
264
265  void AddSubdir(const string& dirname) {
266    GOOGLE_CHECK_OK(File::CreateDir(dirname, 0777));
267  }
268
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
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
299TEST_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
309TEST_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
334TEST_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
351TEST_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
361TEST_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
375TEST_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
392TEST_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
424TEST_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
491TEST_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", &not_touched));
510  EXPECT_EQ("not touched", not_touched);
511  EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("baz/foo", &not_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