Skip to content

Commit 47f705b

Browse files
committed
tests: add fuzz tests for BitSet
1 parent 59a6df6 commit 47f705b

File tree

2 files changed

+317
-0
lines changed

2 files changed

+317
-0
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ test_fuzz_fuzz_SOURCES = \
294294
test/fuzz/bech32.cpp \
295295
test/fuzz/bip324.cpp \
296296
test/fuzz/bitdeque.cpp \
297+
test/fuzz/bitset.cpp \
297298
test/fuzz/block.cpp \
298299
test/fuzz/block_header.cpp \
299300
test/fuzz/blockfilter.cpp \

src/test/fuzz/bitset.cpp

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
// Copyright (c) The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <span.h>
6+
#include <test/fuzz/util.h>
7+
#include <test/util/xoroshiro128plusplus.h>
8+
#include <util/bitset.h>
9+
10+
#include <bitset>
11+
#include <vector>
12+
13+
namespace {
14+
15+
/** Pop the first byte from a Span<const uint8_t>, and return it. */
16+
uint8_t ReadByte(Span<const uint8_t>& buffer)
17+
{
18+
if (buffer.empty()) return 0;
19+
uint8_t ret = buffer.front();
20+
buffer = buffer.subspan(1);
21+
return ret;
22+
}
23+
24+
/** Perform a simulation fuzz test on BitSet type S. */
25+
template<typename S>
26+
void TestType(Span<const uint8_t> buffer)
27+
{
28+
/** This fuzz test's design is based on the assumption that the actual bits stored in the
29+
* bitsets and their simulations do not matter for the purpose of detecting edge cases, thus
30+
* these are taken from a deterministically-seeded RNG instead. To provide some level of
31+
* variation however, pick the seed based on the buffer size and size of the chosen bitset. */
32+
XoRoShiRo128PlusPlus rng(buffer.size() + 0x10000 * S::Size());
33+
34+
using Sim = std::bitset<S::Size()>;
35+
// Up to 4 real BitSets (initially 2).
36+
std::vector<S> real(2);
37+
// Up to 4 std::bitsets with the same corresponding contents.
38+
std::vector<Sim> sim(2);
39+
40+
/* Compare sim[idx] with real[idx], using all inspector operations. */
41+
auto compare_fn = [&](unsigned idx) {
42+
/* iterators and operator[] */
43+
auto it = real[idx].begin();
44+
unsigned first = S::Size();
45+
unsigned last = S::Size();
46+
for (unsigned i = 0; i < S::Size(); ++i) {
47+
bool match = (it != real[idx].end()) && *it == i;
48+
assert(sim[idx][i] == real[idx][i]);
49+
assert(match == real[idx][i]);
50+
assert((it == real[idx].end()) != (it != real[idx].end()));
51+
if (match) {
52+
++it;
53+
if (first == S::Size()) first = i;
54+
last = i;
55+
}
56+
}
57+
assert(it == real[idx].end());
58+
assert(!(it != real[idx].end()));
59+
/* Any / None */
60+
assert(sim[idx].any() == real[idx].Any());
61+
assert(sim[idx].none() == real[idx].None());
62+
/* First / Last */
63+
if (sim[idx].any()) {
64+
assert(first == real[idx].First());
65+
assert(last == real[idx].Last());
66+
}
67+
/* Count */
68+
assert(sim[idx].count() == real[idx].Count());
69+
};
70+
71+
LIMITED_WHILE(buffer.size() > 0, 1000) {
72+
// Read one byte to determine which operation to execute on the BitSets.
73+
int command = ReadByte(buffer) % 64;
74+
// Read another byte that determines which bitsets will be involved.
75+
unsigned args = ReadByte(buffer);
76+
unsigned dest = ((args & 7) * sim.size()) >> 3;
77+
unsigned src = (((args >> 3) & 7) * sim.size()) >> 3;
78+
unsigned aux = (((args >> 6) & 3) * sim.size()) >> 2;
79+
// Args are in range for non-empty sim, or sim is completely empty and will be grown
80+
assert((sim.empty() && dest == 0 && src == 0 && aux == 0) ||
81+
(!sim.empty() && dest < sim.size() && src < sim.size() && aux < sim.size()));
82+
83+
// Pick one operation based on value of command. Not all operations are always applicable.
84+
// Loop through the applicable ones until command reaches 0 (which avoids the need to
85+
// compute the number of applicable commands ahead of time).
86+
while (true) {
87+
if (dest < sim.size() && command-- == 0) {
88+
/* Set() (true) */
89+
unsigned val = ReadByte(buffer) % S::Size();
90+
assert(sim[dest][val] == real[dest][val]);
91+
sim[dest].set(val);
92+
real[dest].Set(val);
93+
break;
94+
} else if (dest < sim.size() && command-- == 0) {
95+
/* Reset() */
96+
unsigned val = ReadByte(buffer) % S::Size();
97+
assert(sim[dest][val] == real[dest][val]);
98+
sim[dest].reset(val);
99+
real[dest].Reset(val);
100+
break;
101+
} else if (dest < sim.size() && command-- == 0) {
102+
/* Set() (conditional) */
103+
unsigned val = ReadByte(buffer) % S::Size();
104+
assert(sim[dest][val] == real[dest][val]);
105+
sim[dest].set(val, args >> 7);
106+
real[dest].Set(val, args >> 7);
107+
break;
108+
} else if (sim.size() < 4 && command-- == 0) {
109+
/* Construct empty. */
110+
sim.resize(sim.size() + 1);
111+
real.resize(real.size() + 1);
112+
break;
113+
} else if (sim.size() < 4 && command-- == 0) {
114+
/* Construct singleton. */
115+
unsigned val = ReadByte(buffer) % S::Size();
116+
std::bitset<S::Size()> newset;
117+
newset[val] = true;
118+
sim.push_back(newset);
119+
real.push_back(S::Singleton(val));
120+
break;
121+
} else if (dest < sim.size() && command-- == 0) {
122+
/* Make random. */
123+
compare_fn(dest);
124+
sim[dest].reset();
125+
real[dest] = S{};
126+
for (unsigned i = 0; i < S::Size(); ++i) {
127+
if (rng() & 1) {
128+
sim[dest][i] = true;
129+
real[dest].Set(i);
130+
}
131+
}
132+
break;
133+
} else if (dest < sim.size() && command-- == 0) {
134+
/* Assign initializer list. */
135+
unsigned r1 = rng() % S::Size();
136+
unsigned r2 = rng() % S::Size();
137+
unsigned r3 = rng() % S::Size();
138+
compare_fn(dest);
139+
sim[dest].reset();
140+
real[dest] = {r1, r2, r3};
141+
sim[dest].set(r1);
142+
sim[dest].set(r2);
143+
sim[dest].set(r3);
144+
break;
145+
} else if (!sim.empty() && command-- == 0) {
146+
/* Destruct. */
147+
compare_fn(sim.size() - 1);
148+
sim.pop_back();
149+
real.pop_back();
150+
break;
151+
} else if (sim.size() < 4 && src < sim.size() && command-- == 0) {
152+
/* Copy construct. */
153+
sim.emplace_back(sim[src]);
154+
real.emplace_back(real[src]);
155+
break;
156+
} else if (src < sim.size() && dest < sim.size() && command-- == 0) {
157+
/* Copy assign. */
158+
compare_fn(dest);
159+
sim[dest] = sim[src];
160+
real[dest] = real[src];
161+
break;
162+
} else if (src < sim.size() && dest < sim.size() && command-- == 0) {
163+
/* swap() function. */
164+
swap(sim[dest], sim[src]);
165+
swap(real[dest], real[src]);
166+
break;
167+
} else if (sim.size() < 4 && command-- == 0) {
168+
/* Construct with initializer list. */
169+
unsigned r1 = rng() % S::Size();
170+
unsigned r2 = rng() % S::Size();
171+
sim.emplace_back();
172+
sim.back().set(r1);
173+
sim.back().set(r2);
174+
real.push_back(S{r1, r2});
175+
break;
176+
} else if (dest < sim.size() && command-- == 0) {
177+
/* Fill() + copy assign. */
178+
unsigned len = ReadByte(buffer) % S::Size();
179+
compare_fn(dest);
180+
sim[dest].reset();
181+
for (unsigned i = 0; i < len; ++i) sim[dest][i] = true;
182+
real[dest] = S::Fill(len);
183+
break;
184+
} else if (src < sim.size() && command-- == 0) {
185+
/* Iterator copy based compare. */
186+
unsigned val = ReadByte(buffer) % S::Size();
187+
/* In a first loop, compare begin..end, and copy to it_copy at some point. */
188+
auto it = real[src].begin(), it_copy = it;
189+
for (unsigned i = 0; i < S::Size(); ++i) {
190+
if (i == val) it_copy = it;
191+
bool match = (it != real[src].end()) && *it == i;
192+
assert(match == sim[src][i]);
193+
if (match) ++it;
194+
}
195+
assert(it == real[src].end());
196+
/* Then compare from the copied point again to end. */
197+
for (unsigned i = val; i < S::Size(); ++i) {
198+
bool match = (it_copy != real[src].end()) && *it_copy == i;
199+
assert(match == sim[src][i]);
200+
if (match) ++it_copy;
201+
}
202+
assert(it_copy == real[src].end());
203+
break;
204+
} else if (src < sim.size() && dest < sim.size() && command-- == 0) {
205+
/* operator|= */
206+
compare_fn(dest);
207+
sim[dest] |= sim[src];
208+
real[dest] |= real[src];
209+
break;
210+
} else if (src < sim.size() && dest < sim.size() && command-- == 0) {
211+
/* operator&= */
212+
compare_fn(dest);
213+
sim[dest] &= sim[src];
214+
real[dest] &= real[src];
215+
break;
216+
} else if (src < sim.size() && dest < sim.size() && command-- == 0) {
217+
/* operator-= */
218+
compare_fn(dest);
219+
sim[dest] &= ~sim[src];
220+
real[dest] -= real[src];
221+
break;
222+
} else if (src < sim.size() && dest < sim.size() && command-- == 0) {
223+
/* operator^= */
224+
compare_fn(dest);
225+
sim[dest] ^= sim[src];
226+
real[dest] ^= real[src];
227+
break;
228+
} else if (src < sim.size() && dest < sim.size() && aux < sim.size() && command-- == 0) {
229+
/* operator| */
230+
compare_fn(dest);
231+
sim[dest] = sim[src] | sim[aux];
232+
real[dest] = real[src] | real[aux];
233+
break;
234+
} else if (src < sim.size() && dest < sim.size() && aux < sim.size() && command-- == 0) {
235+
/* operator& */
236+
compare_fn(dest);
237+
sim[dest] = sim[src] & sim[aux];
238+
real[dest] = real[src] & real[aux];
239+
break;
240+
} else if (src < sim.size() && dest < sim.size() && aux < sim.size() && command-- == 0) {
241+
/* operator- */
242+
compare_fn(dest);
243+
sim[dest] = sim[src] & ~sim[aux];
244+
real[dest] = real[src] - real[aux];
245+
break;
246+
} else if (src < sim.size() && dest < sim.size() && aux < sim.size() && command-- == 0) {
247+
/* operator^ */
248+
compare_fn(dest);
249+
sim[dest] = sim[src] ^ sim[aux];
250+
real[dest] = real[src] ^ real[aux];
251+
break;
252+
} else if (src < sim.size() && aux < sim.size() && command-- == 0) {
253+
/* IsSupersetOf() and IsSubsetOf() */
254+
bool is_superset = (sim[aux] & ~sim[src]).none();
255+
bool is_subset = (sim[src] & ~sim[aux]).none();
256+
assert(real[src].IsSupersetOf(real[aux]) == is_superset);
257+
assert(real[src].IsSubsetOf(real[aux]) == is_subset);
258+
assert(real[aux].IsSupersetOf(real[src]) == is_subset);
259+
assert(real[aux].IsSubsetOf(real[src]) == is_superset);
260+
break;
261+
} else if (src < sim.size() && aux < sim.size() && command-- == 0) {
262+
/* operator== and operator!= */
263+
assert((sim[src] == sim[aux]) == (real[src] == real[aux]));
264+
assert((sim[src] != sim[aux]) == (real[src] != real[aux]));
265+
break;
266+
} else if (src < sim.size() && aux < sim.size() && command-- == 0) {
267+
/* Overlaps() */
268+
assert((sim[src] & sim[aux]).any() == real[src].Overlaps(real[aux]));
269+
assert((sim[src] & sim[aux]).any() == real[aux].Overlaps(real[src]));
270+
break;
271+
}
272+
}
273+
}
274+
/* Fully compare the final state. */
275+
for (unsigned i = 0; i < sim.size(); ++i) {
276+
compare_fn(i);
277+
}
278+
}
279+
280+
} // namespace
281+
282+
FUZZ_TARGET(bitset)
283+
{
284+
unsigned typdat = ReadByte(buffer) % 8;
285+
if (typdat == 0) {
286+
/* 16 bits */
287+
TestType<bitset_detail::IntBitSet<uint16_t>>(buffer);
288+
TestType<bitset_detail::MultiIntBitSet<uint16_t, 1>>(buffer);
289+
} else if (typdat == 1) {
290+
/* 32 bits */
291+
TestType<bitset_detail::MultiIntBitSet<uint16_t, 2>>(buffer);
292+
TestType<bitset_detail::IntBitSet<uint32_t>>(buffer);
293+
} else if (typdat == 2) {
294+
/* 48 bits */
295+
TestType<bitset_detail::MultiIntBitSet<uint16_t, 3>>(buffer);
296+
} else if (typdat == 3) {
297+
/* 64 bits */
298+
TestType<bitset_detail::IntBitSet<uint64_t>>(buffer);
299+
TestType<bitset_detail::MultiIntBitSet<uint64_t, 1>>(buffer);
300+
TestType<bitset_detail::MultiIntBitSet<uint32_t, 2>>(buffer);
301+
TestType<bitset_detail::MultiIntBitSet<uint16_t, 4>>(buffer);
302+
} else if (typdat == 4) {
303+
/* 96 bits */
304+
TestType<bitset_detail::MultiIntBitSet<uint32_t, 3>>(buffer);
305+
} else if (typdat == 5) {
306+
/* 128 bits */
307+
TestType<bitset_detail::MultiIntBitSet<uint64_t, 2>>(buffer);
308+
TestType<bitset_detail::MultiIntBitSet<uint32_t, 4>>(buffer);
309+
} else if (typdat == 6) {
310+
/* 192 bits */
311+
TestType<bitset_detail::MultiIntBitSet<uint64_t, 3>>(buffer);
312+
} else if (typdat == 7) {
313+
/* 256 bits */
314+
TestType<bitset_detail::MultiIntBitSet<uint64_t, 4>>(buffer);
315+
}
316+
}

0 commit comments

Comments
 (0)