From 451de390cf26325db0f04d3dc3c6ca49fc948cd8 Mon Sep 17 00:00:00 2001 From: Martin Leitner-Ankerl Date: Mon, 11 Jul 2022 11:39:39 +0200 Subject: [PATCH] Added rudimentary fuzzer --- include/ankerl/unordered_dense.h | 4 +- scripts/lint/lint-clang-format.py | 2 +- test/app/Counter.cpp | 32 ++- test/app/Counter.h | 2 + test/fuzz/Fuzz.h | 73 ++++++ test/fuzz/FuzzedDataProvider.h | 397 ++++++++++++++++++++++++++++++ test/fuzz/main.cpp | 113 +++++++++ test/meson.build | 23 ++ 8 files changed, 643 insertions(+), 3 deletions(-) create mode 100644 test/fuzz/Fuzz.h create mode 100644 test/fuzz/FuzzedDataProvider.h create mode 100644 test/fuzz/main.cpp diff --git a/include/ankerl/unordered_dense.h b/include/ankerl/unordered_dense.h index 7bc3f1cf..51d82552 100644 --- a/include/ankerl/unordered_dense.h +++ b/include/ankerl/unordered_dense.h @@ -474,7 +474,9 @@ class table { } void clear_buckets() { - std::memset(m_buckets_start, 0, sizeof(Bucket) * bucket_count()); + if (m_buckets_start != nullptr) { + std::memset(m_buckets_start, 0, sizeof(Bucket) * bucket_count()); + } } void clear_and_fill_buckets_from_values() { diff --git a/scripts/lint/lint-clang-format.py b/scripts/lint/lint-clang-format.py index 0ab5b344..741e7a36 100755 --- a/scripts/lint/lint-clang-format.py +++ b/scripts/lint/lint-clang-format.py @@ -16,7 +16,7 @@ f"{root_path}/test/**/*.h", f"{root_path}/test/**/*.cpp", ] -exclusions = ["nanobench\.h"] +exclusions = ["nanobench\.h", "FuzzedDataProvider\.h"] files = [] for g in globs: diff --git a/test/app/Counter.cpp b/test/app/Counter.cpp index 3c6106e3..b6ef01df 100644 --- a/test/app/Counter.cpp +++ b/test/app/Counter.cpp @@ -7,34 +7,43 @@ #include #include +#define COUNTER_ENABLE_UNORDERED_SET 0 + +#if COUNTER_ENABLE_UNORDERED_SET auto singletonConstructedObjects() -> std::unordered_set& { static std::unordered_set data{}; return data; } +#endif Counter::Obj::Obj() : mData(0) , mCounts(nullptr) { +#if COUNTER_ENABLE_UNORDERED_SET if (!singletonConstructedObjects().emplace(this).second) { test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); } +#endif ++staticDefaultCtor; } Counter::Obj::Obj(const size_t& data, Counter& counts) : mData(data) , mCounts(&counts) { +#if COUNTER_ENABLE_UNORDERED_SET if (!singletonConstructedObjects().emplace(this).second) { test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); } +#endif ++mCounts->ctor; } Counter::Obj::Obj(const Counter::Obj& o) : mData(o.mData) , mCounts(o.mCounts) { +#if COUNTER_ENABLE_UNORDERED_SET if (1 != singletonConstructedObjects().count(&o)) { test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); @@ -43,6 +52,7 @@ Counter::Obj::Obj(const Counter::Obj& o) test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); } +#endif if (nullptr != mCounts) { ++mCounts->copyCtor; } @@ -51,6 +61,7 @@ Counter::Obj::Obj(const Counter::Obj& o) Counter::Obj::Obj(Counter::Obj&& o) noexcept : mData(o.mData) , mCounts(o.mCounts) { +#if COUNTER_ENABLE_UNORDERED_SET if (1 != singletonConstructedObjects().count(&o)) { test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); @@ -59,16 +70,19 @@ Counter::Obj::Obj(Counter::Obj&& o) noexcept test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); } +#endif if (nullptr != mCounts) { ++mCounts->moveCtor; } } Counter::Obj::~Obj() { +#if COUNTER_ENABLE_UNORDERED_SET if (1 != singletonConstructedObjects().erase(this)) { test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); } +#endif if (nullptr != mCounts) { ++mCounts->dtor; } else { @@ -77,10 +91,12 @@ Counter::Obj::~Obj() { } auto Counter::Obj::operator==(const Counter::Obj& o) const -> bool { +#if COUNTER_ENABLE_UNORDERED_SET if (1 != singletonConstructedObjects().count(this) || 1 != singletonConstructedObjects().count(&o)) { test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); } +#endif if (nullptr != mCounts) { ++mCounts->equals; } @@ -88,10 +104,12 @@ auto Counter::Obj::operator==(const Counter::Obj& o) const -> bool { } auto Counter::Obj::operator<(const Obj& o) const -> bool { +#if COUNTER_ENABLE_UNORDERED_SET if (1 != singletonConstructedObjects().count(this) || 1 != singletonConstructedObjects().count(&o)) { test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); } +#endif if (nullptr != mCounts) { ++mCounts->less; } @@ -100,10 +118,12 @@ auto Counter::Obj::operator<(const Obj& o) const -> bool { // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) auto Counter::Obj::operator=(const Counter::Obj& o) -> Counter::Obj& { +#if COUNTER_ENABLE_UNORDERED_SET if (1 != singletonConstructedObjects().count(this) || 1 != singletonConstructedObjects().count(&o)) { test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); } +#endif mCounts = o.mCounts; if (nullptr != mCounts) { ++mCounts->assign; @@ -113,10 +133,12 @@ auto Counter::Obj::operator=(const Counter::Obj& o) -> Counter::Obj& { } auto Counter::Obj::operator=(Counter::Obj&& o) noexcept -> Counter::Obj& { +#if COUNTER_ENABLE_UNORDERED_SET if (1 != singletonConstructedObjects().count(this) || 1 != singletonConstructedObjects().count(&o)) { test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); } +#endif if (nullptr != o.mCounts) { mCounts = o.mCounts; } @@ -142,10 +164,12 @@ auto Counter::Obj::get() -> size_t& { } void Counter::Obj::swap(Obj& other) { +#if COUNTER_ENABLE_UNORDERED_SET if (1 != singletonConstructedObjects().count(this) || 1 != singletonConstructedObjects().count(&other)) { test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__); std::abort(); } +#endif using std::swap; swap(mData, other.mData); swap(mCounts, other.mCounts); @@ -166,7 +190,8 @@ Counter::Counter() { Counter::staticDtor = 0; } -Counter::~Counter() { +void Counter::check_all_done() const { +#if COUNTER_ENABLE_UNORDERED_SET // check that all are destructed if (!singletonConstructedObjects().empty()) { test::print("ERROR at ~Counter(): got {} objects still alive!", singletonConstructedObjects().size()); @@ -176,6 +201,11 @@ Counter::~Counter() { test::print("ERROR at ~Counter(): number of counts does not match!"); std::abort(); } +#endif +} + +Counter::~Counter() { + check_all_done(); } auto Counter::total() const -> size_t { diff --git a/test/app/Counter.h b/test/app/Counter.h index 7971066f..34307686 100644 --- a/test/app/Counter.h +++ b/test/app/Counter.h @@ -43,6 +43,8 @@ struct Counter { Counter(); ~Counter(); + void check_all_done() const; + size_t ctor{}; size_t defaultCtor{}; size_t copyCtor{}; diff --git a/test/fuzz/Fuzz.h b/test/fuzz/Fuzz.h new file mode 100644 index 00000000..3dfec0f9 --- /dev/null +++ b/test/fuzz/Fuzz.h @@ -0,0 +1,73 @@ +#pragma once + +#include "FuzzedDataProvider.h" + +class Fuzz { + FuzzedDataProvider m_fdp; + +public: + inline explicit Fuzz(uint8_t const* data, size_t size) + : m_fdp(data, size) {} + + // random number in inclusive range [min, max] + template + auto range(T min, T max) -> T { + return m_fdp.ConsumeIntegralInRange(min, max); + } + + template + auto integral() -> T { + return m_fdp.ConsumeIntegral(); + } + + template + auto pick(Args&&... args) -> std::common_type_t& { + static constexpr auto num_ops = sizeof...(args); + + auto idx = size_t{}; + auto const chosen_idx = m_fdp.ConsumeIntegralInRange(0, num_ops - 1); + std::common_type_t* result = nullptr; + ((idx++ == chosen_idx ? (result = &args, true) : false) || ...); + return *result; + } + + template + void loop_call_any(Ops&&... op) { + static constexpr auto num_ops = sizeof...(op); + + do { + if constexpr (num_ops == 1) { + (op(), ...); + } else { + auto chosen_op_idx = range(0, num_ops - 1); + auto op_idx = size_t{}; + ((op_idx++ == chosen_op_idx ? op() : void()), ...); + } + } while (0 != m_fdp.remaining_bytes()); + } + + template + void limited_loop_call_any(size_t min, size_t max, Ops&&... op) { + static constexpr auto num_ops = sizeof...(op); + + size_t const num_evaluations = m_fdp.ConsumeIntegralInRange(min, max); + for (size_t i = 0; i < num_evaluations; ++i) { + if constexpr (num_ops == 1) { + (op(), ...); + } else { + auto chosen_op_idx = range(0, num_ops - 1); + auto op_idx = size_t{}; + ((op_idx++ == chosen_op_idx ? op() : void()), ...); + } + if (m_fdp.remaining_bytes() == 0) { + return; + } + } + } + + static inline void require(bool b) { + if (!b) { + __builtin_trap(); + } + } +}; diff --git a/test/fuzz/FuzzedDataProvider.h b/test/fuzz/FuzzedDataProvider.h new file mode 100644 index 00000000..71cb427e --- /dev/null +++ b/test/fuzz/FuzzedDataProvider.h @@ -0,0 +1,397 @@ +//===- FuzzedDataProvider.h - Utility header for fuzz targets ---*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// A single header library providing an utility class to break up an array of +// bytes. Whenever run on the same input, provides the same output, as long as +// its methods are called in the same order, with the same arguments. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_ +#define LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// In addition to the comments below, the API is also briefly documented at +// https://github.com/google/fuzzing/blob/master/docs/split-inputs.md#fuzzed-data-provider +class FuzzedDataProvider { + public: + // |data| is an array of length |size| that the FuzzedDataProvider wraps to + // provide more granular access. |data| must outlive the FuzzedDataProvider. + FuzzedDataProvider(const uint8_t *data, size_t size) + : data_ptr_(data), remaining_bytes_(size) {} + ~FuzzedDataProvider() = default; + + // See the implementation below (after the class definition) for more verbose + // comments for each of the methods. + + // Methods returning std::vector of bytes. These are the most popular choice + // when splitting fuzzing input into pieces, as every piece is put into a + // separate buffer (i.e. ASan would catch any under-/overflow) and the memory + // will be released automatically. + template std::vector ConsumeBytes(size_t num_bytes); + template + std::vector ConsumeBytesWithTerminator(size_t num_bytes, T terminator = 0); + template std::vector ConsumeRemainingBytes(); + + // Methods returning strings. Use only when you need a std::string or a null + // terminated C-string. Otherwise, prefer the methods returning std::vector. + std::string ConsumeBytesAsString(size_t num_bytes); + std::string ConsumeRandomLengthString(size_t max_length); + std::string ConsumeRandomLengthString(); + std::string ConsumeRemainingBytesAsString(); + + // Methods returning integer values. + template T ConsumeIntegral(); + template T ConsumeIntegralInRange(T min, T max); + + // Methods returning floating point values. + template T ConsumeFloatingPoint(); + template T ConsumeFloatingPointInRange(T min, T max); + + // 0 <= return value <= 1. + template T ConsumeProbability(); + + bool ConsumeBool(); + + // Returns a value chosen from the given enum. + template T ConsumeEnum(); + + // Returns a value from the given array. + template T PickValueInArray(const T (&array)[size]); + template + T PickValueInArray(const std::array &array); + template T PickValueInArray(std::initializer_list list); + + // Writes data to the given destination and returns number of bytes written. + size_t ConsumeData(void *destination, size_t num_bytes); + + // Reports the remaining bytes available for fuzzed input. + size_t remaining_bytes() { return remaining_bytes_; } + + private: + FuzzedDataProvider(const FuzzedDataProvider &) = delete; + FuzzedDataProvider &operator=(const FuzzedDataProvider &) = delete; + + void CopyAndAdvance(void *destination, size_t num_bytes); + + void Advance(size_t num_bytes); + + template + std::vector ConsumeBytes(size_t size, size_t num_bytes); + + template TS ConvertUnsignedToSigned(TU value); + + const uint8_t *data_ptr_; + size_t remaining_bytes_; +}; + +// Returns a std::vector containing |num_bytes| of input data. If fewer than +// |num_bytes| of data remain, returns a shorter std::vector containing all +// of the data that's left. Can be used with any byte sized type, such as +// char, unsigned char, uint8_t, etc. +template +std::vector FuzzedDataProvider::ConsumeBytes(size_t num_bytes) { + num_bytes = std::min(num_bytes, remaining_bytes_); + return ConsumeBytes(num_bytes, num_bytes); +} + +// Similar to |ConsumeBytes|, but also appends the terminator value at the end +// of the resulting vector. Useful, when a mutable null-terminated C-string is +// needed, for example. But that is a rare case. Better avoid it, if possible, +// and prefer using |ConsumeBytes| or |ConsumeBytesAsString| methods. +template +std::vector FuzzedDataProvider::ConsumeBytesWithTerminator(size_t num_bytes, + T terminator) { + num_bytes = std::min(num_bytes, remaining_bytes_); + std::vector result = ConsumeBytes(num_bytes + 1, num_bytes); + result.back() = terminator; + return result; +} + +// Returns a std::vector containing all remaining bytes of the input data. +template +std::vector FuzzedDataProvider::ConsumeRemainingBytes() { + return ConsumeBytes(remaining_bytes_); +} + +// Returns a std::string containing |num_bytes| of input data. Using this and +// |.c_str()| on the resulting string is the best way to get an immutable +// null-terminated C string. If fewer than |num_bytes| of data remain, returns +// a shorter std::string containing all of the data that's left. +inline std::string FuzzedDataProvider::ConsumeBytesAsString(size_t num_bytes) { + static_assert(sizeof(std::string::value_type) == sizeof(uint8_t), + "ConsumeBytesAsString cannot convert the data to a string."); + + num_bytes = std::min(num_bytes, remaining_bytes_); + std::string result( + reinterpret_cast(data_ptr_), num_bytes); + Advance(num_bytes); + return result; +} + +// Returns a std::string of length from 0 to |max_length|. When it runs out of +// input data, returns what remains of the input. Designed to be more stable +// with respect to a fuzzer inserting characters than just picking a random +// length and then consuming that many bytes with |ConsumeBytes|. +inline std::string +FuzzedDataProvider::ConsumeRandomLengthString(size_t max_length) { + // Reads bytes from the start of |data_ptr_|. Maps "\\" to "\", and maps "\" + // followed by anything else to the end of the string. As a result of this + // logic, a fuzzer can insert characters into the string, and the string + // will be lengthened to include those new characters, resulting in a more + // stable fuzzer than picking the length of a string independently from + // picking its contents. + std::string result; + + // Reserve the anticipated capaticity to prevent several reallocations. + result.reserve(std::min(max_length, remaining_bytes_)); + for (size_t i = 0; i < max_length && remaining_bytes_ != 0; ++i) { + char next = ConvertUnsignedToSigned(data_ptr_[0]); + Advance(1); + if (next == '\\' && remaining_bytes_ != 0) { + next = ConvertUnsignedToSigned(data_ptr_[0]); + Advance(1); + if (next != '\\') + break; + } + result += next; + } + + result.shrink_to_fit(); + return result; +} + +// Returns a std::string of length from 0 to |remaining_bytes_|. +inline std::string FuzzedDataProvider::ConsumeRandomLengthString() { + return ConsumeRandomLengthString(remaining_bytes_); +} + +// Returns a std::string containing all remaining bytes of the input data. +// Prefer using |ConsumeRemainingBytes| unless you actually need a std::string +// object. +inline std::string FuzzedDataProvider::ConsumeRemainingBytesAsString() { + return ConsumeBytesAsString(remaining_bytes_); +} + +// Returns a number in the range [Type's min, Type's max]. The value might +// not be uniformly distributed in the given range. If there's no input data +// left, always returns |min|. +template T FuzzedDataProvider::ConsumeIntegral() { + return ConsumeIntegralInRange(std::numeric_limits::min(), + std::numeric_limits::max()); +} + +// Returns a number in the range [min, max] by consuming bytes from the +// input data. The value might not be uniformly distributed in the given +// range. If there's no input data left, always returns |min|. |min| must +// be less than or equal to |max|. +template +T FuzzedDataProvider::ConsumeIntegralInRange(T min, T max) { + static_assert(std::is_integral::value, "An integral type is required."); + static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type."); + + if (min > max) + abort(); + + // Use the biggest type possible to hold the range and the result. + uint64_t range = static_cast(max) - min; + uint64_t result = 0; + size_t offset = 0; + + while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 && + remaining_bytes_ != 0) { + // Pull bytes off the end of the seed data. Experimentally, this seems to + // allow the fuzzer to more easily explore the input space. This makes + // sense, since it works by modifying inputs that caused new code to run, + // and this data is often used to encode length of data read by + // |ConsumeBytes|. Separating out read lengths makes it easier modify the + // contents of the data that is actually read. + --remaining_bytes_; + result = (result << CHAR_BIT) | data_ptr_[remaining_bytes_]; + offset += CHAR_BIT; + } + + // Avoid division by 0, in case |range + 1| results in overflow. + if (range != std::numeric_limits::max()) + result = result % (range + 1); + + return static_cast(min + result); +} + +// Returns a floating point value in the range [Type's lowest, Type's max] by +// consuming bytes from the input data. If there's no input data left, always +// returns approximately 0. +template T FuzzedDataProvider::ConsumeFloatingPoint() { + return ConsumeFloatingPointInRange(std::numeric_limits::lowest(), + std::numeric_limits::max()); +} + +// Returns a floating point value in the given range by consuming bytes from +// the input data. If there's no input data left, returns |min|. Note that +// |min| must be less than or equal to |max|. +template +T FuzzedDataProvider::ConsumeFloatingPointInRange(T min, T max) { + if (min > max) + abort(); + + T range = .0; + T result = min; + constexpr T zero(.0); + if (max > zero && min < zero && max > min + std::numeric_limits::max()) { + // The diff |max - min| would overflow the given floating point type. Use + // the half of the diff as the range and consume a bool to decide whether + // the result is in the first of the second part of the diff. + range = (max / 2.0) - (min / 2.0); + if (ConsumeBool()) { + result += range; + } + } else { + range = max - min; + } + + return result + range * ConsumeProbability(); +} + +// Returns a floating point number in the range [0.0, 1.0]. If there's no +// input data left, always returns 0. +template T FuzzedDataProvider::ConsumeProbability() { + static_assert(std::is_floating_point::value, + "A floating point type is required."); + + // Use different integral types for different floating point types in order + // to provide better density of the resulting values. + using IntegralType = + typename std::conditional<(sizeof(T) <= sizeof(uint32_t)), uint32_t, + uint64_t>::type; + + T result = static_cast(ConsumeIntegral()); + result /= static_cast(std::numeric_limits::max()); + return result; +} + +// Reads one byte and returns a bool, or false when no data remains. +inline bool FuzzedDataProvider::ConsumeBool() { + return 1 & ConsumeIntegral(); +} + +// Returns an enum value. The enum must start at 0 and be contiguous. It must +// also contain |kMaxValue| aliased to its largest (inclusive) value. Such as: +// enum class Foo { SomeValue, OtherValue, kMaxValue = OtherValue }; +template T FuzzedDataProvider::ConsumeEnum() { + static_assert(std::is_enum::value, "|T| must be an enum type."); + return static_cast( + ConsumeIntegralInRange(0, static_cast(T::kMaxValue))); +} + +// Returns a copy of the value selected from the given fixed-size |array|. +template +T FuzzedDataProvider::PickValueInArray(const T (&array)[size]) { + static_assert(size > 0, "The array must be non empty."); + return array[ConsumeIntegralInRange(0, size - 1)]; +} + +template +T FuzzedDataProvider::PickValueInArray(const std::array &array) { + static_assert(size > 0, "The array must be non empty."); + return array[ConsumeIntegralInRange(0, size - 1)]; +} + +template +T FuzzedDataProvider::PickValueInArray(std::initializer_list list) { + // TODO(Dor1s): switch to static_assert once C++14 is allowed. + if (!list.size()) + abort(); + + return *(list.begin() + ConsumeIntegralInRange(0, list.size() - 1)); +} + +// Writes |num_bytes| of input data to the given destination pointer. If there +// is not enough data left, writes all remaining bytes. Return value is the +// number of bytes written. +// In general, it's better to avoid using this function, but it may be useful +// in cases when it's necessary to fill a certain buffer or object with +// fuzzing data. +inline size_t FuzzedDataProvider::ConsumeData(void *destination, + size_t num_bytes) { + num_bytes = std::min(num_bytes, remaining_bytes_); + CopyAndAdvance(destination, num_bytes); + return num_bytes; +} + +// Private methods. +inline void FuzzedDataProvider::CopyAndAdvance(void *destination, + size_t num_bytes) { + std::memcpy(destination, data_ptr_, num_bytes); + Advance(num_bytes); +} + +inline void FuzzedDataProvider::Advance(size_t num_bytes) { + if (num_bytes > remaining_bytes_) + abort(); + + data_ptr_ += num_bytes; + remaining_bytes_ -= num_bytes; +} + +template +std::vector FuzzedDataProvider::ConsumeBytes(size_t size, size_t num_bytes) { + static_assert(sizeof(T) == sizeof(uint8_t), "Incompatible data type."); + + // The point of using the size-based constructor below is to increase the + // odds of having a vector object with capacity being equal to the length. + // That part is always implementation specific, but at least both libc++ and + // libstdc++ allocate the requested number of bytes in that constructor, + // which seems to be a natural choice for other implementations as well. + // To increase the odds even more, we also call |shrink_to_fit| below. + std::vector result(size); + if (size == 0) { + if (num_bytes != 0) + abort(); + return result; + } + + CopyAndAdvance(result.data(), num_bytes); + + // Even though |shrink_to_fit| is also implementation specific, we expect it + // to provide an additional assurance in case vector's constructor allocated + // a buffer which is larger than the actual amount of data we put inside it. + result.shrink_to_fit(); + return result; +} + +template +TS FuzzedDataProvider::ConvertUnsignedToSigned(TU value) { + static_assert(sizeof(TS) == sizeof(TU), "Incompatible data types."); + static_assert(!std::numeric_limits::is_signed, + "Source type must be unsigned."); + + // TODO(Dor1s): change to `if constexpr` once C++17 becomes mainstream. + if (std::numeric_limits::is_modulo) + return static_cast(value); + + // Avoid using implementation-defined unsigned to signed conversions. + // To learn more, see https://stackoverflow.com/questions/13150449. + if (value <= std::numeric_limits::max()) { + return static_cast(value); + } else { + constexpr auto TS_min = std::numeric_limits::min(); + return TS_min + static_cast(value - TS_min); + } +} + +#endif // LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_ diff --git a/test/fuzz/main.cpp b/test/fuzz/main.cpp new file mode 100644 index 00000000..5e1e08dd --- /dev/null +++ b/test/fuzz/main.cpp @@ -0,0 +1,113 @@ +#include +#include +#include + +#include "Fuzz.h" + +#include +#include +#include +#include + +extern "C" auto LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) -> int { + auto fuzz = Fuzz(data, size); + Counter counts; + + using Map = ankerl::unordered_dense::map; + // using Map = robin_hood::unordered_flat_map; + auto map = Map(); + fuzz.loop_call_any( + [&] { + auto key = fuzz.integral(); + map.try_emplace(Counter::Obj(key, counts), Counter::Obj(key, counts)); + }, + [&] { + auto key = fuzz.integral(); + map.emplace(std::piecewise_construct, std::forward_as_tuple(key, counts), std::forward_as_tuple(key + 77, counts)); + }, + [&] { + auto key = fuzz.integral(); + map[Counter::Obj(key, counts)] = Counter::Obj(key + 123, counts); + }, + [&] { + auto key = fuzz.integral(); + map.insert(std::pair(Counter::Obj(key, counts), Counter::Obj(key, counts))); + }, + [&] { + auto key = fuzz.integral(); + map.insert_or_assign(Counter::Obj(key, counts), Counter::Obj(key + 1, counts)); + }, + [&] { + auto key = fuzz.integral(); + map.erase(Counter::Obj(key, counts)); + }, + [&] { + map = Map{}; + }, + [&] { + auto m = Map{}; + m.swap(map); + }, + [&] { + map.clear(); + }, + [&] { + auto s = fuzz.range(0, 1024); + map.rehash(s); + }, + [&] { + auto s = fuzz.range(0, 1024); + map.reserve(s); + }, + [&] { + auto key = fuzz.integral(); + auto it = map.find(Counter::Obj(key, counts)); + auto d = std::distance(map.begin(), it); + Fuzz::require(0 <= d && d <= static_cast(map.size())); + }, + [&] { + if (!map.empty()) { + auto idx = fuzz.range(0, static_cast(map.size() - 1)); + auto it = map.cbegin() + idx; + auto const& key = it->first; + auto found_it = map.find(key); + Fuzz::require(it == found_it); + } + }, + [&] { + if (!map.empty()) { + auto it = map.begin() + fuzz.range(0, static_cast(map.size() - 1)); + map.erase(it); + } + }, + [&] { + auto tmp = Map(); + std::swap(tmp, map); + }, + [&] { + map = std::initializer_list>{ + {{1, counts}, {2, counts}}, + {{3, counts}, {4, counts}}, + {{5, counts}, {6, counts}}, + }; + Fuzz::require(map.size() == 3); + }, + [&] { + auto first_idx = 0; + auto last_idx = 0; + if (!map.empty()) { + first_idx = fuzz.range(0, static_cast(map.size() - 1)); + last_idx = fuzz.range(0, static_cast(map.size() - 1)); + if (first_idx > last_idx) { + std::swap(first_idx, last_idx); + } + } + map.erase(map.cbegin() + first_idx, map.cbegin() + last_idx); + }, + [&] { + map.~Map(); + counts.check_all_done(); + new (&map) Map(); + }); + return 0; +} diff --git a/test/meson.build b/test/meson.build index 58232d85..82087fd6 100644 --- a/test/meson.build +++ b/test/meson.build @@ -95,3 +95,26 @@ test( 'unordered_dense_map test', test_exe, verbose: true) + + +fuzz_sources = [ + 'app/Counter.cpp', + 'fuzz/main.cpp', +] + + +if compiler.get_id() == 'clang' + fuzz_exe = executable( + 'fuzz', + fuzz_sources, + include_directories: incdir, + #cpp_args : ['-fsanitize-undefined-trap-on-error', '-fsanitize=undefined,address,fuzzer', '-g'], + #link_args: ['-fsanitize=undefined,address,fuzzer'], + cpp_args : ['-fsanitize=fuzzer', '-g'], + link_args: ['-fsanitize=fuzzer'], + dependencies: [ + dependency('threads'), + dependency('fmt', method: fmt_method), + ], + ) +endif \ No newline at end of file