kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/cxx/common/kzip_reader_test.cc (about)

     1  /*
     2   * Copyright 2018 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  #include "kythe/cxx/common/kzip_reader.h"
    18  
    19  #include <zip.h>
    20  #include <zipconf.h>
    21  
    22  #include <functional>
    23  #include <string>
    24  
    25  #include "absl/log/log.h"
    26  #include "absl/status/status.h"
    27  #include "absl/status/statusor.h"
    28  #include "absl/strings/str_cat.h"
    29  #include "absl/strings/string_view.h"
    30  #include "absl/strings/strip.h"
    31  #include "gtest/gtest.h"
    32  #include "kythe/cxx/common/index_reader.h"
    33  #include "kythe/cxx/common/libzip/error.h"
    34  #include "kythe/cxx/common/testutil.h"
    35  #include "kythe/proto/go.pb.h"
    36  
    37  namespace kythe {
    38  namespace {
    39  
    40  using absl::StatusCode;
    41  
    42  /// \brief Returns an error for any command except ZIP_SOURCE_ERROR.
    43  zip_int64_t BadZipSource(void* state, void* data, zip_uint64_t len,
    44                           zip_source_cmd_t cmd) {
    45    switch (cmd) {
    46      case ZIP_SOURCE_FREE:
    47        return 0;
    48      case ZIP_SOURCE_ERROR:
    49        return zip_error_to_data(static_cast<zip_error_t*>(state), data, len);
    50      default:
    51        zip_error_set(static_cast<zip_error_t*>(state), ZIP_ER_INVAL, 0);
    52        return -1;
    53    }
    54  }
    55  
    56  using ZipCallback =
    57      std::function<zip_int64_t(void*, zip_uint64_t, zip_source_cmd_t)>;
    58  
    59  /// \brief Trampoline that forwards to a std::function.
    60  zip_int64_t FunctionTrampoline(void* state, void* data, zip_uint64_t len,
    61                                 zip_source_cmd_t cmd) {
    62    return (*static_cast<ZipCallback*>(state))(data, len, cmd);
    63  }
    64  
    65  zip_source_t* ZipSourceFunctionCreate(ZipCallback* callback,
    66                                        zip_error_t* error) {
    67    return zip_source_function_create(FunctionTrampoline,
    68                                      static_cast<void*>(callback), error);
    69  }
    70  
    71  std::string TestFile(absl::string_view basename) {
    72    return absl::StrCat(TestSourceRoot(), "kythe/testdata/platform/",
    73                        absl::StripPrefix(basename, "/"));
    74  }
    75  
    76  TEST(KzipReaderTest, OpenFailsForMissingFile) {
    77    EXPECT_EQ(KzipReader::Open(TestFile("MISSING.kzip")).status().code(),
    78              StatusCode::kNotFound);
    79  }
    80  
    81  TEST(KzipReaderTest, OpenFailsForEmptyFile) {
    82    EXPECT_EQ(KzipReader::Open(TestFile("empty.kzip")).status().code(),
    83              StatusCode::kInvalidArgument);
    84  }
    85  
    86  TEST(KzipReaderTest, OpenFailsForMissingPbUnit) {
    87    EXPECT_EQ(KzipReader::Open(TestFile("missing-pbunit.kzip")).status().code(),
    88              StatusCode::kInvalidArgument);
    89  }
    90  
    91  TEST(KzipReaderTest, OpenFailsForMissingUnit) {
    92    EXPECT_EQ(KzipReader::Open(TestFile("missing-unit.kzip")).status().code(),
    93              StatusCode::kInvalidArgument);
    94  }
    95  
    96  TEST(KzipReaderTest, OpenFailsForMissingRoot) {
    97    EXPECT_EQ(KzipReader::Open(TestFile("malformed.kzip")).status().code(),
    98              StatusCode::kInvalidArgument);
    99  }
   100  
   101  TEST(KzipReaderTest, OpenAndReadSimpleKzip) {
   102    // This forces the GoDetails proto descriptor to be added to the pool so we
   103    // can deserialize it. If we don't do this, we get an error like:
   104    // "Invalid type URL, unknown type: kythe.proto.GoDetails for type Any".
   105    proto::GoDetails needed_for_proto_deserialization;
   106  
   107    absl::StatusOr<IndexReader> reader =
   108        KzipReader::Open(TestFile("stringset.kzip"));
   109    ASSERT_TRUE(reader.ok()) << reader.status();
   110    EXPECT_TRUE(reader
   111                    ->Scan([&](absl::string_view digest) {
   112                      auto unit = reader->ReadUnit(digest);
   113                      if (unit.ok()) {
   114                        for (const auto& file : unit->unit().required_input()) {
   115                          auto data = reader->ReadFile(file.info().digest());
   116                          EXPECT_TRUE(data.ok())
   117                              << "Failed to read file contents: "
   118                              << data.status().ToString();
   119                          if (!data.ok()) {
   120                            return false;
   121                          }
   122                        }
   123                      }
   124                      EXPECT_TRUE(unit.ok())
   125                          << "Failed to read compilation unit: "
   126                          << unit.status().ToString();
   127                      return unit.ok();
   128                    })
   129                    .ok());
   130  }
   131  
   132  TEST(KzipReaderTest, FromSourceFailsIfSourceDoes) {
   133    libzip::Error error;
   134    {
   135      libzip::Error inner;
   136  
   137      zip_source_t* source = zip_source_function_create(
   138          BadZipSource, static_cast<void*>(error.get()), inner.get());
   139      EXPECT_EQ(KzipReader::FromSource(source).status().code(),
   140                StatusCode::kUnimplemented);
   141      zip_source_free(source);
   142    }
   143  }
   144  
   145  // Checks that when there is an error due to a zero-length
   146  // file, the reference count is maintained correctly.
   147  TEST(KzipReaderTest, SourceFreedOnEmptyFile) {
   148    bool freed = false;
   149    zip_error_t error;
   150    ZipCallback callback = [&](void* data, zip_uint64_t len,
   151                               zip_source_cmd_t cmd) -> zip_int64_t {
   152      switch (cmd) {
   153        case ZIP_SOURCE_FREE:
   154          freed = true;
   155          return 0;
   156        case ZIP_SOURCE_STAT:
   157          if (zip_stat_t* stat =
   158                  ZIP_SOURCE_GET_ARGS(zip_stat_t, data, len, &error)) {
   159            stat->size = 0;
   160            stat->valid |= ZIP_STAT_SIZE;
   161            return sizeof(zip_stat_t);
   162          } else {
   163            return -1;
   164          }
   165        case ZIP_SOURCE_ERROR:
   166          return zip_error_to_data(&error, data, len);
   167        case ZIP_SOURCE_SUPPORTS:
   168          // KzipReader requires SEEKABLE.
   169          return ZIP_SOURCE_SUPPORTS_SEEKABLE;
   170        default:
   171          LOG(ERROR) << "Unsupported operation: " << cmd;
   172          zip_error_set(&error, ZIP_ER_INVAL, 0);
   173          return -1;
   174      }
   175    };
   176    {
   177      zip_source_t* source = ZipSourceFunctionCreate(&callback, nullptr);
   178      ASSERT_NE(source, nullptr);
   179      absl::StatusOr<IndexReader> reader = KzipReader::FromSource(source);
   180      ASSERT_EQ(reader.status().code(), StatusCode::kInvalidArgument);
   181      ASSERT_FALSE(freed);
   182      zip_source_free(source);
   183      EXPECT_TRUE(freed);
   184    }
   185  }
   186  
   187  // Checks that when there is an error due to an invalid zip
   188  // file, the reference count is maintained correctly.
   189  TEST(KzipReaderTest, SourceFreedOnInvalidFile) {
   190    bool freed = false;
   191    const absl::string_view buf = "this is not a valid zip file";
   192    zip_source_t* buf_src =
   193        zip_source_buffer_create(buf.data(), buf.size(), false, nullptr);
   194    ASSERT_NE(buf_src, nullptr);
   195  
   196    ZipCallback callback = [&](void* data, zip_uint64_t len,
   197                               zip_source_cmd_t cmd) -> zip_int64_t {
   198      switch (cmd) {
   199        case ZIP_SOURCE_OPEN:
   200          return zip_source_open(buf_src);
   201        case ZIP_SOURCE_READ:
   202          return zip_source_read(buf_src, data, len);
   203        case ZIP_SOURCE_CLOSE:
   204          return zip_source_close(buf_src);
   205        case ZIP_SOURCE_STAT:
   206          if (zip_stat_t* stat =
   207                  ZIP_SOURCE_GET_ARGS(zip_stat_t, data, len, nullptr)) {
   208            return zip_source_stat(buf_src, stat) == 0 ? sizeof(zip_stat_t) : -1;
   209          } else {
   210            return -1;
   211          }
   212        case ZIP_SOURCE_ERROR:
   213          return zip_error_to_data(zip_source_error(buf_src), data, len);
   214        case ZIP_SOURCE_FREE:
   215          freed = true;
   216          zip_source_free(buf_src);
   217          return 0;
   218        case ZIP_SOURCE_SEEK:
   219          if (zip_source_args_seek_t* args = ZIP_SOURCE_GET_ARGS(
   220                  zip_source_args_seek_t, data, len, nullptr)) {
   221            return zip_source_seek(buf_src, args->offset, args->whence);
   222          }
   223          return -1;
   224        case ZIP_SOURCE_TELL:
   225          return zip_source_tell(buf_src);
   226        case ZIP_SOURCE_BEGIN_WRITE:
   227          return zip_source_begin_write(buf_src);
   228        case ZIP_SOURCE_COMMIT_WRITE:
   229          return zip_source_commit_write(buf_src);
   230        case ZIP_SOURCE_ROLLBACK_WRITE:
   231          zip_source_rollback_write(buf_src);
   232          return 0;
   233        case ZIP_SOURCE_WRITE:
   234          return zip_source_write(buf_src, data, len);
   235        case ZIP_SOURCE_SEEK_WRITE:
   236          if (zip_source_args_seek_t* args = ZIP_SOURCE_GET_ARGS(
   237                  zip_source_args_seek_t, data, len, nullptr)) {
   238            return zip_source_seek_write(buf_src, args->offset, args->whence);
   239          }
   240          return -1;
   241        case ZIP_SOURCE_TELL_WRITE:
   242          return zip_source_tell_write(buf_src);
   243        case ZIP_SOURCE_SUPPORTS:
   244          // Cannot wrap this either.
   245          return ZIP_SOURCE_SUPPORTS_SEEKABLE;
   246        case ZIP_SOURCE_REMOVE:
   247          // No way to wrap this call.
   248          return 0;
   249        default:
   250          return -1;
   251      }
   252    };
   253    {
   254      zip_source_t* source = ZipSourceFunctionCreate(&callback, nullptr);
   255      ASSERT_NE(source, nullptr);
   256      absl::StatusOr<IndexReader> reader = KzipReader::FromSource(source);
   257      ASSERT_EQ(reader.status().code(), StatusCode::kInvalidArgument);
   258      ASSERT_FALSE(freed);
   259      zip_source_free(source);
   260      EXPECT_TRUE(freed);
   261    }
   262  }
   263  
   264  // Checks that when there is an error due to a violation
   265  // of kzip-specific requirements the reference count is
   266  // maintained correctly.
   267  TEST(KzipReaderTest, SourceFreedOnEmptyZipFile) {
   268    // A valid zip file, but an invalid Kzip file.
   269    const char buffer[] =
   270        "PK\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
   271        "\x00\x00\x00";
   272    zip_source_t* buf_src =
   273        zip_source_buffer_create(buffer, sizeof(buffer), false, nullptr);
   274    ASSERT_NE(buf_src, nullptr);
   275    bool freed = false;
   276    ZipCallback callback = [&](void* data, zip_uint64_t len,
   277                               zip_source_cmd_t cmd) -> zip_int64_t {
   278      switch (cmd) {
   279        case ZIP_SOURCE_OPEN:
   280          return zip_source_open(buf_src);
   281        case ZIP_SOURCE_READ:
   282          return zip_source_read(buf_src, data, len);
   283        case ZIP_SOURCE_CLOSE:
   284          return zip_source_close(buf_src);
   285        case ZIP_SOURCE_STAT:
   286          if (zip_stat_t* stat =
   287                  ZIP_SOURCE_GET_ARGS(zip_stat_t, data, len, nullptr)) {
   288            return zip_source_stat(buf_src, stat) == 0 ? sizeof(zip_stat_t) : -1;
   289          } else {
   290            return -1;
   291          }
   292        case ZIP_SOURCE_ERROR:
   293          return zip_error_to_data(zip_source_error(buf_src), data, len);
   294        case ZIP_SOURCE_FREE:
   295          freed = true;
   296          zip_source_free(buf_src);
   297          return 0;
   298        case ZIP_SOURCE_SEEK:
   299          if (zip_source_args_seek_t* args = ZIP_SOURCE_GET_ARGS(
   300                  zip_source_args_seek_t, data, len, nullptr)) {
   301            return zip_source_seek(buf_src, args->offset, args->whence);
   302          }
   303          return -1;
   304        case ZIP_SOURCE_TELL:
   305          return zip_source_tell(buf_src);
   306        case ZIP_SOURCE_BEGIN_WRITE:
   307          return zip_source_begin_write(buf_src);
   308        case ZIP_SOURCE_COMMIT_WRITE:
   309          return zip_source_commit_write(buf_src);
   310        case ZIP_SOURCE_ROLLBACK_WRITE:
   311          zip_source_rollback_write(buf_src);
   312          return 0;
   313        case ZIP_SOURCE_WRITE:
   314          return zip_source_write(buf_src, data, len);
   315        case ZIP_SOURCE_SEEK_WRITE:
   316          if (zip_source_args_seek_t* args = ZIP_SOURCE_GET_ARGS(
   317                  zip_source_args_seek_t, data, len, nullptr)) {
   318            return zip_source_seek_write(buf_src, args->offset, args->whence);
   319          }
   320          return -1;
   321        case ZIP_SOURCE_TELL_WRITE:
   322          return zip_source_tell_write(buf_src);
   323        case ZIP_SOURCE_SUPPORTS:
   324          // Cannot wrap this either.
   325          return ZIP_SOURCE_SUPPORTS_SEEKABLE;
   326        case ZIP_SOURCE_REMOVE:
   327          // No way to wrap this call.
   328          return 0;
   329        default:
   330          return -1;
   331      }
   332    };
   333    {
   334      zip_source_t* source = ZipSourceFunctionCreate(&callback, nullptr);
   335      ASSERT_NE(source, nullptr);
   336      absl::StatusOr<IndexReader> reader = KzipReader::FromSource(source);
   337      ASSERT_EQ(reader.status().code(), StatusCode::kInvalidArgument);
   338      ASSERT_FALSE(freed);
   339      zip_source_free(source);
   340      EXPECT_TRUE(freed);
   341    }
   342  }
   343  
   344  TEST(KzipReaderTest, FromSourceReadsSimpleKzip) {
   345    // This forces the GoDetails proto descriptor to be added to the pool so we
   346    // can deserialize it. If we don't do this, we get an error like:
   347    // "Invalid type URL, unknown type: kythe.proto.GoDetails for type Any".
   348    proto::GoDetails needed_for_proto_deserialization;
   349  
   350    libzip::Error error;
   351    absl::StatusOr<IndexReader> reader =
   352        KzipReader::FromSource(zip_source_file_create(
   353            TestFile("stringset.kzip").c_str(), 0, -1, error.get()));
   354  
   355    ASSERT_TRUE(reader.ok()) << reader.status();
   356    EXPECT_TRUE(reader
   357                    ->Scan([&](absl::string_view digest) {
   358                      auto unit = reader->ReadUnit(digest);
   359                      if (unit.ok()) {
   360                        for (const auto& file : unit->unit().required_input()) {
   361                          auto data = reader->ReadFile(file.info().digest());
   362                          EXPECT_TRUE(data.ok())
   363                              << "Failed to read file contents: "
   364                              << data.status().ToString();
   365                          if (!data.ok()) {
   366                            return false;
   367                          }
   368                        }
   369                      }
   370                      EXPECT_TRUE(unit.ok())
   371                          << "Failed to read compilation unit: "
   372                          << unit.status().ToString();
   373                      return unit.ok();
   374                    })
   375                    .ok());
   376  }
   377  }  // namespace
   378  }  // namespace kythe