|
| 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