#include "Facade.h" #include "Internal.h" #include "KeyCompare.h" #include #include #include #define DEBUG 0 struct Reference { explicit Reference(int64_t version) : version(version), oldestVersion(version) { versioned[version] = std::map(); } struct View { std::vector> rangeRead(const String &begin, const String &end, int limit, bool reverse) const { std::vector> result; if (begin >= end) { return result; } if (reverse) { auto iter = map->lower_bound(end); const auto beginIter = map->begin(); if (iter == beginIter) { return result; } --iter; for (; iter->first >= begin && limit > 0; --iter) { result.push_back(*iter); --limit; if (iter == beginIter) { return result; } } } else { for (auto iter = map->lower_bound(begin), iterEnd = map->lower_bound(end); iter != iterEnd && limit > 0; ++iter) { result.push_back(*iter); --limit; } } return result; } /// @private explicit View(const std::map *map) : map(map) {} private: const std::map *map; }; void addMutations(const weaselab::VersionedMap::Mutation *mutations, int numMutations, int64_t version) { assert(this->version < version); this->version = version; auto back = (--versioned.end())->second; auto &latest = versioned[version]; latest = std::move(back); for (int i = 0; i < numMutations; ++i) { switch (mutations[i].type) { case weaselab::VersionedMap::Set: latest[String(mutations[i].param1, mutations[i].param1Len)] = String(mutations[i].param2, mutations[i].param2Len); break; case weaselab::VersionedMap::Clear: if (mutations[i].param2Len > 0) { latest.erase(latest.lower_bound( String(mutations[i].param1, mutations[i].param1Len)), latest.lower_bound(String(mutations[i].param2, mutations[i].param2Len))); } else { latest.erase(String(mutations[i].param1, mutations[i].param1Len)); } break; } } } void setOldestVersion(int64_t version) { oldestVersion = version; while (versioned.size() > 1) { auto first = versioned.begin(); auto second = first; ++second; if (second->first <= version) { versioned.erase(first); } else { break; } } } View viewAt(int64_t version) const { auto iter = versioned.lower_bound(version); if (iter->first > version) { --iter; } return View{&iter->second}; } int64_t getVersion() const { return version; } int64_t getOldestVersion() const { return oldestVersion; } private: int64_t version; int64_t oldestVersion; std::map> versioned; }; constexpr int kKeySize = 4; weaselab::VersionedMap::Key randomKey(Arena &arena) { auto *result = new (arena) uint8_t[kKeySize]; gRandom.randomHex(result, kKeySize); return {result, kKeySize}; } weaselab::VersionedMap::Key arbitraryKey(Arena &arena) { auto *result = new (arena) uint8_t[kKeySize]; gArbitrary.randomHex(result, kKeySize); return {result, kKeySize}; } struct KeyComp { bool operator()(const weaselab::VersionedMap::Key &lhs, const weaselab::VersionedMap::Key &rhs) const { return lhs < rhs; } }; extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { initFuzz(data, size); Reference reference{0}; Facade facade{0}; Arena arena; while (gArbitrary.hasEntropy()) { switch (gArbitrary.bounded(3)) { case 0: { // Add mutations #if DEBUG printf("Add mutations\n"); #endif const int numKeys = gArbitrary.bounded(10); std::set keySet; while (int(keySet.size()) < numKeys) { keySet.insert(randomKey(arena)); } std::vector keys; keys.insert(keys.end(), keySet.begin(), keySet.end()); std::vector mutations; for (int i = 0; i < int(keys.size());) { switch (gArbitrary.bounded(3)) { case 0: { // Point write auto val = randomKey(arena); mutations.push_back({keys[i].p, keys[i].len, val.p, val.len, weaselab::VersionedMap::Set}); ++i; } break; case 1: { // Point clear mutations.push_back({keys[i].p, keys[i].len, nullptr, 0, weaselab::VersionedMap::Clear}); ++i; } break; case 2: { // Range clear if (i + 1 < int(keys.size())) { mutations.push_back({keys[i].p, keys[i].len, keys[i + 1].p, keys[i + 1].len, weaselab::VersionedMap::Clear}); i += 2; } } break; } } assert(reference.getVersion() == facade.getVersion()); const int64_t newVersion = reference.getVersion() + 1; #if DEBUG for (const auto &m : mutations) { printf("%s %.*s %.*s\n", m.type == weaselab::VersionedMap::Set ? "set" : "clear", m.param1Len, m.param1, m.param2Len, m.param2); } printf("Version: %" PRId64 "\n", newVersion); #endif facade.addMutations(mutations.data(), mutations.size(), newVersion); reference.addMutations(mutations.data(), mutations.size(), newVersion); } break; case 1: { // Set oldest version const int64_t newOldestVersion = reference.getOldestVersion() + gArbitrary.bounded(reference.getVersion() - reference.getOldestVersion() + 1); #if DEBUG printf("Set oldest version %" PRId64 "\n", newOldestVersion); #endif facade.setOldestVersion(newOldestVersion); reference.setOldestVersion(newOldestVersion); } break; case 2: { // Check range read #if DEBUG printf("Check range read\n"); #endif assert(reference.getOldestVersion() == facade.getOldestVersion()); const int64_t version = reference.getOldestVersion() + gArbitrary.bounded(reference.getVersion() - reference.getOldestVersion() + 1); auto begin = arbitraryKey(arena); auto end = arbitraryKey(arena); const int limit = gArbitrary.bounded(100000); const bool reverse = gArbitrary.bounded(2); auto result = facade.viewAt(version).rangeRead( String(begin.p, begin.len), String(end.p, end.len), limit, reverse); auto expected = reference.viewAt(version).rangeRead( String(begin.p, begin.len), String(end.p, end.len), limit, reverse); #if DEBUG printf("[%.*s, %.*s) version=%" PRId64 " limit=%d %s result:\n", begin.len, begin.p, end.len, end.p, version, limit, reverse ? "reverse" : "forward"); for (const auto &r : result) { printf("%.*s %.*s\n", (int)r.first.length(), r.first.data(), (int)r.second.length(), r.second.data()); } if (result != expected) { printf("[%.*s, %.*s) version=%" PRId64 " limit=%d %s expected:\n", begin.len, begin.p, end.len, end.p, version, limit, reverse ? "reverse" : "forward"); for (const auto &r : expected) { printf("%.*s %.*s\n", (int)r.first.length(), r.first.data(), (int)r.second.length(), r.second.data()); } } #endif assert(result == expected); } break; } } return 0; }