sst_file_writer_fuzzer.cc 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
  2. // This source code is licensed under both the GPLv2 (found in the
  3. // COPYING file in the root directory) and Apache 2.0 License
  4. // (found in the LICENSE.Apache file in the root directory).
  5. #include <algorithm>
  6. #include <iostream>
  7. #include <memory>
  8. #include <string>
  9. #include "proto/gen/db_operation.pb.h"
  10. #include "rocksdb/file_system.h"
  11. #include "rocksdb/sst_file_writer.h"
  12. #include "src/libfuzzer/libfuzzer_macro.h"
  13. #include "table/table_builder.h"
  14. #include "table/table_reader.h"
  15. #include "util.h"
  16. using ROCKSDB_NAMESPACE::BytewiseComparator;
  17. using ROCKSDB_NAMESPACE::Comparator;
  18. using ROCKSDB_NAMESPACE::EnvOptions;
  19. using ROCKSDB_NAMESPACE::ExternalSstFileInfo;
  20. using ROCKSDB_NAMESPACE::FileOptions;
  21. using ROCKSDB_NAMESPACE::FileSystem;
  22. using ROCKSDB_NAMESPACE::ImmutableCFOptions;
  23. using ROCKSDB_NAMESPACE::ImmutableOptions;
  24. using ROCKSDB_NAMESPACE::InternalIterator;
  25. using ROCKSDB_NAMESPACE::IOOptions;
  26. using ROCKSDB_NAMESPACE::kMaxSequenceNumber;
  27. using ROCKSDB_NAMESPACE::Options;
  28. using ROCKSDB_NAMESPACE::ParsedInternalKey;
  29. using ROCKSDB_NAMESPACE::ParseInternalKey;
  30. using ROCKSDB_NAMESPACE::RandomAccessFileReader;
  31. using ROCKSDB_NAMESPACE::ReadOptions;
  32. using ROCKSDB_NAMESPACE::SstFileWriter;
  33. using ROCKSDB_NAMESPACE::Status;
  34. using ROCKSDB_NAMESPACE::TableReader;
  35. using ROCKSDB_NAMESPACE::TableReaderCaller;
  36. using ROCKSDB_NAMESPACE::TableReaderOptions;
  37. using ROCKSDB_NAMESPACE::ValueType;
  38. // Keys in SST file writer operations must be unique and in ascending order.
  39. // For each DBOperation generated by the fuzzer, this function is called on
  40. // it to deduplicate and sort the keys in the DBOperations.
  41. protobuf_mutator::libfuzzer::PostProcessorRegistration<DBOperations> reg = {
  42. [](DBOperations* input, unsigned int /* seed */) {
  43. const Comparator* comparator = BytewiseComparator();
  44. auto ops = input->mutable_operations();
  45. // Make sure begin <= end for DELETE_RANGE.
  46. for (DBOperation& op : *ops) {
  47. if (op.type() == OpType::DELETE_RANGE) {
  48. auto begin = op.key();
  49. auto end = op.value();
  50. if (comparator->Compare(begin, end) > 0) {
  51. std::swap(begin, end);
  52. op.set_key(begin);
  53. op.set_value(end);
  54. }
  55. }
  56. }
  57. std::sort(ops->begin(), ops->end(),
  58. [&comparator](const DBOperation& a, const DBOperation& b) {
  59. return comparator->Compare(a.key(), b.key()) < 0;
  60. });
  61. auto last = std::unique(
  62. ops->begin(), ops->end(),
  63. [&comparator](const DBOperation& a, const DBOperation& b) {
  64. return comparator->Compare(a.key(), b.key()) == 0;
  65. });
  66. ops->erase(last, ops->end());
  67. }};
  68. TableReader* NewTableReader(const std::string& sst_file_path,
  69. const Options& options,
  70. const EnvOptions& env_options,
  71. const ImmutableCFOptions& cf_ioptions) {
  72. // This code block is similar to SstFileReader::Open.
  73. uint64_t file_size = 0;
  74. std::unique_ptr<RandomAccessFileReader> file_reader;
  75. std::unique_ptr<TableReader> table_reader;
  76. const auto& fs = options.env->GetFileSystem();
  77. FileOptions fopts(env_options);
  78. Status s = options.env->GetFileSize(sst_file_path, &file_size);
  79. if (s.ok()) {
  80. s = RandomAccessFileReader::Create(fs, sst_file_path, fopts, &file_reader,
  81. nullptr);
  82. }
  83. if (s.ok()) {
  84. ImmutableOptions iopts(options, cf_ioptions);
  85. TableReaderOptions t_opt(iopts, /*prefix_extractor=*/nullptr,
  86. /*compression_manager=*/nullptr, env_options,
  87. cf_ioptions.internal_comparator,
  88. 0 /* block_protection_bytes_per_key */);
  89. t_opt.largest_seqno = kMaxSequenceNumber;
  90. s = options.table_factory->NewTableReader(t_opt, std::move(file_reader),
  91. file_size, &table_reader,
  92. /*prefetch=*/false);
  93. }
  94. if (!s.ok()) {
  95. std::cerr << "Failed to create TableReader for " << sst_file_path << ": "
  96. << s.ToString() << std::endl;
  97. abort();
  98. }
  99. return table_reader.release();
  100. }
  101. ValueType ToValueType(OpType op_type) {
  102. switch (op_type) {
  103. case OpType::PUT:
  104. return ValueType::kTypeValue;
  105. case OpType::MERGE:
  106. return ValueType::kTypeMerge;
  107. case OpType::DELETE:
  108. return ValueType::kTypeDeletion;
  109. case OpType::DELETE_RANGE:
  110. return ValueType::kTypeRangeDeletion;
  111. default:
  112. std::cerr << "Unknown operation type " << static_cast<int>(op_type)
  113. << std::endl;
  114. abort();
  115. }
  116. }
  117. // Fuzzes DB operations as input, let SstFileWriter generate a SST file
  118. // according to the operations, then let TableReader read and check all the
  119. // key-value pairs from the generated SST file.
  120. DEFINE_PROTO_FUZZER(DBOperations& input) {
  121. if (input.operations().empty()) {
  122. return;
  123. }
  124. std::string sstfile;
  125. {
  126. auto fs = FileSystem::Default();
  127. std::string dir;
  128. IOOptions opt;
  129. CHECK_OK(fs->GetTestDirectory(opt, &dir, nullptr));
  130. sstfile = dir + "/SstFileWriterFuzzer.sst";
  131. }
  132. Options options;
  133. EnvOptions env_options(options);
  134. ImmutableCFOptions cf_ioptions(options);
  135. // Generate sst file.
  136. SstFileWriter writer(env_options, options);
  137. CHECK_OK(writer.Open(sstfile));
  138. for (const DBOperation& op : input.operations()) {
  139. switch (op.type()) {
  140. case OpType::PUT: {
  141. CHECK_OK(writer.Put(op.key(), op.value()));
  142. break;
  143. }
  144. case OpType::MERGE: {
  145. CHECK_OK(writer.Merge(op.key(), op.value()));
  146. break;
  147. }
  148. case OpType::DELETE: {
  149. CHECK_OK(writer.Delete(op.key()));
  150. break;
  151. }
  152. case OpType::DELETE_RANGE: {
  153. CHECK_OK(writer.DeleteRange(op.key(), op.value()));
  154. break;
  155. }
  156. default: {
  157. std::cerr << "Unsupported operation" << static_cast<int>(op.type())
  158. << std::endl;
  159. abort();
  160. }
  161. }
  162. }
  163. ExternalSstFileInfo info;
  164. CHECK_OK(writer.Finish(&info));
  165. // Iterate and verify key-value pairs.
  166. std::unique_ptr<TableReader> table_reader(
  167. ::NewTableReader(sstfile, options, env_options, cf_ioptions));
  168. ReadOptions roptions;
  169. CHECK_OK(table_reader->VerifyChecksum(roptions,
  170. TableReaderCaller::kUncategorized));
  171. std::unique_ptr<InternalIterator> it(
  172. table_reader->NewIterator(roptions, /*prefix_extractor=*/nullptr,
  173. /*arena=*/nullptr, /*skip_filters=*/true,
  174. TableReaderCaller::kUncategorized));
  175. it->SeekToFirst();
  176. for (const DBOperation& op : input.operations()) {
  177. if (op.type() == OpType::DELETE_RANGE) {
  178. // InternalIterator cannot iterate over DELETE_RANGE entries.
  179. continue;
  180. }
  181. CHECK_TRUE(it->Valid());
  182. ParsedInternalKey ikey;
  183. CHECK_OK(ParseInternalKey(it->key(), &ikey, /*log_err_key=*/true));
  184. CHECK_EQ(ikey.user_key.ToString(), op.key());
  185. CHECK_EQ(ikey.sequence, 0);
  186. CHECK_EQ(ikey.type, ToValueType(op.type()));
  187. if (op.type() != OpType::DELETE) {
  188. CHECK_EQ(op.value(), it->value().ToString());
  189. }
  190. it->Next();
  191. }
  192. CHECK_TRUE(!it->Valid());
  193. // Delete sst file.
  194. remove(sstfile.c_str());
  195. }