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", ¬_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