#include <random.h>
#include <span.h>
#include <test/fuzz/util.h>
#include <util/vecdeque.h>
#include <cstdint>
#include <deque>
namespace {
static constexpr size_t MAX_BUFFERS{3};
static constexpr size_t MAX_BUFFER_SIZE{48};
static constexpr size_t MAX_OPERATIONS{1024};
template<typename T, bool CheckNoneLeft>
void TestType(std::span<const uint8_t> buffer, uint64_t rng_tweak)
{
FuzzedDataProvider provider(buffer.data(), buffer.size());
InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>() ^ rng_tweak);
std::vector<VecDeque<T>> real;
real.reserve(MAX_BUFFERS);
std::vector<std::deque<T>> sim;
sim.reserve(MAX_BUFFERS);
std::optional<T> tmp;
auto compare_fn = [](const VecDeque<T>& r, const std::deque<T>& s) {
assert(r.size() == s.size());
assert(r.empty() == s.empty());
assert(r.capacity() >= r.size());
if (s.size() == 0) return;
assert(r.front() == s.front());
assert(r.back() == s.back());
for (size_t i = 0; i < s.size(); ++i) {
assert(r[i] == s[i]);
}
};
LIMITED_WHILE(provider.remaining_bytes(), MAX_OPERATIONS) {
int command = provider.ConsumeIntegral<uint8_t>() % 64;
unsigned idx = real.empty() ? 0 : provider.ConsumeIntegralInRange<unsigned>(0, real.size() - 1);
const size_t num_buffers = sim.size();
const bool non_empty{num_buffers != 0};
const bool non_full{num_buffers < MAX_BUFFERS};
const bool partially_full{non_empty && non_full};
const bool multiple_exist{num_buffers > 1};
const bool existing_buffer_non_full{non_empty && sim[idx].size() < MAX_BUFFER_SIZE};
const bool existing_buffer_non_empty{non_empty && !sim[idx].empty()};
assert(non_full || non_empty);
while (true) {
if (non_full && command-- == 0) {
real.emplace_back();
sim.emplace_back();
break;
}
if (non_empty && command-- == 0) {
compare_fn(real[idx], sim[idx]);
size_t new_size = provider.ConsumeIntegralInRange<size_t>(0, MAX_BUFFER_SIZE);
real[idx].resize(new_size);
sim[idx].resize(new_size);
assert(real[idx].size() == new_size);
break;
}
if (non_empty && command-- == 0) {
compare_fn(real[idx], sim[idx]);
real[idx].clear();
sim[idx].clear();
assert(real[idx].empty());
break;
}
if (non_empty && command-- == 0) {
compare_fn(real[idx], sim[idx]);
real[idx] = VecDeque<T>();
sim[idx].clear();
assert(real[idx].size() == 0);
break;
}
if (non_empty && command-- == 0) {
compare_fn(real.back(), sim.back());
real.pop_back();
sim.pop_back();
break;
}
if (partially_full && command-- == 0) {
real.emplace_back(real[idx]);
sim.emplace_back(sim[idx]);
break;
}
if (partially_full && command-- == 0) {
VecDeque<T> copy(real[idx]);
real.emplace_back(std::move(copy));
sim.emplace_back(sim[idx]);
break;
}
if (multiple_exist && command-- == 0) {
swap(real[idx], real[(idx + 1) % num_buffers]);
swap(sim[idx], sim[(idx + 1) % num_buffers]);
break;
}
if (multiple_exist && command-- == 0) {
compare_fn(real[idx], sim[idx]);
real[idx] = real[(idx + 1) % num_buffers];
sim[idx] = sim[(idx + 1) % num_buffers];
break;
}
if (multiple_exist && command-- == 0) {
VecDeque<T> copy(real[(idx + 1) % num_buffers]);
compare_fn(real[idx], sim[idx]);
real[idx] = std::move(copy);
sim[idx] = sim[(idx + 1) % num_buffers];
break;
}
if (non_empty && command-- == 0) {
swap(real[idx], real[idx]);
break;
}
if (non_empty && command-- == 0) {
real[idx] = real[idx];
break;
}
if (non_empty && command-- == 0) {
real[idx] = static_cast<VecDeque<T>&&>(real[idx]);
break;
}
if (non_empty && command-- == 0) {
size_t res_size = provider.ConsumeIntegralInRange<size_t>(0, MAX_BUFFER_SIZE);
size_t old_cap = real[idx].capacity();
size_t old_size = real[idx].size();
real[idx].reserve(res_size);
assert(real[idx].size() == old_size);
assert(real[idx].capacity() == std::max(old_cap, res_size));
break;
}
if (non_empty && command-- == 0) {
size_t old_size = real[idx].size();
real[idx].shrink_to_fit();
assert(real[idx].size() == old_size);
assert(real[idx].capacity() == old_size);
break;
}
if (existing_buffer_non_full && command-- == 0) {
tmp = T(rng.rand64());
size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity();
real[idx].push_back(*tmp);
sim[idx].push_back(*tmp);
assert(real[idx].size() == old_size + 1);
if (old_cap > old_size) {
assert(real[idx].capacity() == old_cap);
} else {
assert(real[idx].capacity() > old_cap);
assert(real[idx].capacity() <= 2 * (old_cap + 1));
}
break;
}
if (existing_buffer_non_full && command-- == 0) {
tmp = T(rng.rand64());
size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity();
sim[idx].push_back(*tmp);
real[idx].push_back(std::move(*tmp));
assert(real[idx].size() == old_size + 1);
if (old_cap > old_size) {
assert(real[idx].capacity() == old_cap);
} else {
assert(real[idx].capacity() > old_cap);
assert(real[idx].capacity() <= 2 * (old_cap + 1));
}
break;
}
if (existing_buffer_non_full && command-- == 0) {
uint64_t seed{rng.rand64()};
size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity();
sim[idx].emplace_back(seed);
real[idx].emplace_back(seed);
assert(real[idx].size() == old_size + 1);
if (old_cap > old_size) {
assert(real[idx].capacity() == old_cap);
} else {
assert(real[idx].capacity() > old_cap);
assert(real[idx].capacity() <= 2 * (old_cap + 1));
}
break;
}
if (existing_buffer_non_full && command-- == 0) {
tmp = T(rng.rand64());
size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity();
real[idx].push_front(*tmp);
sim[idx].push_front(*tmp);
assert(real[idx].size() == old_size + 1);
if (old_cap > old_size) {
assert(real[idx].capacity() == old_cap);
} else {
assert(real[idx].capacity() > old_cap);
assert(real[idx].capacity() <= 2 * (old_cap + 1));
}
break;
}
if (existing_buffer_non_full && command-- == 0) {
tmp = T(rng.rand64());
size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity();
sim[idx].push_front(*tmp);
real[idx].push_front(std::move(*tmp));
assert(real[idx].size() == old_size + 1);
if (old_cap > old_size) {
assert(real[idx].capacity() == old_cap);
} else {
assert(real[idx].capacity() > old_cap);
assert(real[idx].capacity() <= 2 * (old_cap + 1));
}
break;
}
if (existing_buffer_non_full && command-- == 0) {
uint64_t seed{rng.rand64()};
size_t old_size = real[idx].size();
size_t old_cap = real[idx].capacity();
sim[idx].emplace_front(seed);
real[idx].emplace_front(seed);
assert(real[idx].size() == old_size + 1);
if (old_cap > old_size) {
assert(real[idx].capacity() == old_cap);
} else {
assert(real[idx].capacity() > old_cap);
assert(real[idx].capacity() <= 2 * (old_cap + 1));
}
break;
}
if (existing_buffer_non_empty && command-- == 0) {
tmp = T(rng.rand64());
size_t old_size = real[idx].size();
assert(sim[idx].front() == real[idx].front());
sim[idx].front() = *tmp;
real[idx].front() = std::move(*tmp);
assert(real[idx].size() == old_size);
break;
}
if (existing_buffer_non_empty && command-- == 0) {
tmp = T(rng.rand64());
size_t old_size = real[idx].size();
assert(sim[idx].back() == real[idx].back());
sim[idx].back() = *tmp;
real[idx].back() = *tmp;
assert(real[idx].size() == old_size);
break;
}
if (existing_buffer_non_empty && command-- == 0) {
tmp = T(rng.rand64());
size_t pos = provider.ConsumeIntegralInRange<size_t>(0, sim[idx].size() - 1);
size_t old_size = real[idx].size();
assert(sim[idx][pos] == real[idx][pos]);
sim[idx][pos] = *tmp;
real[idx][pos] = std::move(*tmp);
assert(real[idx].size() == old_size);
break;
}
if (existing_buffer_non_empty && command-- == 0) {
assert(sim[idx].front() == real[idx].front());
size_t old_size = real[idx].size();
sim[idx].pop_front();
real[idx].pop_front();
assert(real[idx].size() == old_size - 1);
break;
}
if (existing_buffer_non_empty && command-- == 0) {
assert(sim[idx].back() == real[idx].back());
size_t old_size = real[idx].size();
sim[idx].pop_back();
real[idx].pop_back();
assert(real[idx].size() == old_size - 1);
break;
}
}
}
for (unsigned i = 0; i < sim.size(); ++i) {
const VecDeque<T>& realbuf = real[i];
const std::deque<T>& simbuf = sim[i];
compare_fn(realbuf, simbuf);
for (unsigned j = 0; j < sim.size(); ++j) {
assert((realbuf == real[j]) == (simbuf == sim[j]));
assert(((realbuf <=> real[j]) >= 0) == (simbuf >= sim[j]));
assert(((realbuf <=> real[j]) <= 0) == (simbuf <= sim[j]));
}
sim[i].clear();
real[i].clear();
}
if constexpr (CheckNoneLeft) {
tmp = std::nullopt;
T::CheckNoneExist();
}
}
template<size_t Size>
class TrackedObj
{
static_assert(Size > 0);
using track_map_type = std::map<const TrackedObj<Size>*, std::optional<uint64_t>>;
private:
static inline track_map_type g_tracker;
typename track_map_type::iterator m_track_entry[Size];
void Check() const
{
auto it = g_tracker.find(this);
for (size_t i = 0; i < Size; ++i) {
assert(m_track_entry[i] == it);
}
}
void Register()
{
auto [it, inserted] = g_tracker.emplace(this, std::nullopt);
assert(inserted);
for (size_t i = 0; i < Size; ++i) {
m_track_entry[i] = it;
}
}
void Deregister()
{
Check();
assert(m_track_entry[0] != g_tracker.end());
g_tracker.erase(m_track_entry[0]);
for (size_t i = 0; i < Size; ++i) {
m_track_entry[i] = g_tracker.end();
}
}
std::optional<uint64_t>& Deref()
{
Check();
assert(m_track_entry[0] != g_tracker.end());
return m_track_entry[0]->second;
}
const std::optional<uint64_t>& Deref() const
{
Check();
assert(m_track_entry[0] != g_tracker.end());
return m_track_entry[0]->second;
}
public:
~TrackedObj() { Deregister(); }
TrackedObj() { Register(); }
TrackedObj(uint64_t value)
{
Register();
Deref() = value;
}
TrackedObj(const TrackedObj& other)
{
Register();
Deref() = other.Deref();
}
TrackedObj(TrackedObj&& other)
{
Register();
Deref() = other.Deref();
other.Deref() = std::nullopt;
}
TrackedObj& operator=(const TrackedObj& other)
{
if (this == &other) return *this;
Deref() = other.Deref();
return *this;
}
TrackedObj& operator=(TrackedObj&& other)
{
if (this == &other) return *this;
Deref() = other.Deref();
other.Deref() = std::nullopt;
return *this;
}
friend bool operator==(const TrackedObj& a, const TrackedObj& b)
{
return a.Deref() == b.Deref();
}
friend std::strong_ordering operator<=>(const TrackedObj& a, const TrackedObj& b)
{
if (!a.Deref().has_value() || !b.Deref().has_value()) {
return a.Deref().has_value() <=> b.Deref().has_value();
}
return *a.Deref() <=> *b.Deref();
}
static void CheckNoneExist()
{
assert(g_tracker.empty());
}
};
}
FUZZ_TARGET(vecdeque)
{
static_assert(std::is_trivially_copyable_v<uint32_t>);
static_assert(std::is_trivially_destructible_v<uint64_t>);
TestType<uint8_t, false>(buffer, 1);
TestType<uint16_t, false>(buffer, 2);
TestType<uint32_t, false>(buffer, 3);
TestType<uint64_t, false>(buffer, 4);
static_assert(!std::is_trivially_copyable_v<TrackedObj<3>>);
static_assert(!std::is_trivially_destructible_v<TrackedObj<17>>);
TestType<TrackedObj<1>, true>(buffer, 5);
TestType<TrackedObj<3>, true>(buffer, 6);
TestType<TrackedObj<17>, true>(buffer, 7);
}