2222
2323typedef std::array<uint8_t , crypto_shorthash_KEYBYTES> binary_fuse_seed_t ;
2424
25+ #ifdef BUILD_TESTS
26+
27+ #include " test/CovMark.h"
28+
29+ // Test-only flags: force specific rare paths in populate() so tests can
30+ // exercise them without needing pathological hash distributions.
31+ inline bool gBinaryFuseForcePopulateError = false ;
32+ inline bool gBinaryFuseForcePeelingFailure = false ;
33+ #endif
34+
2535#ifndef XOR_MAX_ITERATIONS
2636#define XOR_MAX_ITERATIONS \
2737 1000000 // probability of success should always be > 0.5, so with this many
@@ -256,14 +266,14 @@ template <typename T> class binary_fuse_t
256266 return h;
257267 }
258268
259- // Construct the filter, returns true on success, false on failure.
260- // The algorithm fails when there is insufficient memory .
269+ // Construct the filter. Throws std::runtime_error if population fails
270+ // after max iterations (practically impossible) .
261271 // For best performance, the caller should ensure that there are not
262272 // too many duplicated keys.
263- // While highly improbable, it is possible that the population fails, at
264- // which point the seed must be rotated. keys will be sorted and duplicates
265- // removed if any duplicate keys exist
266- [[nodiscard]] bool
273+ // While highly improbable, it is possible that an iteration fails, at
274+ // which point the seed is rotated and retried . keys will be sorted and
275+ // duplicates removed if any duplicate keys exist
276+ void
267277 populate (std::vector<uint64_t >& keys, binary_fuse_seed_t rngSeed)
268278 {
269279 ZoneScoped;
@@ -295,6 +305,9 @@ template <typename T> class binary_fuse_t
295305 std::vector<uint32_t > startPos (block);
296306 std::vector<uint32_t > h012 (5 );
297307
308+ // Non-zero sentinel past the last valid slot. The placement loop
309+ // treats 0 as empty; this prevents startPos from advancing past
310+ // the end of the array.
298311 reverseOrder.at (size) = 1 ;
299312 for (int loop = 0 ; true ; ++loop)
300313 {
@@ -303,7 +316,8 @@ template <typename T> class binary_fuse_t
303316 // The probability of this happening is lower than the
304317 // the cosmic-ray probability (i.e., a cosmic ray corrupts your
305318 // system).
306- return false ;
319+ throw std::runtime_error (
320+ " BinaryFuseFilter failed to populate after max iterations" );
307321 }
308322
309323 for (uint32_t i = 0 ; i < block; i++)
@@ -363,9 +377,16 @@ template <typename T> class binary_fuse_t
363377 error = (t2count.at (h1) < 4 ) ? 1 : error;
364378 error = (t2count.at (h2) < 4 ) ? 1 : error;
365379 }
380+ #ifdef BUILD_TESTS
381+ if (!error && gBinaryFuseForcePopulateError && loop == 0 )
382+ {
383+ error = 1 ;
384+ gBinaryFuseForcePopulateError = false ;
385+ }
386+ #endif
366387 if (error)
367388 {
368- // Reset everything except the sentinel at reverseOrder[size]
389+ COVMARK_HIT (BINARY_FUSE_POPULATE_ERROR_RETRY);
369390 std::fill_n (reverseOrder.begin (), size, 0 );
370391 std::fill (t2count.begin (), t2count.end (), 0 );
371392 std::fill (t2hash.begin (), t2hash.end (), 0 );
@@ -420,6 +441,14 @@ template <typename T> class binary_fuse_t
420441 t2hash.at (other_index2) ^= hash;
421442 }
422443 }
444+ #ifdef BUILD_TESTS
445+ if (gBinaryFuseForcePeelingFailure && loop == 0 )
446+ {
447+ stacksize = 0 ;
448+ duplicates = 0 ;
449+ gBinaryFuseForcePeelingFailure = false ;
450+ }
451+ #endif
423452 if (stacksize + duplicates == size)
424453 {
425454 // success
@@ -428,12 +457,18 @@ template <typename T> class binary_fuse_t
428457 }
429458 else if (duplicates > 0 )
430459 {
460+ COVMARK_HIT (BINARY_FUSE_DUPLICATE_REMOVAL);
431461 // Sort keys and remove duplicates
432462 std::sort (keys.begin (), keys.end ());
433463 keys.erase (std::unique (keys.begin (), keys.end ()), keys.end ());
434464 size = keys.size ();
465+
466+ // Size may hav decreased, so make sure we write a new sentinel
467+ // value.
468+ reverseOrder.at (size) = 1 ;
435469 }
436470
471+ COVMARK_HIT (BINARY_FUSE_POPULATE_PEELING_FAILURE_RETRY);
437472 // Reset everything except for the last entry in reverseOrder
438473 std::fill_n (reverseOrder.begin (), size, 0 );
439474 std::fill (t2count.begin (), t2count.end (), 0 );
@@ -458,8 +493,6 @@ template <typename T> class binary_fuse_t
458493 Fingerprints.at (h012.at (found)) = xor2 ^ Fingerprints.at (h012.at (found + 1 )) ^
459494 Fingerprints.at (h012.at (found + 2 ));
460495 }
461-
462- return true ;
463496 }
464497
465498 public:
@@ -498,10 +531,7 @@ template <typename T> class binary_fuse_t
498531 SegmentCountLength = SegmentCount * SegmentLength;
499532 Fingerprints.resize (ArrayLength);
500533
501- if (!populate (keys, rngSeed))
502- {
503- throw std::runtime_error (" BinaryFuseFilter failed to populate" );
504- }
534+ populate (keys, rngSeed);
505535 }
506536
507537 explicit binary_fuse_t (stellar::SerializedBinaryFuseFilter const & xdrFilter)
0 commit comments