Home | History | Annotate | Download | only in compiler
      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 (at) 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 
     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:
     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.
     94 class 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 
    123 class 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 
    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 
    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 
    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 
    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 
    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:
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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", &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