vecdeque.cpp
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 <random.h> 6 #include <span.h> 7 #include <test/fuzz/util.h> 8 #include <util/vecdeque.h> 9 10 #include <cstdint> 11 #include <deque> 12 13 namespace { 14 15 /** The maximum number of simultaneous buffers kept by the test. */ 16 static constexpr size_t MAX_BUFFERS{3}; 17 /** How many elements are kept in a buffer at most. */ 18 static constexpr size_t MAX_BUFFER_SIZE{48}; 19 /** How many operations are performed at most on the buffers in one test. */ 20 static constexpr size_t MAX_OPERATIONS{1024}; 21 22 /** Perform a simulation fuzz test on VecDeque type T. 23 * 24 * T must be constructible from a uint64_t seed, comparable to other T, copyable, and movable. 25 */ 26 template<typename T, bool CheckNoneLeft> 27 void TestType(std::span<const uint8_t> buffer, uint64_t rng_tweak) 28 { 29 FuzzedDataProvider provider(buffer.data(), buffer.size()); 30 // Local RNG, only used for the seeds to initialize T objects with. 31 InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>() ^ rng_tweak); 32 33 // Real circular buffers. 34 std::vector<VecDeque<T>> real; 35 real.reserve(MAX_BUFFERS); 36 // Simulated circular buffers. 37 std::vector<std::deque<T>> sim; 38 sim.reserve(MAX_BUFFERS); 39 // Temporary object of type T. 40 std::optional<T> tmp; 41 42 // Compare a real and a simulated buffer. 43 auto compare_fn = [](const VecDeque<T>& r, const std::deque<T>& s) { 44 assert(r.size() == s.size()); 45 assert(r.empty() == s.empty()); 46 assert(r.capacity() >= r.size()); 47 if (s.size() == 0) return; 48 assert(r.front() == s.front()); 49 assert(r.back() == s.back()); 50 for (size_t i = 0; i < s.size(); ++i) { 51 assert(r[i] == s[i]); 52 } 53 }; 54 55 LIMITED_WHILE(provider.remaining_bytes(), MAX_OPERATIONS) { 56 int command = provider.ConsumeIntegral<uint8_t>() % 64; 57 unsigned idx = real.empty() ? 0 : provider.ConsumeIntegralInRange<unsigned>(0, real.size() - 1); 58 const size_t num_buffers = sim.size(); 59 // Pick one operation based on value of command. Not all operations are always applicable. 60 // Loop through the applicable ones until command reaches 0 (which avoids the need to 61 // compute the number of applicable commands ahead of time). 62 const bool non_empty{num_buffers != 0}; 63 const bool non_full{num_buffers < MAX_BUFFERS}; 64 const bool partially_full{non_empty && non_full}; 65 const bool multiple_exist{num_buffers > 1}; 66 const bool existing_buffer_non_full{non_empty && sim[idx].size() < MAX_BUFFER_SIZE}; 67 const bool existing_buffer_non_empty{non_empty && !sim[idx].empty()}; 68 assert(non_full || non_empty); 69 while (true) { 70 if (non_full && command-- == 0) { 71 /* Default construct. */ 72 real.emplace_back(); 73 sim.emplace_back(); 74 break; 75 } 76 if (non_empty && command-- == 0) { 77 /* resize() */ 78 compare_fn(real[idx], sim[idx]); 79 size_t new_size = provider.ConsumeIntegralInRange<size_t>(0, MAX_BUFFER_SIZE); 80 real[idx].resize(new_size); 81 sim[idx].resize(new_size); 82 assert(real[idx].size() == new_size); 83 break; 84 } 85 if (non_empty && command-- == 0) { 86 /* clear() */ 87 compare_fn(real[idx], sim[idx]); 88 real[idx].clear(); 89 sim[idx].clear(); 90 assert(real[idx].empty()); 91 break; 92 } 93 if (non_empty && command-- == 0) { 94 /* Copy construct default. */ 95 compare_fn(real[idx], sim[idx]); 96 real[idx] = VecDeque<T>(); 97 sim[idx].clear(); 98 assert(real[idx].size() == 0); 99 break; 100 } 101 if (non_empty && command-- == 0) { 102 /* Destruct. */ 103 compare_fn(real.back(), sim.back()); 104 real.pop_back(); 105 sim.pop_back(); 106 break; 107 } 108 if (partially_full && command-- == 0) { 109 /* Copy construct. */ 110 real.emplace_back(real[idx]); 111 sim.emplace_back(sim[idx]); 112 break; 113 } 114 if (partially_full && command-- == 0) { 115 /* Move construct. */ 116 VecDeque<T> copy(real[idx]); 117 real.emplace_back(std::move(copy)); 118 sim.emplace_back(sim[idx]); 119 break; 120 } 121 if (multiple_exist && command-- == 0) { 122 /* swap() */ 123 swap(real[idx], real[(idx + 1) % num_buffers]); 124 swap(sim[idx], sim[(idx + 1) % num_buffers]); 125 break; 126 } 127 if (multiple_exist && command-- == 0) { 128 /* Copy assign. */ 129 compare_fn(real[idx], sim[idx]); 130 real[idx] = real[(idx + 1) % num_buffers]; 131 sim[idx] = sim[(idx + 1) % num_buffers]; 132 break; 133 } 134 if (multiple_exist && command-- == 0) { 135 /* Move assign. */ 136 VecDeque<T> copy(real[(idx + 1) % num_buffers]); 137 compare_fn(real[idx], sim[idx]); 138 real[idx] = std::move(copy); 139 sim[idx] = sim[(idx + 1) % num_buffers]; 140 break; 141 } 142 if (non_empty && command-- == 0) { 143 /* Self swap() */ 144 swap(real[idx], real[idx]); 145 break; 146 } 147 if (non_empty && command-- == 0) { 148 /* Self-copy assign. */ 149 real[idx] = real[idx]; 150 break; 151 } 152 if (non_empty && command-- == 0) { 153 /* Self-move assign. */ 154 // Do not use std::move(real[idx]) here: -Wself-move correctly warns about that. 155 real[idx] = static_cast<VecDeque<T>&&>(real[idx]); 156 break; 157 } 158 if (non_empty && command-- == 0) { 159 /* reserve() */ 160 size_t res_size = provider.ConsumeIntegralInRange<size_t>(0, MAX_BUFFER_SIZE); 161 size_t old_cap = real[idx].capacity(); 162 size_t old_size = real[idx].size(); 163 real[idx].reserve(res_size); 164 assert(real[idx].size() == old_size); 165 assert(real[idx].capacity() == std::max(old_cap, res_size)); 166 break; 167 } 168 if (non_empty && command-- == 0) { 169 /* shrink_to_fit() */ 170 size_t old_size = real[idx].size(); 171 real[idx].shrink_to_fit(); 172 assert(real[idx].size() == old_size); 173 assert(real[idx].capacity() == old_size); 174 break; 175 } 176 if (existing_buffer_non_full && command-- == 0) { 177 /* push_back() (copying) */ 178 tmp = T(rng.rand64()); 179 size_t old_size = real[idx].size(); 180 size_t old_cap = real[idx].capacity(); 181 real[idx].push_back(*tmp); 182 sim[idx].push_back(*tmp); 183 assert(real[idx].size() == old_size + 1); 184 if (old_cap > old_size) { 185 assert(real[idx].capacity() == old_cap); 186 } else { 187 assert(real[idx].capacity() > old_cap); 188 assert(real[idx].capacity() <= 2 * (old_cap + 1)); 189 } 190 break; 191 } 192 if (existing_buffer_non_full && command-- == 0) { 193 /* push_back() (moving) */ 194 tmp = T(rng.rand64()); 195 size_t old_size = real[idx].size(); 196 size_t old_cap = real[idx].capacity(); 197 sim[idx].push_back(*tmp); 198 real[idx].push_back(std::move(*tmp)); 199 assert(real[idx].size() == old_size + 1); 200 if (old_cap > old_size) { 201 assert(real[idx].capacity() == old_cap); 202 } else { 203 assert(real[idx].capacity() > old_cap); 204 assert(real[idx].capacity() <= 2 * (old_cap + 1)); 205 } 206 break; 207 } 208 if (existing_buffer_non_full && command-- == 0) { 209 /* emplace_back() */ 210 uint64_t seed{rng.rand64()}; 211 size_t old_size = real[idx].size(); 212 size_t old_cap = real[idx].capacity(); 213 sim[idx].emplace_back(seed); 214 real[idx].emplace_back(seed); 215 assert(real[idx].size() == old_size + 1); 216 if (old_cap > old_size) { 217 assert(real[idx].capacity() == old_cap); 218 } else { 219 assert(real[idx].capacity() > old_cap); 220 assert(real[idx].capacity() <= 2 * (old_cap + 1)); 221 } 222 break; 223 } 224 if (existing_buffer_non_full && command-- == 0) { 225 /* push_front() (copying) */ 226 tmp = T(rng.rand64()); 227 size_t old_size = real[idx].size(); 228 size_t old_cap = real[idx].capacity(); 229 real[idx].push_front(*tmp); 230 sim[idx].push_front(*tmp); 231 assert(real[idx].size() == old_size + 1); 232 if (old_cap > old_size) { 233 assert(real[idx].capacity() == old_cap); 234 } else { 235 assert(real[idx].capacity() > old_cap); 236 assert(real[idx].capacity() <= 2 * (old_cap + 1)); 237 } 238 break; 239 } 240 if (existing_buffer_non_full && command-- == 0) { 241 /* push_front() (moving) */ 242 tmp = T(rng.rand64()); 243 size_t old_size = real[idx].size(); 244 size_t old_cap = real[idx].capacity(); 245 sim[idx].push_front(*tmp); 246 real[idx].push_front(std::move(*tmp)); 247 assert(real[idx].size() == old_size + 1); 248 if (old_cap > old_size) { 249 assert(real[idx].capacity() == old_cap); 250 } else { 251 assert(real[idx].capacity() > old_cap); 252 assert(real[idx].capacity() <= 2 * (old_cap + 1)); 253 } 254 break; 255 } 256 if (existing_buffer_non_full && command-- == 0) { 257 /* emplace_front() */ 258 uint64_t seed{rng.rand64()}; 259 size_t old_size = real[idx].size(); 260 size_t old_cap = real[idx].capacity(); 261 sim[idx].emplace_front(seed); 262 real[idx].emplace_front(seed); 263 assert(real[idx].size() == old_size + 1); 264 if (old_cap > old_size) { 265 assert(real[idx].capacity() == old_cap); 266 } else { 267 assert(real[idx].capacity() > old_cap); 268 assert(real[idx].capacity() <= 2 * (old_cap + 1)); 269 } 270 break; 271 } 272 if (existing_buffer_non_empty && command-- == 0) { 273 /* front() [modifying] */ 274 tmp = T(rng.rand64()); 275 size_t old_size = real[idx].size(); 276 assert(sim[idx].front() == real[idx].front()); 277 sim[idx].front() = *tmp; 278 real[idx].front() = std::move(*tmp); 279 assert(real[idx].size() == old_size); 280 break; 281 } 282 if (existing_buffer_non_empty && command-- == 0) { 283 /* back() [modifying] */ 284 tmp = T(rng.rand64()); 285 size_t old_size = real[idx].size(); 286 assert(sim[idx].back() == real[idx].back()); 287 sim[idx].back() = *tmp; 288 real[idx].back() = *tmp; 289 assert(real[idx].size() == old_size); 290 break; 291 } 292 if (existing_buffer_non_empty && command-- == 0) { 293 /* operator[] [modifying] */ 294 tmp = T(rng.rand64()); 295 size_t pos = provider.ConsumeIntegralInRange<size_t>(0, sim[idx].size() - 1); 296 size_t old_size = real[idx].size(); 297 assert(sim[idx][pos] == real[idx][pos]); 298 sim[idx][pos] = *tmp; 299 real[idx][pos] = std::move(*tmp); 300 assert(real[idx].size() == old_size); 301 break; 302 } 303 if (existing_buffer_non_empty && command-- == 0) { 304 /* pop_front() */ 305 assert(sim[idx].front() == real[idx].front()); 306 size_t old_size = real[idx].size(); 307 sim[idx].pop_front(); 308 real[idx].pop_front(); 309 assert(real[idx].size() == old_size - 1); 310 break; 311 } 312 if (existing_buffer_non_empty && command-- == 0) { 313 /* pop_back() */ 314 assert(sim[idx].back() == real[idx].back()); 315 size_t old_size = real[idx].size(); 316 sim[idx].pop_back(); 317 real[idx].pop_back(); 318 assert(real[idx].size() == old_size - 1); 319 break; 320 } 321 } 322 } 323 324 /* Fully compare the final state. */ 325 for (unsigned i = 0; i < sim.size(); ++i) { 326 // Make sure const getters work. 327 const VecDeque<T>& realbuf = real[i]; 328 const std::deque<T>& simbuf = sim[i]; 329 compare_fn(realbuf, simbuf); 330 for (unsigned j = 0; j < sim.size(); ++j) { 331 assert((realbuf == real[j]) == (simbuf == sim[j])); 332 assert(((realbuf <=> real[j]) >= 0) == (simbuf >= sim[j])); 333 assert(((realbuf <=> real[j]) <= 0) == (simbuf <= sim[j])); 334 } 335 // Clear out the buffers so we can check below that no objects exist anymore. 336 sim[i].clear(); 337 real[i].clear(); 338 } 339 340 if constexpr (CheckNoneLeft) { 341 tmp = std::nullopt; 342 T::CheckNoneExist(); 343 } 344 } 345 346 /** Data structure with built-in tracking of all existing objects. */ 347 template<size_t Size> 348 class TrackedObj 349 { 350 static_assert(Size > 0); 351 352 /* Data type for map that actually stores the object data. 353 * 354 * The key is a pointer to the TrackedObj, the value is the uint64_t it was initialized with. 355 * Default-constructed and moved-from objects hold an std::nullopt. 356 */ 357 using track_map_type = std::map<const TrackedObj<Size>*, std::optional<uint64_t>>; 358 359 private: 360 361 /** Actual map. */ 362 static inline track_map_type g_tracker; 363 364 /** Iterators into the tracker map for this object. 365 * 366 * This is an array of size Size, all holding the same value, to give the object configurable 367 * size. The value is g_tracker.end() if this object is not fully initialized. */ 368 typename track_map_type::iterator m_track_entry[Size]; 369 370 void Check() const 371 { 372 auto it = g_tracker.find(this); 373 for (size_t i = 0; i < Size; ++i) { 374 assert(m_track_entry[i] == it); 375 } 376 } 377 378 /** Create entry for this object in g_tracker and populate m_track_entry. */ 379 void Register() 380 { 381 auto [it, inserted] = g_tracker.emplace(this, std::nullopt); 382 assert(inserted); 383 for (size_t i = 0; i < Size; ++i) { 384 m_track_entry[i] = it; 385 } 386 } 387 388 void Deregister() 389 { 390 Check(); 391 assert(m_track_entry[0] != g_tracker.end()); 392 g_tracker.erase(m_track_entry[0]); 393 for (size_t i = 0; i < Size; ++i) { 394 m_track_entry[i] = g_tracker.end(); 395 } 396 } 397 398 /** Get value corresponding to this object in g_tracker. */ 399 std::optional<uint64_t>& Deref() 400 { 401 Check(); 402 assert(m_track_entry[0] != g_tracker.end()); 403 return m_track_entry[0]->second; 404 } 405 406 /** Get value corresponding to this object in g_tracker. */ 407 const std::optional<uint64_t>& Deref() const 408 { 409 Check(); 410 assert(m_track_entry[0] != g_tracker.end()); 411 return m_track_entry[0]->second; 412 } 413 414 public: 415 ~TrackedObj() { Deregister(); } 416 TrackedObj() { Register(); } 417 418 TrackedObj(uint64_t value) 419 { 420 Register(); 421 Deref() = value; 422 } 423 424 TrackedObj(const TrackedObj& other) 425 { 426 Register(); 427 Deref() = other.Deref(); 428 } 429 430 TrackedObj(TrackedObj&& other) 431 { 432 Register(); 433 Deref() = other.Deref(); 434 other.Deref() = std::nullopt; 435 } 436 437 TrackedObj& operator=(const TrackedObj& other) 438 { 439 if (this == &other) return *this; 440 Deref() = other.Deref(); 441 return *this; 442 } 443 444 TrackedObj& operator=(TrackedObj&& other) 445 { 446 if (this == &other) return *this; 447 Deref() = other.Deref(); 448 other.Deref() = std::nullopt; 449 return *this; 450 } 451 452 friend bool operator==(const TrackedObj& a, const TrackedObj& b) 453 { 454 return a.Deref() == b.Deref(); 455 } 456 457 friend std::strong_ordering operator<=>(const TrackedObj& a, const TrackedObj& b) 458 { 459 // Libc++ 15 & 16 do not support std::optional<T>::operator<=> yet. See 460 // https://reviews.llvm.org/D146392. 461 if (!a.Deref().has_value() || !b.Deref().has_value()) { 462 return a.Deref().has_value() <=> b.Deref().has_value(); 463 } 464 return *a.Deref() <=> *b.Deref(); 465 } 466 467 static void CheckNoneExist() 468 { 469 assert(g_tracker.empty()); 470 } 471 }; 472 473 } // namespace 474 475 FUZZ_TARGET(vecdeque) 476 { 477 // Run the test with simple uints (which satisfy all the trivial properties). 478 static_assert(std::is_trivially_copyable_v<uint32_t>); 479 static_assert(std::is_trivially_destructible_v<uint64_t>); 480 TestType<uint8_t, false>(buffer, 1); 481 TestType<uint16_t, false>(buffer, 2); 482 TestType<uint32_t, false>(buffer, 3); 483 TestType<uint64_t, false>(buffer, 4); 484 485 // Run the test with TrackedObjs (which do not). 486 static_assert(!std::is_trivially_copyable_v<TrackedObj<3>>); 487 static_assert(!std::is_trivially_destructible_v<TrackedObj<17>>); 488 TestType<TrackedObj<1>, true>(buffer, 5); 489 TestType<TrackedObj<3>, true>(buffer, 6); 490 TestType<TrackedObj<17>, true>(buffer, 7); 491 }