datasketches 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/ext/datasketches/cpc_wrapper.cpp +8 -9
  4. data/ext/datasketches/fi_wrapper.cpp +4 -5
  5. data/ext/datasketches/hll_wrapper.cpp +9 -10
  6. data/ext/datasketches/kll_wrapper.cpp +13 -12
  7. data/ext/datasketches/theta_wrapper.cpp +11 -13
  8. data/ext/datasketches/vo_wrapper.cpp +1 -2
  9. data/lib/datasketches/version.rb +1 -1
  10. data/vendor/datasketches-cpp/CMakeLists.txt +5 -3
  11. data/vendor/datasketches-cpp/CODE_OF_CONDUCT.md +1 -1
  12. data/vendor/datasketches-cpp/LICENSE +14 -0
  13. data/vendor/datasketches-cpp/README.md +67 -73
  14. data/vendor/datasketches-cpp/benchmarks/CMakeLists.txt +52 -0
  15. data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch.cpp +153 -0
  16. data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch_serialization.cpp +161 -0
  17. data/vendor/datasketches-cpp/common/CMakeLists.txt +1 -0
  18. data/vendor/datasketches-cpp/common/include/binomial_bounds.hpp +2 -2
  19. data/vendor/datasketches-cpp/common/include/fdlibm_log.hpp +101 -0
  20. data/vendor/datasketches-cpp/common/include/serde.hpp +6 -0
  21. data/vendor/datasketches-cpp/common/test/CMakeLists.txt +44 -2
  22. data/vendor/datasketches-cpp/common/test/binomial_bounds_test.cpp +279 -0
  23. data/vendor/datasketches-cpp/common/test/deserialize_hardening_test.cpp +188 -0
  24. data/vendor/datasketches-cpp/count/include/count_min.hpp +17 -4
  25. data/vendor/datasketches-cpp/count/include/count_min_impl.hpp +65 -83
  26. data/vendor/datasketches-cpp/count/test/count_min_test.cpp +63 -7
  27. data/vendor/datasketches-cpp/cpc/include/compression_data.hpp +2 -0
  28. data/vendor/datasketches-cpp/cpc/include/cpc_compressor_impl.hpp +16 -7
  29. data/vendor/datasketches-cpp/cpc/include/cpc_sketch.hpp +1 -0
  30. data/vendor/datasketches-cpp/cpc/include/cpc_sketch_impl.hpp +54 -36
  31. data/vendor/datasketches-cpp/cpc/include/cpc_union_impl.hpp +27 -27
  32. data/vendor/datasketches-cpp/cpc/include/cpc_util.hpp +7 -7
  33. data/vendor/datasketches-cpp/cpc/include/icon_estimator.hpp +5 -5
  34. data/vendor/datasketches-cpp/cpc/include/u32_table_impl.hpp +16 -16
  35. data/vendor/datasketches-cpp/cpc/test/cpc_sketch_test.cpp +35 -0
  36. data/vendor/datasketches-cpp/fi/include/frequent_items_sketch.hpp +28 -3
  37. data/vendor/datasketches-cpp/fi/include/frequent_items_sketch_impl.hpp +41 -25
  38. data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map.hpp +3 -1
  39. data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map_impl.hpp +10 -5
  40. data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_serialize_for_java.cpp +24 -0
  41. data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_test.cpp +116 -0
  42. data/vendor/datasketches-cpp/filters/include/bloom_filter.hpp +1 -1
  43. data/vendor/datasketches-cpp/filters/include/bloom_filter_impl.hpp +32 -12
  44. data/vendor/datasketches-cpp/filters/test/bloom_filter_test.cpp +28 -1
  45. data/vendor/datasketches-cpp/hll/include/CouponHashSet-internal.hpp +1 -2
  46. data/vendor/datasketches-cpp/hll/include/CouponList-internal.hpp +19 -5
  47. data/vendor/datasketches-cpp/hll/include/CubicInterpolation-internal.hpp +4 -4
  48. data/vendor/datasketches-cpp/hll/include/HarmonicNumbers-internal.hpp +2 -1
  49. data/vendor/datasketches-cpp/hll/include/Hll4Array-internal.hpp +4 -4
  50. data/vendor/datasketches-cpp/hll/include/Hll4Array.hpp +1 -1
  51. data/vendor/datasketches-cpp/hll/include/Hll6Array-internal.hpp +3 -3
  52. data/vendor/datasketches-cpp/hll/include/Hll6Array.hpp +1 -1
  53. data/vendor/datasketches-cpp/hll/include/Hll8Array-internal.hpp +6 -3
  54. data/vendor/datasketches-cpp/hll/include/Hll8Array.hpp +1 -1
  55. data/vendor/datasketches-cpp/hll/include/HllArray-internal.hpp +35 -29
  56. data/vendor/datasketches-cpp/hll/include/HllArray.hpp +1 -1
  57. data/vendor/datasketches-cpp/hll/include/HllSketch-internal.hpp +3 -5
  58. data/vendor/datasketches-cpp/hll/include/HllSketchImpl-internal.hpp +5 -11
  59. data/vendor/datasketches-cpp/hll/include/HllSketchImpl.hpp +2 -4
  60. data/vendor/datasketches-cpp/hll/include/HllSketchImplFactory.hpp +11 -10
  61. data/vendor/datasketches-cpp/hll/include/HllUnion-internal.hpp +17 -8
  62. data/vendor/datasketches-cpp/hll/include/HllUtil.hpp +12 -3
  63. data/vendor/datasketches-cpp/hll/include/coupon_iterator-internal.hpp +2 -2
  64. data/vendor/datasketches-cpp/hll/include/coupon_iterator.hpp +3 -0
  65. data/vendor/datasketches-cpp/hll/include/hll.hpp +11 -4
  66. data/vendor/datasketches-cpp/hll/include/hll.private.hpp +19 -0
  67. data/vendor/datasketches-cpp/hll/test/CMakeLists.txt +2 -0
  68. data/vendor/datasketches-cpp/hll/test/CouponListTest.cpp +64 -0
  69. data/vendor/datasketches-cpp/hll/test/HllFullSizeTest.cpp +137 -0
  70. data/vendor/datasketches-cpp/hll/test/HllKxqRebuildTest.cpp +150 -0
  71. data/vendor/datasketches-cpp/hll/test/HllSketchTest.cpp +3 -3
  72. data/vendor/datasketches-cpp/hll/test/HllUnionTest.cpp +71 -1
  73. data/vendor/datasketches-cpp/kll/include/kll_helper_impl.hpp +10 -10
  74. data/vendor/datasketches-cpp/kll/include/kll_sketch.hpp +10 -1
  75. data/vendor/datasketches-cpp/kll/include/kll_sketch_impl.hpp +33 -24
  76. data/vendor/datasketches-cpp/kll/test/kll_sketch_deserialize_from_java_test.cpp +24 -0
  77. data/vendor/datasketches-cpp/kll/test/kll_sketch_serialize_for_java.cpp +10 -0
  78. data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch.hpp +11 -2
  79. data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch_impl.hpp +24 -15
  80. data/vendor/datasketches-cpp/req/include/req_sketch.hpp +9 -0
  81. data/vendor/datasketches-cpp/req/include/req_sketch_impl.hpp +24 -15
  82. data/vendor/datasketches-cpp/req/test/req_sketch_deserialize_from_java_test.cpp +46 -0
  83. data/vendor/datasketches-cpp/req/test/req_sketch_serialize_for_java.cpp +20 -0
  84. data/vendor/datasketches-cpp/req/test/req_sketch_test.cpp +70 -0
  85. data/vendor/datasketches-cpp/sampling/include/ebpps_sample_impl.hpp +17 -8
  86. data/vendor/datasketches-cpp/sampling/include/ebpps_sketch.hpp +13 -0
  87. data/vendor/datasketches-cpp/sampling/include/var_opt_sketch.hpp +10 -1
  88. data/vendor/datasketches-cpp/sampling/include/var_opt_sketch_impl.hpp +6 -7
  89. data/vendor/datasketches-cpp/sampling/include/var_opt_union.hpp +5 -1
  90. data/vendor/datasketches-cpp/sampling/include/var_opt_union_impl.hpp +2 -1
  91. data/vendor/datasketches-cpp/sampling/test/ebpps_allocation_test.cpp +1 -1
  92. data/vendor/datasketches-cpp/sampling/test/ebpps_sketch_test.cpp +2 -2
  93. data/vendor/datasketches-cpp/sampling/test/var_opt_allocation_test.cpp +1 -1
  94. data/vendor/datasketches-cpp/sampling/test/var_opt_sketch_test.cpp +10 -4
  95. data/vendor/datasketches-cpp/sampling/test/var_opt_union_test.cpp +12 -0
  96. data/vendor/datasketches-cpp/tdigest/include/tdigest.hpp +38 -2
  97. data/vendor/datasketches-cpp/tdigest/include/tdigest_impl.hpp +168 -9
  98. data/vendor/datasketches-cpp/tdigest/test/CMakeLists.txt +1 -0
  99. data/vendor/datasketches-cpp/tdigest/test/tdigest_iterator_test.cpp +274 -0
  100. data/vendor/datasketches-cpp/tdigest/test/tdigest_test.cpp +275 -0
  101. data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser.hpp +2 -0
  102. data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser_impl.hpp +24 -3
  103. data/vendor/datasketches-cpp/theta/include/theta_constants.hpp +4 -2
  104. data/vendor/datasketches-cpp/theta/include/theta_helpers.hpp +32 -0
  105. data/vendor/datasketches-cpp/theta/include/theta_set_difference_base_impl.hpp +4 -2
  106. data/vendor/datasketches-cpp/theta/include/theta_sketch.hpp +22 -4
  107. data/vendor/datasketches-cpp/theta/include/theta_sketch_impl.hpp +60 -38
  108. data/vendor/datasketches-cpp/theta/include/theta_union_base_impl.hpp +2 -6
  109. data/vendor/datasketches-cpp/theta/include/theta_update_sketch_base_impl.hpp +2 -2
  110. data/vendor/datasketches-cpp/theta/test/bit_packing_test.cpp +50 -0
  111. data/vendor/datasketches-cpp/theta/test/theta_a_not_b_test.cpp +22 -0
  112. data/vendor/datasketches-cpp/theta/test/theta_sketch_test.cpp +315 -0
  113. data/vendor/datasketches-cpp/tools/rat-check.sh +68 -0
  114. data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch.hpp +35 -4
  115. data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch_impl.hpp +2 -2
  116. data/vendor/datasketches-cpp/tuple/include/tuple_sketch.hpp +41 -0
  117. data/vendor/datasketches-cpp/tuple/include/tuple_sketch_impl.hpp +5 -4
  118. data/vendor/datasketches-cpp/tuple/test/tuple_sketch_test.cpp +59 -0
  119. data/vendor/datasketches-cpp/version.cfg.in +1 -1
  120. metadata +12 -2
