Skip to content

Commit da23ece

Browse files
committed
clusterlin tests: support non-empty ReadTopologicalSubset()
In several call sites for ReadTopologicalSubset, a non-empty result is expected, necessitating a special case at the call site for empty results. Fix this by adding a bool non_empty argument, which does this special casing (more efficiently) inside ReadTopologicalSubset itself.
1 parent 94f3e17 commit da23ece

File tree

1 file changed

+46
-31
lines changed

1 file changed

+46
-31
lines changed

src/test/fuzz/cluster_linearize.cpp

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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. */
189189
template<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

Comments
 (0)