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