@@ -379,4 +379,39 @@ TEST_CASE("cpc sketch: max serialized size", "[cpc_sketch]") {
379
379
  REQUIRE(cpc_sketch::get_max_serialized_size_bytes(26) == static_cast<size_t>((0.6 * (1 << 26)) + 40));
380
380
  }
381
381
 
382
+ TEST_CASE("cpc sketch: deserialize corrupt num_coupons", "[cpc_sketch]") {
383
+ cpc_sketch sketch(11);
384
+ for (int i = 0; i < 100; i++) sketch.update(i);
385
+ auto bytes = sketch.serialize();
386
+ REQUIRE((bytes[5] & (1 << 3)) != 0); // sparse flavor: table present
387
+ REQUIRE((bytes[5] & (1 << 4)) == 0); // and no window
388
+ uint32_t num_coupons;
389
+ std::memcpy(&num_coupons, bytes.data() + 8, sizeof(num_coupons));
390
+
391
+ // more pairs than the compressed table holds: decoder must not read past it
392
+ auto corrupt = bytes;
393
+ const uint32_t more_coupons = num_coupons + 50;
394
+ std::memcpy(corrupt.data() + 8, &more_coupons, sizeof(more_coupons));
395
+ REQUIRE_THROWS_AS(cpc_sketch::deserialize(corrupt.data(), corrupt.size()), std::out_of_range);
396
+
397
+ // more coupons than the k x 64 bit matrix can hold
398
+ corrupt = bytes;
399
+ const uint32_t too_many_coupons = 64 * 2048 + 1;
400
+ std::memcpy(corrupt.data() + 8, &too_many_coupons, sizeof(too_many_coupons));
401
+ REQUIRE_THROWS_AS(cpc_sketch::deserialize(corrupt.data(), corrupt.size()), std::invalid_argument);
402
+ }
403
+
404
+ TEST_CASE("cpc sketch: deserialize corrupt lg_k", "[cpc_sketch]") {
405
+ cpc_sketch sketch(11);
406
+ for (int i = 0; i < 100; i++) sketch.update(i);
407
+ auto bytes = sketch.serialize();
408
+ for (uint8_t lg_k: {0, 3, 27, 255}) {
409
+ bytes[3] = lg_k;
410
+ REQUIRE_THROWS_AS(cpc_sketch::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
411
+ std::stringstream s;
412
+ s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
413
+ REQUIRE_THROWS_AS(cpc_sketch::deserialize(s), std::invalid_argument);
414
+ }
415
+ }
416
+
382
417
  } /* namespace datasketches */
