@@ -187,20 +187,34 @@ void MakeConnected(DepGraph<BS>& depgraph)
187187
188188/* * Given a dependency graph, and a todo set, read a topological subset of todo from reader. */
189189template <typename SetType>
190- SetType ReadTopologicalSet (const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader)
190+ SetType ReadTopologicalSet (const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader, bool non_empty )
191191{
192+ // Read a bitmask from the fuzzing input. Add 1 if non_empty, so the mask is definitely not
193+ // zero in that case.
192194 uint64_t mask{0 };
193195 try {
194196 reader >> VARINT (mask);
195197 } catch (const std::ios_base::failure&) {}
198+ mask += non_empty;
199+
196200 SetType ret;
197201 for (auto i : todo) {
198202 if (!ret[i]) {
199203 if (mask & 1 ) ret |= depgraph.Ancestors (i);
200204 mask >>= 1 ;
201205 }
202206 }
203- return ret & todo;
207+ ret &= todo;
208+
209+ // While mask starts off non-zero if non_empty is true, it is still possible that all its low
210+ // bits are 0, and ret ends up being empty. As a last resort, use the in-todo ancestry of the
211+ // first todo position.
212+ if (non_empty && ret.None ()) {
213+ Assume (todo.Any ());
214+ ret = depgraph.Ancestors (todo.First ()) & todo;
215+ Assume (ret.Any ());
216+ }
217+ return ret;
204218}
205219
206220/* * Given a dependency graph, construct any valid linearization for it, reading from a SpanReader. */
@@ -629,10 +643,10 @@ FUZZ_TARGET(clusterlin_ancestor_finder)
629643 assert (real_best_anc.has_value ());
630644 assert (*real_best_anc == best_anc);
631645
632- // Find a topologically valid subset of transactions to remove from the graph.
633- auto del_set = ReadTopologicalSet (depgraph, todo, reader);
634- // If we did not find anything, use best_anc itself, because we should remove something .
635- if ( del_set. None ()) del_set = best_anc. transactions ;
646+ // Find a non-empty topologically valid subset of transactions to remove from the graph.
647+ // Using an empty set would mean the next iteration is identical to the current one, and
648+ // could cause an infinite loop .
649+ auto del_set = ReadTopologicalSet (depgraph, todo, reader, /* non_empty= */ true ) ;
636650 todo -= del_set;
637651 anc_finder.MarkDone (del_set);
638652 }
@@ -708,15 +722,16 @@ FUZZ_TARGET(clusterlin_simple_finder)
708722 assert (exhaustive.feerate == found.feerate );
709723 }
710724
711- // Compare with a topological set read from the fuzz input.
712- auto read_topo = ReadTopologicalSet (depgraph, todo, reader);
713- if (read_topo.Any ()) assert (found.feerate >= depgraph.FeeRate (read_topo));
725+ // Compare with a non-empty topological set read from the fuzz input (comparing with an
726+ // empty set is not interesting).
727+ auto read_topo = ReadTopologicalSet (depgraph, todo, reader, /* non_empty=*/ true );
728+ assert (found.feerate >= depgraph.FeeRate (read_topo));
714729 }
715730
716- // Find a topologically valid subset of transactions to remove from the graph.
717- auto del_set = ReadTopologicalSet (depgraph, todo, reader);
718- // If we did not find anything, use found itself, because we should remove something .
719- if ( del_set. None ()) del_set = found. transactions ;
731+ // Find a non-empty topologically valid subset of transactions to remove from the graph.
732+ // Using an empty set would mean the next iteration is identical to the current one, and
733+ // could cause an infinite loop .
734+ auto del_set = ReadTopologicalSet (depgraph, todo, reader, /* non_empty= */ true ) ;
720735 todo -= del_set;
721736 smp_finder.MarkDone (del_set);
722737 exh_finder.MarkDone (del_set);
@@ -766,8 +781,9 @@ FUZZ_TARGET(clusterlin_search_finder)
766781 } catch (const std::ios_base::failure&) {}
767782 max_iterations &= 0xfffff ;
768783
769- // Read an initial subset from the fuzz input.
770- SetInfo init_best (depgraph, ReadTopologicalSet (depgraph, todo, reader));
784+ // Read an initial subset from the fuzz input (allowed to be empty).
785+ auto init_set = ReadTopologicalSet (depgraph, todo, reader, /* non_empty=*/ false );
786+ SetInfo init_best (depgraph, init_set);
771787
772788 // Call the search finder's FindCandidateSet for what remains of the graph.
773789 auto [found, iterations_done] = src_finder.FindCandidateSet (max_iterations, init_best);
@@ -812,15 +828,16 @@ FUZZ_TARGET(clusterlin_search_finder)
812828 auto anc = anc_finder.FindCandidateSet ();
813829 assert (found.feerate >= anc.feerate );
814830
815- // Compare with a topological set read from the fuzz input.
816- auto read_topo = ReadTopologicalSet (depgraph, todo, reader);
817- if (read_topo.Any ()) assert (found.feerate >= depgraph.FeeRate (read_topo));
831+ // Compare with a non-empty topological set read from the fuzz input (comparing with an
832+ // empty set is not interesting).
833+ auto read_topo = ReadTopologicalSet (depgraph, todo, reader, /* non_empty=*/ true );
834+ assert (found.feerate >= depgraph.FeeRate (read_topo));
818835 }
819836
820- // Find a topologically valid subset of transactions to remove from the graph.
821- auto del_set = ReadTopologicalSet (depgraph, todo, reader);
822- // If we did not find anything, use found itself, because we should remove something .
823- if ( del_set. None ()) del_set = found. transactions ;
837+ // Find a non-empty topologically valid subset of transactions to remove from the graph.
838+ // Using an empty set would mean the next iteration is identical to the current one, and
839+ // could cause an infinite loop .
840+ auto del_set = ReadTopologicalSet (depgraph, todo, reader, /* non_empty= */ true ) ;
824841 todo -= del_set;
825842 src_finder.MarkDone (del_set);
826843 smp_finder.MarkDone (del_set);
@@ -844,9 +861,10 @@ FUZZ_TARGET(clusterlin_linearization_chunking)
844861 reader >> Using<DepGraphFormatter>(depgraph);
845862 } catch (const std::ios_base::failure&) {}
846863
847- // Retrieve a topologically-valid subset of depgraph.
864+ // Retrieve a topologically-valid subset of depgraph (allowed to be empty, because the argument
865+ // to LinearizationChunking::Intersect is allowed to be empty).
848866 auto todo = depgraph.Positions ();
849- auto subset = SetInfo (depgraph, ReadTopologicalSet (depgraph, todo, reader));
867+ auto subset = SetInfo (depgraph, ReadTopologicalSet (depgraph, todo, reader, /* non_empty= */ false ));
850868
851869 // Retrieve a valid linearization for depgraph.
852870 auto linearization = ReadLinearization (depgraph, reader);
@@ -935,13 +953,10 @@ FUZZ_TARGET(clusterlin_linearization_chunking)
935953 }
936954 }
937955
938- // Find a subset to remove from linearization.
939- auto done = ReadTopologicalSet (depgraph, todo, reader);
940- if (done.None ()) {
941- // We need to remove a non-empty subset, so fall back to the unlinearized ancestors of
942- // the first transaction in todo if done is empty.
943- done = depgraph.Ancestors (todo.First ()) & todo;
944- }
956+ // Find a non-empty topologically valid subset of transactions to remove from the graph.
957+ // Using an empty set would mean the next iteration is identical to the current one, and
958+ // could cause an infinite loop.
959+ auto done = ReadTopologicalSet (depgraph, todo, reader, /* non_empty=*/ true );
945960 todo -= done;
946961 chunking.MarkDone (done);
947962 subset = SetInfo (depgraph, subset.transactions - done);
0 commit comments