@@ -44,6 +44,11 @@ enum frequent_items_error_type {
44
44
  * Based on Java implementation here:
45
45
  * https://github.com/apache/datasketches-java/blob/master/src/main/java/org/apache/datasketches/frequencies/ItemsSketch.java
46
46
  * @author Alexander Saydakov
47
+ *
48
+ * Sketch that may retain string values.
49
+ * For sketches containing strings, cross-language portability depends on
50
+ * using compatible string encodings. This class does not by itself enforce
51
+ * UTF-8 validity for all string inputs.
47
52
  */
48
53
  template<
49
54
  typename T,
@@ -74,6 +79,8 @@ public:
74
79
 
75
80
  /**
76
81
  * Update this sketch with an item and a positive weight (frequency count).
82
+ * If cross-language portability is required, callers should ensure that
83
+ * the input string uses a compatible encoding (valid UTF-8).
77
84
  * @param item for which the weight should be increased (lvalue)
78
85
  * @param weight the amount by which the weight of the item should be increased
79
86
  * A count of zero is a no-op, and a negative count will throw an exception.
@@ -82,6 +89,8 @@ public:
82
89
 
83
90
  /**
84
91
  * Update this sketch with an item and a positive weight (frequency count).
92
+ * If cross-language portability is required, callers should ensure that
93
+ * the input string uses a compatible encoding (valid UTF-8).
85
94
  * @param item for which the weight should be increased (rvalue)
86
95
  * @param weight the amount by which the weight of the item should be increased
87
96
  * A count of zero is a no-op, and a negative count will throw an exception.
@@ -91,6 +100,8 @@ public:
91
100
  /**
92
101
  * This function merges the other sketch into this one.
93
102
  * The other sketch may be of a different size.
103
+ * If sketches contain strings, callers are responsible for ensuring that
104
+ * both sketches were built using compatible string encodings.
94
105
  * @param other sketch to be merged into this (lvalue)
95
106
  */
96
107
  void merge(const frequent_items_sketch& other);
@@ -98,11 +109,22 @@ public:
98
109
  /**
99
110
  * This function merges the other sketch into this one.
100
111
  * The other sketch may be of a different size.
112
+ * If sketches contain strings, callers are responsible for ensuring that
113
+ * both sketches were built using compatible string encodings.
101
114
  * @param other sketch to be merged into this (rvalue)
102
115
  */
103
116
  void merge(frequent_items_sketch&& other);
104
117
 
105
118
  /**
119
+ * Resets this sketch to the empty state, as if newly constructed.
120
+ * The maximum map size, equality operator and allocator are retained.
121
+ * The internal hash map restarts at its minimum size.
122
+ */
123
+ void reset();
124
+
125
+ /**
126
+ * A sketch is empty if it has not been updated with any positive weight.
127
+ * A non-empty sketch may retain no items if a purge removed all of them.
106
128
  * @return true if this sketch is empty
107
129
  */
108
130
  bool is_empty() const;
@@ -298,13 +320,16 @@ private:
298
320
  static const uint8_t PREAMBLE_LONGS_EMPTY = 1;
299
321
  static const uint8_t PREAMBLE_LONGS_NONEMPTY = 4;
300
322
  static constexpr double EPSILON_FACTOR = 3.5;
301
- // due to a mistake different bits were used in C++ and Java to indicate empty sketch
302
- // therefore both are set and checked for compatibility with historical binary format
323
+ // Emptiness of a serialized image is determined by preamble longs (1 for empty, 4 otherwise).
324
+ // The flags byte is only cross-checked against it. Due to a mistake different bits were used
325
+ // in C++ and Java to indicate an empty sketch, therefore both are set for compatibility with
326
+ // the historical binary format, and either one is accepted on read. No other flag bits are defined.
303
327
  enum flags { IS_EMPTY_1 = 0, IS_EMPTY_2 = 2 };
304
328
  W total_weight;
305
329
  W offset;
306
330
  reverse_purge_hash_map<T, W, H, E, A> map;
307
- static void check_preamble_longs(uint8_t preamble_longs, bool is_empty);
331
+ static void check_preamble_longs(uint8_t preamble_longs, uint8_t flags_byte);
332
+ static void check_total_weight(W total_weight);
308
333
  static void check_serial_version(uint8_t serial_version);
309
334
  static void check_family_id(uint8_t family_id);
310
335
  static void check_size(uint8_t lg_cur_size, uint8_t lg_max_size);
@@ -45,13 +45,13 @@ map(
45
45
  allocator
46
46
  )
47
47
  {
48
- if (lg_start_map_size > lg_max_map_size) throw std::invalid_argument("starting size must not be greater than maximum size");
48
+ if (lg_start_map_size > lg_max_map_size) { throw std::invalid_argument("starting size must not be greater than maximum size"); }
49
49
  }
50
50
 
51
51
  template<typename T, typename W, typename H, typename E, typename A>
52
52
  void frequent_items_sketch<T, W, H, E, A>::update(const T& item, W weight) {
53
53
  check_weight(weight);
54
- if (weight == 0) return;
54
+ if (weight == 0) { return; }
55
55
  total_weight += weight;
56
56
  offset += map.adjust_or_insert(item, weight);
57
57
  }
@@ -59,14 +59,14 @@ void frequent_items_sketch<T, W, H, E, A>::update(const T& item, W weight) {
59
59
  template<typename T, typename W, typename H, typename E, typename A>
60
60
  void frequent_items_sketch<T, W, H, E, A>::update(T&& item, W weight) {
61
61
  check_weight(weight);
62
- if (weight == 0) return;
62
+ if (weight == 0) { return; }
63
63
  total_weight += weight;
64
64
  offset += map.adjust_or_insert(std::move(item), weight);
65
65
  }
66
66
 
67
67
  template<typename T, typename W, typename H, typename E, typename A>
68
68
  void frequent_items_sketch<T, W, H, E, A>::merge(const frequent_items_sketch& other) {
69
- if (other.is_empty()) return;
69
+ if (other.is_empty()) { return; }
70
70
  const W merged_total_weight = total_weight + other.get_total_weight(); // for correction at the end
71
71
  for (auto it: other.map) {
72
72
  update(it.first, it.second);
@@ -77,7 +77,7 @@ void frequent_items_sketch<T, W, H, E, A>::merge(const frequent_items_sketch& ot
77
77
 
78
78
  template<typename T, typename W, typename H, typename E, typename A>
79
79
  void frequent_items_sketch<T, W, H, E, A>::merge(frequent_items_sketch&& other) {
80
- if (other.is_empty()) return;
80
+ if (other.is_empty()) { return; }
81
81
  const W merged_total_weight = total_weight + other.get_total_weight(); // for correction at the end
82
82
  for (auto it: other.map) {
83
83
  update(std::move(it.first), it.second);
@@ -86,9 +86,18 @@ void frequent_items_sketch<T, W, H, E, A>::merge(frequent_items_sketch&& other)
86
86
  total_weight = merged_total_weight;
87
87
  }
88
88
 
89
+ template<typename T, typename W, typename H, typename E, typename A>
90
+ void frequent_items_sketch<T, W, H, E, A>::reset() {
91
+ map = reverse_purge_hash_map<T, W, H, E, A>(LG_MIN_MAP_SIZE, map.get_lg_max_size(), map.get_equal(), map.get_allocator());
92
+ total_weight = 0;
93
+ offset = 0;
94
+ }
95
+
89
96
  template<typename T, typename W, typename H, typename E, typename A>
90
97
  bool frequent_items_sketch<T, W, H, E, A>::is_empty() const {
91
- return map.get_num_active() == 0;
98
+ // a purge may clear all counters while offset and total_weight remain non-zero;
99
+ // emptiness must mean "no observations", not "no retained items"
100
+ return total_weight == 0;
92
101
  }
93
102
 
94
103
  template<typename T, typename W, typename H, typename E, typename A>
@@ -105,7 +114,7 @@ template<typename T, typename W, typename H, typename E, typename A>
105
114
  W frequent_items_sketch<T, W, H, E, A>::get_estimate(const T& item) const {
106
115
  // if item is tracked estimate = weight + offset, otherwise 0
107
116
  const W weight = map.get(item);
108
- if (weight > 0) return weight + offset;
117
+ if (weight > 0) { return weight + offset; }
109
118
  return 0;
110
119
  }
111
120
 
@@ -210,7 +219,7 @@ void frequent_items_sketch<T, W, H, E, A>::serialize(std::ostream& os, const Ser
210
219
  template<typename T, typename W, typename H, typename E, typename A>
211
220
  template<typename SerDe>
212
221
  size_t frequent_items_sketch<T, W, H, E, A>::get_serialized_size_bytes(const SerDe& sd) const {
213
- if (is_empty()) return PREAMBLE_LONGS_EMPTY * sizeof(uint64_t);
222
+ if (is_empty()) { return PREAMBLE_LONGS_EMPTY * sizeof(uint64_t); }
214
223
  size_t size = PREAMBLE_LONGS_NONEMPTY * sizeof(uint64_t) + map.get_num_active() * sizeof(W);
215
224
  for (auto it: map) size += sd.size_of_item(it.first);
216
225
  return size;
@@ -300,9 +309,8 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
300
309
  const auto flags_byte = read<uint8_t>(is);
301
310
  read<uint16_t>(is); // unused
302
311
 
303
- const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY_1)) | (flags_byte & (1 << flags::IS_EMPTY_2));
304
-
305
- check_preamble_longs(preamble_longs, is_empty);
312
+ check_preamble_longs(preamble_longs, flags_byte);
313
+ const bool is_empty = preamble_longs == PREAMBLE_LONGS_EMPTY;
306
314
  check_serial_version(serial_version);
307
315
  check_family_id(family_id);
308
316
  check_size(lg_cur_size, lg_max_size);
@@ -313,6 +321,7 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
313
321
  read<uint32_t>(is); // unused
314
322
  const auto total_weight = read<W>(is);
315
323
  const auto offset = read<W>(is);
324
+ check_total_weight(total_weight);
316
325
 
317
326
  // batch deserialization with intermediate array of items and weights
318
327
  using AllocW = typename std::allocator_traits<A>::template rebind_alloc<W>;
@@ -328,8 +337,7 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
328
337
  sketch.total_weight = total_weight;
329
338
  sketch.offset = offset;
330
339
  }
331
- if (!is.good())
332
- throw std::runtime_error("error reading from std::istream");
340
+ if (!is.good()) { throw std::runtime_error("error reading from std::istream"); }
333
341
  return sketch;
334
342
  }
335
343
 
@@ -354,9 +362,8 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
354
362
  ptr += copy_from_mem(ptr, flags_byte);
355
363
  ptr += sizeof(uint16_t); // unused
356
364
 
357
- const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY_1)) | (flags_byte & (1 << flags::IS_EMPTY_2));
358
-
359
- check_preamble_longs(preamble_longs, is_empty);
365
+ check_preamble_longs(preamble_longs, flags_byte);
366
+ const bool is_empty = preamble_longs == PREAMBLE_LONGS_EMPTY;
360
367
  check_serial_version(serial_version);
361
368
  check_family_id(family_id);
362
369
  check_size(lg_cur_size, lg_max_size);
@@ -371,6 +378,7 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
371
378
  ptr += copy_from_mem(ptr, total_weight);
372
379
  W offset;
373
380
  ptr += copy_from_mem(ptr, offset);
381
+ check_total_weight(total_weight);
374
382
 
375
383
  ensure_minimum_memory(size, ptr - base + (sizeof(W) * num_items));
376
384
  // batch deserialization with intermediate array of items and weights
@@ -393,15 +401,23 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
393
401
  }
394
402
 
395
403
  template<typename T, typename W, typename H, typename E, typename A>
396
- void frequent_items_sketch<T, W, H, E, A>::check_preamble_longs(uint8_t preamble_longs, bool is_empty) {
397
- if (is_empty) {
398
- if (preamble_longs != PREAMBLE_LONGS_EMPTY) {
399
- throw std::invalid_argument("Possible corruption: preamble longs of an empty sketch must be " + std::to_string(PREAMBLE_LONGS_EMPTY) + ": " + std::to_string(preamble_longs));
400
- }
401
- } else {
402
- if (preamble_longs != PREAMBLE_LONGS_NONEMPTY) {
403
- throw std::invalid_argument("Possible corruption: preamble longs of an non-empty sketch must be " + std::to_string(PREAMBLE_LONGS_NONEMPTY) + ": " + std::to_string(preamble_longs));
404
- }
404
+ void frequent_items_sketch<T, W, H, E, A>::check_preamble_longs(uint8_t preamble_longs, uint8_t flags_byte) {
405
+ if (preamble_longs != PREAMBLE_LONGS_EMPTY && preamble_longs != PREAMBLE_LONGS_NONEMPTY) {
406
+ throw std::invalid_argument("Possible corruption: preamble longs must be " + std::to_string(PREAMBLE_LONGS_EMPTY)
407
+ + " or " + std::to_string(PREAMBLE_LONGS_NONEMPTY) + ": " + std::to_string(preamble_longs));
408
+ }
409
+ const bool empty_flag = (flags_byte & ((1 << flags::IS_EMPTY_1) | (1 << flags::IS_EMPTY_2))) != 0;
410
+ if (empty_flag != (preamble_longs == PREAMBLE_LONGS_EMPTY)) {
411
+ throw std::invalid_argument("Possible corruption: empty flag does not match preamble longs: flags "
412
+ + std::to_string(flags_byte) + ", preamble longs " + std::to_string(preamble_longs));
413
+ }
414
+ }
415
+
416
+ template<typename T, typename W, typename H, typename E, typename A>
417
+ void frequent_items_sketch<T, W, H, E, A>::check_total_weight(W total_weight) {
418
+ // written as !(x > 0) to also reject NaN
419
+ if (!(total_weight > 0)) {
420
+ throw std::invalid_argument("Possible corruption: total weight of a non-empty sketch must be positive");
405
421
  }
406
422
  }
407
423
 
@@ -20,8 +20,9 @@
20
20
  #ifndef REVERSE_PURGE_HASH_MAP_HPP_
21
21
  #define REVERSE_PURGE_HASH_MAP_HPP_
22
22
 
23
- #include <memory>
23
+ #include <cstdint>
24
24
  #include <iterator>
25
+ #include <memory>
25
26
 
26
27
  namespace datasketches {
27
28
 
@@ -61,6 +62,7 @@ public:
61
62
  uint32_t get_capacity() const;
62
63
  uint32_t get_num_active() const;
63
64
  const A& get_allocator() const;
65
+ const E& get_equal() const;
64
66
 
65
67
  class iterator;
66
68
  iterator begin() const;
@@ -74,7 +74,7 @@ states_(nullptr)
74
74
  if (other.states_[i] > 0) {
75
75
  new (&keys_[i]) K(other.keys_[i]);
76
76
  values_[i] = other.values_[i];
77
- if (--num == 0) break;
77
+ if (--num == 0) { break; }
78
78
  }
79
79
  }
80
80
  }
@@ -105,7 +105,7 @@ reverse_purge_hash_map<K, V, H, E, A>::~reverse_purge_hash_map() {
105
105
  for (uint32_t i = 0; i < size; i++) {
106
106
  if (is_active(i)) {
107
107
  keys_[i].~K();
108
- if (--num_active_ == 0) break;
108
+ if (--num_active_ == 0) { break; }
109
109
  }
110
110
  }
111
111
  }
@@ -166,7 +166,7 @@ V reverse_purge_hash_map<K, V, H, E, A>::get(const K& key) const {
166
166
  const uint32_t mask = (1 << lg_cur_size_) - 1;
167
167
  uint32_t probe = fmix64(H()(key)) & mask;
168
168
  while (is_active(probe)) {
169
- if (E()(keys_[probe], key)) return values_[probe];
169
+ if (E()(keys_[probe], key)) { return values_[probe]; }
170
170
  probe = (probe + 1) & mask;
171
171
  }
172
172
  return 0;
@@ -197,6 +197,11 @@ const A& reverse_purge_hash_map<K, V, H, E, A>::get_allocator() const {
197
197
  return allocator_;
198
198
  }
199
199
 
200
+ template<typename K, typename V, typename H, typename E, typename A>
201
+ const E& reverse_purge_hash_map<K, V, H, E, A>::get_equal() const {
202
+ return equal_;
203
+ }
204
+
200
205
  template<typename K, typename V, typename H, typename E, typename A>
201
206
  typename reverse_purge_hash_map<K, V, H, E, A>::iterator reverse_purge_hash_map<K, V, H, E, A>::begin() const {
202
207
  const uint32_t size = 1 << lg_cur_size_;
@@ -271,7 +276,7 @@ void reverse_purge_hash_map<K, V, H, E, A>::hash_delete(uint32_t delete_index) {
271
276
  probe = (probe + 1) & mask;
272
277
  drift++;
273
278
  // only used for theoretical analysis
274
- if (drift >= DRIFT_LIMIT) throw std::logic_error("drift: " + std::to_string(drift) + " >= DRIFT_LIMIT");
279
+ if (drift >= DRIFT_LIMIT) { throw std::logic_error("drift: " + std::to_string(drift) + " >= DRIFT_LIMIT"); }
275
280
  }
276
281
  }
277
282
 
@@ -289,7 +294,7 @@ uint32_t reverse_purge_hash_map<K, V, H, E, A>::internal_adjust_or_insert(const
289
294
  index = (index + 1) & mask;
290
295
  drift++;
291
296
  // only used for theoretical analysis
292
- if (drift >= DRIFT_LIMIT) throw std::logic_error("drift limit reached");
297
+ if (drift >= DRIFT_LIMIT) { throw std::logic_error("drift limit reached"); }
293
298
  }
294
299
  // adding the key and value to the table
295
300
  if (num_active_ > get_capacity()) {
@@ -57,6 +57,30 @@ TEST_CASE("frequent strings sketch generate", "[serialize_for_java]") {
57
57
  }
58
58
  }
59
59
 
60
+ // lg_max_map_size=8 -> capacity 192; the 193rd distinct item triggers a purge
61
+ // whose median (1) removes every counter: non-empty with no retained items
62
+ TEST_CASE("frequent longs sketch purged to zero items", "[serialize_for_java]") {
63
+ frequent_items_sketch<long> sketch(8);
64
+ for (long i = 1; i <= 193; ++i) sketch.update(i);
65
+ REQUIRE_FALSE(sketch.is_empty());
66
+ REQUIRE(sketch.get_num_active_items() == 0);
67
+ REQUIRE(sketch.get_total_weight() == 193);
68
+ REQUIRE(sketch.get_maximum_error() == 1);
69
+ std::ofstream os("frequent_long_purged_cpp.sk", std::ios::binary);
70
+ sketch.serialize(os);
71
+ }
72
+
73
+ TEST_CASE("frequent strings sketch purged to zero items", "[serialize_for_java]") {
74
+ frequent_items_sketch<std::string> sketch(8);
75
+ for (unsigned i = 1; i <= 193; ++i) sketch.update(std::to_string(i));
76
+ REQUIRE_FALSE(sketch.is_empty());
77
+ REQUIRE(sketch.get_num_active_items() == 0);
78
+ REQUIRE(sketch.get_total_weight() == 193);
79
+ REQUIRE(sketch.get_maximum_error() == 1);
80
+ std::ofstream os("frequent_string_purged_cpp.sk", std::ios::binary);
81
+ sketch.serialize(os);
82
+ }
83
+
60
84
  TEST_CASE("frequent strings sketch ascii", "[serialize_for_java]") {
61
85
  frequent_items_sketch<std::string> sketch(6);
62
86
  sketch.update("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1);
@@ -131,6 +131,122 @@ TEST_CASE("frequent items: estimation mode", "[frequent_items_sketch]") {
131
131
  REQUIRE(12 >= items.size()); // but not more than 12 items
132
132
  }
133
133
 
134
+ TEST_CASE("frequent items: purge clearing all counters is not empty", "[frequent_items_sketch]") {
135
+ // lg_k=8 -> capacity 192; the 193rd distinct item triggers a purge whose
136
+ // median (1) wipes every counter. The sketch still holds observations:
137
+ // offset=1, total_weight=193. It must not be considered empty, and must
138
+ // survive serialization and merge without losing weight or error.
139
+ frequent_items_sketch<uint64_t> sketch(8);
140
+ for (uint64_t i = 0; i < 193; ++i) sketch.update(i);
141
+
142
+ REQUIRE(sketch.get_num_active_items() == 0); // purge wiped all counters
143
+ REQUIRE_FALSE(sketch.is_empty());
144
+ REQUIRE(sketch.get_total_weight() == 193);
145
+ REQUIRE(sketch.get_maximum_error() == 1);
146
+
147
+ auto bytes = sketch.serialize();
148
+ auto sketch2 = frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size());
149
+ REQUIRE_FALSE(sketch2.is_empty());
150
+ REQUIRE(sketch2.get_total_weight() == 193);
151
+ REQUIRE(sketch2.get_maximum_error() == 1);
152
+
153
+ frequent_items_sketch<uint64_t> sketch3(8);
154
+ sketch3.update(999999);
155
+ sketch3.merge(sketch);
156
+ REQUIRE(sketch3.get_total_weight() == 194);
157
+ REQUIRE(sketch3.get_maximum_error() == 1);
158
+ }
159
+
160
+ TEST_CASE("frequent items: purge clearing all counters serialized form", "[frequent_items_sketch]") {
161
+ frequent_items_sketch<uint64_t> sketch(8);
162
+ for (uint64_t i = 0; i < 193; ++i) sketch.update(i);
163
+ REQUIRE(sketch.get_num_active_items() == 0);
164
+
165
+ // full preamble with no items
166
+ auto bytes = sketch.serialize();
167
+ REQUIRE(bytes.size() == 32);
168
+ REQUIRE(bytes[0] == 4); // preamble longs
169
+ REQUIRE(bytes[5] == 0); // flags
170
+
171
+ std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);
172
+ sketch.serialize(s);
173
+ auto sketch2 = frequent_items_sketch<uint64_t>::deserialize(s);
174
+ REQUIRE_FALSE(sketch2.is_empty());
175
+ REQUIRE(sketch2.get_num_active_items() == 0);
176
+ REQUIRE(sketch2.get_total_weight() == 193);
177
+ REQUIRE(sketch2.get_maximum_error() == 1);
178
+ }
179
+
180
+ TEST_CASE("frequent items: reset", "[frequent_items_sketch]") {
181
+ frequent_items_sketch<uint64_t> sketch(8);
182
+ for (uint64_t i = 0; i < 1000; ++i) sketch.update(i % 300, i % 7 + 1);
183
+ REQUIRE_FALSE(sketch.is_empty());
184
+ REQUIRE(sketch.get_maximum_error() > 0);
185
+ const double epsilon = sketch.get_epsilon();
186
+
187
+ sketch.reset();
188
+ REQUIRE(sketch.is_empty());
189
+ REQUIRE(sketch.get_num_active_items() == 0);
190
+ REQUIRE(sketch.get_total_weight() == 0);
191
+ REQUIRE(sketch.get_maximum_error() == 0);
192
+ REQUIRE(sketch.get_epsilon() == epsilon); // max map size retained
193
+ REQUIRE(sketch.get_serialized_size_bytes() == 8);
194
+
195
+ // same behavior as a newly constructed sketch
196
+ frequent_items_sketch<uint64_t> fresh(8);
197
+ for (uint64_t i = 0; i < 193; ++i) {
198
+ sketch.update(i);
199
+ fresh.update(i);
200
+ }
201
+ REQUIRE(sketch.serialize() == fresh.serialize());
202
+ }
203
+
204
+ TEST_CASE("frequent items: empty image with either legacy empty flag", "[frequent_items_sketch]") {
205
+ frequent_items_sketch<uint64_t> sketch(8);
206
+ auto bytes = sketch.serialize();
207
+ REQUIRE(bytes.size() == 8);
208
+ REQUIRE(bytes[5] == 5); // both empty bits written
209
+ for (uint8_t flags: {1, 4, 5}) {
210
+ bytes[5] = flags;
211
+ auto sketch2 = frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size());
212
+ REQUIRE(sketch2.is_empty());
213
+ }
214
+ }
215
+
216
+ TEST_CASE("frequent items: corrupt preamble", "[frequent_items_sketch]") {
217
+ frequent_items_sketch<uint64_t> empty_sketch(8);
218
+ frequent_items_sketch<uint64_t> sketch(8);
219
+ sketch.update(1);
220
+
221
+ SECTION("invalid preamble longs") {
222
+ auto bytes = sketch.serialize();
223
+ bytes[0] = 2;
224
+ REQUIRE_THROWS_AS(frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
225
+ }
226
+ SECTION("empty preamble longs, not empty flag") {
227
+ auto bytes = empty_sketch.serialize();
228
+ bytes[5] = 0;
229
+ REQUIRE_THROWS_AS(frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
230
+ }
231
+ SECTION("full preamble longs, empty flag") {
232
+ auto bytes = sketch.serialize();
233
+ bytes[5] = 5;
234
+ REQUIRE_THROWS_AS(frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
235
+ }
236
+ SECTION("full preamble longs, zero total weight, bytes") {
237
+ auto bytes = sketch.serialize();
238
+ for (size_t i = 16; i < 24; ++i) bytes[i] = 0;
239
+ REQUIRE_THROWS_AS(frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
240
+ }
241
+ SECTION("full preamble longs, zero total weight, stream") {
242
+ auto bytes = sketch.serialize();
243
+ for (size_t i = 16; i < 24; ++i) bytes[i] = 0;
244
+ std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);
245
+ s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
246
+ REQUIRE_THROWS_AS(frequent_items_sketch<uint64_t>::deserialize(s), std::invalid_argument);
247
+ }
248
+ }
249
+
134
250
  TEST_CASE("frequent items: merge exact mode", "[frequent_items_sketch]") {
135
251
  frequent_items_sketch<int> sketch1(3);
136
252
  sketch1.update(1);
@@ -624,7 +624,7 @@ private:
624
624
  uint64_t capacity_bits_;
625
625
  uint64_t num_bits_set_;
626
626
  uint8_t* bit_array_; // data backing bit_array_, regardless of ownership
627
- uint8_t* memory_; // if wrapped, pointer to the start of the filter, otheriwse nullptr
627
+ uint8_t* memory_; // if wrapped, pointer to the start of the filter, otherwise nullptr
628
628
  };
629
629
 
630
630
  /**
@@ -37,6 +37,19 @@
37
37
 
38
38
  namespace datasketches {
39
39
 
40
+ static uint64_t validate_bloom_filter_num_bits_set(uint64_t num_bits_set,
41
+ uint8_t* bit_array,
42
+ uint64_t length_bytes,
43
+ uint64_t dirty_bits_value)
44
+ {
45
+ const uint64_t counted_bits_set = bit_array_ops::count_num_bits_set(bit_array, length_bytes);
46
+ if (num_bits_set != dirty_bits_value && num_bits_set != counted_bits_set) {
47
+ throw std::invalid_argument("Possible corruption: invalid number of bits set. Expected "
48
+ + std::to_string(counted_bits_set) + ", found " + std::to_string(num_bits_set));
49
+ }
50
+ return counted_bits_set;
51
+ }
52
+
40
53
  template<typename A>
41
54
  bloom_filter_alloc<A>::bloom_filter_alloc(uint64_t num_bits, uint16_t num_hashes, uint64_t seed, const A& allocator) :
42
55
  allocator_(allocator),
@@ -143,9 +156,6 @@ bloom_filter_alloc<A>::bloom_filter_alloc(uint64_t seed,
143
156
  {
144
157
  // private constructor
145
158
  // no consistency checks since we should have done those prior to calling this
146
- if (is_read_only_ && memory_ != nullptr && num_bits_set == DIRTY_BITS_VALUE) {
147
- num_bits_set_ = bit_array_ops::count_num_bits_set(bit_array_, capacity_bits_ >> 3);
148
- }
149
159
  }
150
160
 
151
161
  template<typename A>
@@ -298,8 +308,7 @@ bloom_filter_alloc<A> bloom_filter_alloc<A>::deserialize(std::istream& is, const
298
308
  return bloom_filter_alloc<A>(num_longs << 6, num_hashes, seed, allocator);
299
309
  }
300
310
 
301
- const uint64_t num_bits_set = read<uint64_t>(is);
302
- const bool is_dirty = (num_bits_set == DIRTY_BITS_VALUE);
311
+ const uint64_t raw_num_bits_set = read<uint64_t>(is);
303
312
 
304
313
  // allocate memory
305
314
  const uint64_t num_bytes = num_longs << 3;
@@ -310,8 +319,17 @@ bloom_filter_alloc<A> bloom_filter_alloc<A>::deserialize(std::istream& is, const
310
319
  }
311
320
  read(is, bit_array, num_bytes);
312
321
 
322
+ uint64_t num_bits_set;
323
+ try {
324
+ num_bits_set = validate_bloom_filter_num_bits_set(
325
+ raw_num_bits_set, bit_array, num_bytes, DIRTY_BITS_VALUE);
326
+ } catch (...) {
327
+ alloc.deallocate(bit_array, num_bytes);
328
+ throw;
329
+ }
330
+
313
331
  // pass to constructor
314
- return bloom_filter_alloc<A>(seed, num_hashes, is_dirty, true, false, num_longs << 6, num_bits_set, bit_array, nullptr, allocator);
332
+ return bloom_filter_alloc<A>(seed, num_hashes, false, true, false, num_longs << 6, num_bits_set, bit_array, nullptr, allocator);
315
333
  }
316
334
 
317
335
  template<typename A>
@@ -374,9 +392,13 @@ bloom_filter_alloc<A> bloom_filter_alloc<A>::internal_deserialize_or_wrap(void*
374
392
  return bloom_filter_alloc<A>(num_longs << 6, num_hashes, seed, allocator);
375
393
  }
376
394
 
377
- uint64_t num_bits_set;
378
- ptr += copy_from_mem(ptr, num_bits_set);
379
- const bool is_dirty = (num_bits_set == DIRTY_BITS_VALUE);
395
+ uint64_t raw_num_bits_set;
396
+ ptr += copy_from_mem(ptr, raw_num_bits_set);
397
+
398
+ const uint64_t num_bytes = num_longs << 3;
399
+ ensure_minimum_memory(end_ptr - ptr, num_bytes);
400
+ const uint64_t num_bits_set = validate_bloom_filter_num_bits_set(
401
+ raw_num_bits_set, const_cast<uint8_t*>(ptr), num_bytes, DIRTY_BITS_VALUE);
380
402
 
381
403
  uint8_t* bit_array;
382
404
  uint8_t* memory;
@@ -386,8 +408,6 @@ bloom_filter_alloc<A> bloom_filter_alloc<A>::internal_deserialize_or_wrap(void*
386
408
  } else {
387
409
  // allocate memory
388
410
  memory = nullptr;
389
- const uint64_t num_bytes = num_longs << 3;
390
- ensure_minimum_memory(end_ptr - ptr, num_bytes);
391
411
  AllocUint8 alloc(allocator);
392
412
  bit_array = alloc.allocate(num_bytes);
393
413
  if (bit_array == nullptr) {
@@ -397,7 +417,7 @@ bloom_filter_alloc<A> bloom_filter_alloc<A>::internal_deserialize_or_wrap(void*
397
417
  }
398
418
 
399
419
  // pass to constructor -- !wrap == is_owned_
400
- return bloom_filter_alloc<A>(seed, num_hashes, is_dirty, !wrap, read_only, num_longs << 6, num_bits_set, bit_array, memory, allocator);
420
+ return bloom_filter_alloc<A>(seed, num_hashes, false, !wrap, read_only, num_longs << 6, num_bits_set, bit_array, memory, allocator);
401
421
  }
402
422
 
403
423
  template<typename A>
@@ -19,6 +19,9 @@
19
19
 
20
20
  #include <catch2/catch.hpp>
21
21
 
22
+ #include <cstring>
23
+ #include <limits>
24
+
22
25
  #include "bloom_filter.hpp"
23
26
 
24
27
  #ifdef TEST_BINARY_INPUT_PATH
@@ -399,8 +402,32 @@ TEST_CASE("bloom_filter: non-empty serialization", "[bloom_filter]") {
399
402
  REQUIRE(bf_writable.query(-1.0));
400
403
 
401
404
  // not good memory management to do this, but because we wrapped the same bytes as both
402
- // read-only adn writable, that update should ahve changed the read-only version, too
405
+ // read-only and writable, that update should have changed the read-only version, too
403
406
  REQUIRE(bf_wrap.query(-1.0));
404
407
  }
405
408
 
409
+ TEST_CASE("bloom_filter: inconsistent num bits set is rejected", "[bloom_filter]") {
410
+ const size_t num_bits_set_offset = 24;
411
+
412
+ auto bf = bloom_filter::builder::create_by_accuracy(100, 0.01);
413
+ bf.update("apple");
414
+ bf.update("banana");
415
+ const uint64_t actual_bits_set = bf.get_bits_used();
416
+ REQUIRE(actual_bits_set > 1);
417
+
418
+ const uint64_t invalid_counts[] = {0, actual_bits_set - 1, actual_bits_set + 1};
419
+ for (const uint64_t serialized_count: invalid_counts) {
420
+ auto bytes = bf.serialize();
421
+ std::memcpy(bytes.data() + num_bits_set_offset, &serialized_count, sizeof(serialized_count));
422
+
423
+ REQUIRE_THROWS_AS(bloom_filter::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
424
+ REQUIRE_THROWS_AS(bloom_filter::wrap(bytes.data(), bytes.size()), std::invalid_argument);
425
+ REQUIRE_THROWS_AS(bloom_filter::writable_wrap(bytes.data(), bytes.size()), std::invalid_argument);
426
+
427
+ const std::string serialized(reinterpret_cast<const char*>(bytes.data()), bytes.size());
428
+ std::istringstream is(serialized, std::ios::in | std::ios::binary);
429
+ REQUIRE_THROWS_AS(bloom_filter::deserialize(is), std::invalid_argument);
430
+ }
431
+ }
432
+
406
433
  } // namespace datasketches
@@ -176,8 +176,7 @@ CouponHashSet<A>* CouponHashSet<A>::newSet(std::istream& is, const A& allocator)
176
176
  read(is, sketch->coupons_.data(), sketch->coupons_.size() * sizeof(uint32_t));
177
177
  }
178
178
 
179
- if (!is.good())
180
- throw std::runtime_error("error reading from std::istream");
179
+ if (!is.good()) { throw std::runtime_error("error reading from std::istream"); }
181
180
 
182
181
  return ptr.release();
183
182
  }