blob: 90a48b9930193bc64027c738dfe1ce33b6aeb334 [file]
// Copyright 2026 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef SRC_TINT_CMD_FUZZ_COMMON_FUZZER_DECODER_H_
#define SRC_TINT_CMD_FUZZ_COMMON_FUZZER_DECODER_H_
#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstddef>
#include <cstring>
#include <optional>
#include <span>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "src/tint/cmd/fuzz/common/fuzzer_limits.h"
#include "src/tint/utils/memory/copy.h"
#include "src/tint/utils/reflection/reflection.h"
#include "src/utils/compiler.h"
namespace tint::fuzz {
/// SafeFuzzerDecoder parses an entropy byte buffer in sequential order, generating structured data
/// from it, and is guaranteed to never fail. If it runs out of input bytes, it gracefully returns
/// default/zero values.
///
/// @note Inspiration and Differences from FuzzedDataProvider:
/// This class is directly inspired and influenced by LLVM's `FuzzedDataProvider` (FDP), which is
/// a standard tool for converting raw, unstructured fuzzer entropy into structured types.
/// Salient features of`SafeFuzzerDecoder` design and how it deviates from FDP:
///
/// 1. **Sequential Consumption:**
/// `FuzzedDataProvider` generates results from raw data in two distinct ways: by consuming
/// dynamic structures (strings, vectors) from the front, and fixed-size integers/booleans from
/// the back. This avoids many situations where a small changes in the format or input causes
/// large impact or randomization on the result, but requires careful structuring of calling code
/// to perform all of one type of generation and then all of the other in sequence.
/// To avoid the complexity re-ordering member reads in the decoder or enforcing a strict struct
/// layout on transform options, a strict sequential decoder is implemented.
/// This means that if a field is removed/added from a struct or an input bit is flipped it will
/// only impact the interpretation of the following elements in the struct. This makes the
/// decoder more flexible in the inputs order, but at the cost of theoretically being more prone
/// to degeneration of the genetic algorithm. This is with it making the decoder more easy to
/// reason about and thus more obvious when a change impacts other parts of the result.
/// A 'perfect' solution here would involve changes to one part of the structured data never
/// affecting any of the other parts of the data, but that requires effectively having separate
/// sources of entropy for each member of the struct.
///
/// 2. **Deep Integration with `TINT_REFLECT`:**
/// Rather than requiring manual range checking or length constraints at every single call-site
/// (as FDP does via `ConsumeIntegralInRange` or `ConsumeBytes`), `SafeFuzzerDecoder` is wrapped
/// inside a reflection based dispatcher (`FuzzDecoder`). Complex nested types (like optionals,
/// vector lists, and hash maps) automatically derive safe maximum capacities and clamp
/// automatically, ensuring no out-of-memory (OOM) or massive allocation panics can occur.
///
/// 3. **Graceful Fallback:**
/// Like FDP, `SafeFuzzerDecoder` never fails. If the buffer is depleted or empty, it returns `0`
/// for numerical types, `false` for booleans, and terminates dynamic collections cleanly. This
/// allows for test cases with no sidecar data to run using safe defaults instead of being
/// outright rejected. It specifically also never returns a 'partial' read, if there is not
/// enough data to satisfy the internal tracking is updated to consume the rest of the data, but
/// the zero/default value is returned.
class SafeFuzzerDecoder {
public:
/// Constructor
/// @param data the fuzzer's sidecar data bytes to consume
explicit SafeFuzzerDecoder(std::span<const std::byte> data) : data_(data) {}
/// @returns true if the decoder has no more bytes to read.
bool IsEOF() const { return data_.empty(); }
/// Safely consumes up to `sizeof(T)` bytes left-to-right to construct an integer.
/// @returns the decoded integer, or 0 if no more bytes are available.
template <typename T>
T ConsumeIntegral() {
static_assert(std::is_integral_v<T>);
if (data_.size() < sizeof(T)) {
data_ = data_.subspan(data_.size()); // Consume whatever is left to prevent reuse
return 0;
}
T result = 0;
tint::Copy(&result, 1, data_.template subspan<0, sizeof(T)>());
data_ = data_.subspan(sizeof(T));
return result;
}
/// Safely consumes up to `sizeof(T)` bytes left-to-right to construct a float.
/// @returns the decoded float, or 0.0 if no more bytes are available.
template <typename T>
T ConsumeFloat() {
static_assert(std::is_floating_point_v<T>);
if (data_.size() < sizeof(T)) {
data_ = data_.subspan(data_.size()); // Consume whatever is left to prevent reuse
return 0;
}
T result = 0;
tint::Copy(&result, 1, data_.template subspan<0, sizeof(T)>());
data_ = data_.subspan(sizeof(T));
// Normalize NaNs and infinities if necessary to avoid non-determinism, or just return.
if (std::isnan(result) || std::isinf(result)) {
result = 0;
}
return result;
}
/// Safely consumes 1 byte to determine a boolean value.
/// @returns true if the byte is odd, otherwise false.
bool ConsumeBool() { return (ConsumeIntegral<uint8_t>() % 2) == 1; }
/// Safely consumes 1 byte to determine a collection's length.
/// @param max_len the maximum permitted collection length.
/// @returns the collection length bounded to [0, max_len] inclusive.
size_t ConsumeCollectionLength(size_t max_len = 16) {
if (data_.empty()) {
return 0;
}
return static_cast<size_t>(ConsumeIntegral<uint8_t>()) % (max_len + 1);
}
private:
std::span<const std::byte> data_;
};
/// FuzzDecoder decodes type `T` from `SafeFuzzerDecoder`.
template <typename T, typename = void>
struct FuzzDecoder;
/// Decoder specialization for integral types.
template <typename T>
requires(std::is_integral_v<T>)
struct FuzzDecoder<T> {
static T Decode(SafeFuzzerDecoder& decoder) { return decoder.ConsumeIntegral<T>(); }
};
template <typename T, typename = void>
struct HasEnumRange : std::false_type {};
template <typename T>
struct HasEnumRange<T, std::void_t<decltype(tint::EnumRange<T>::kMin)>> : std::true_type {};
/// Decoder specialization for enum types.
template <typename T>
requires(std::is_enum_v<T>)
struct FuzzDecoder<T> {
static T Decode(SafeFuzzerDecoder& decoder) {
using UnderT = std::underlying_type_t<T>;
auto val = decoder.ConsumeIntegral<UnderT>();
if constexpr (HasEnumRange<T>::value) {
UnderT min = static_cast<UnderT>(tint::EnumRange<T>::kMin);
UnderT max = static_cast<UnderT>(tint::EnumRange<T>::kMax);
if (min <= max) {
uint64_t uval = static_cast<uint64_t>(val);
uint64_t umin = static_cast<uint64_t>(min);
uint64_t umax = static_cast<uint64_t>(max);
uint64_t range = umax - umin + 1;
val = static_cast<UnderT>(umin + (uval % range));
}
}
return static_cast<T>(val);
}
};
/// Decoder specialization for floating point types.
template <typename T>
requires(std::is_floating_point_v<T>)
struct FuzzDecoder<T> {
static T Decode(SafeFuzzerDecoder& decoder) { return decoder.ConsumeFloat<T>(); }
};
/// Decoder specialization for boolean.
template <>
struct FuzzDecoder<bool, void> {
static bool Decode(SafeFuzzerDecoder& decoder) { return decoder.ConsumeBool(); }
};
/// Decoder specialization for std::bitset.
template <size_t N>
struct FuzzDecoder<std::bitset<N>> {
static std::bitset<N> Decode(SafeFuzzerDecoder& decoder) {
std::bitset<N> bits;
for (size_t i = 0; i < N; ++i) {
bits[i] = decoder.ConsumeBool();
}
return bits;
}
};
/// Decoder specialization for std::string.
template <>
struct FuzzDecoder<std::string, void> {
static std::string Decode(SafeFuzzerDecoder& decoder) {
const size_t len = decoder.ConsumeCollectionLength(kMaxStringLength);
std::string s;
s.reserve(len);
for (size_t i = 0; i < len; ++i) {
if (const char c = decoder.ConsumeIntegral<char>(); c != '\0') {
s.push_back(c);
}
}
return s;
}
};
/// Forward declarations for structural templates.
template <typename T>
requires(HasReflection<T>)
struct FuzzDecoder<T> {
static T Decode(SafeFuzzerDecoder& decoder) {
T object{};
ForeachField(object, [&]<typename F>(F& field) {
using FieldT = std::decay_t<F>;
field = FuzzDecoder<FieldT>::Decode(decoder);
});
return object;
}
};
/// Decoder specialization for std::optional.
template <typename T>
struct FuzzDecoder<std::optional<T>> {
static std::optional<T> Decode(SafeFuzzerDecoder& decoder) {
if (decoder.ConsumeBool()) {
return FuzzDecoder<T>::Decode(decoder);
}
return std::nullopt;
}
};
/// Decoder specialization for std::vector.
template <typename T>
struct FuzzDecoder<std::vector<T>> {
static std::vector<T> Decode(SafeFuzzerDecoder& decoder) {
size_t len = decoder.ConsumeCollectionLength(kMaxCollectionLength);
std::vector<T> vec;
vec.reserve(len);
for (size_t i = 0; i < len; ++i) {
vec.push_back(FuzzDecoder<T>::Decode(decoder));
}
return vec;
}
};
/// Decoder specialization for std::unordered_set.
template <typename T>
struct FuzzDecoder<std::unordered_set<T>> {
static std::unordered_set<T> Decode(SafeFuzzerDecoder& decoder) {
size_t len = decoder.ConsumeCollectionLength(kMaxCollectionLength);
std::unordered_set<T> set;
for (size_t i = 0; i < len; ++i) {
set.insert(FuzzDecoder<T>::Decode(decoder));
}
return set;
}
};
/// Decoder specialization for std::unordered_map.
template <typename K, typename V>
struct FuzzDecoder<std::unordered_map<K, V>> {
static std::unordered_map<K, V> Decode(SafeFuzzerDecoder& decoder) {
const size_t len = decoder.ConsumeCollectionLength(kMaxCollectionLength);
std::unordered_map<K, V> map;
for (size_t i = 0; i < len; ++i) {
auto key = FuzzDecoder<K>::Decode(decoder);
auto val = FuzzDecoder<V>::Decode(decoder);
map[key] = val;
}
return map;
}
};
/// Helper templates for handling std::tuple recursively.
template <typename Tuple, size_t I = 0>
void DecodeTuple(SafeFuzzerDecoder& decoder, Tuple& t) {
if constexpr (I < std::tuple_size_v<Tuple>) {
using ElementT = std::tuple_element_t<I, Tuple>;
std::get<I>(t) = FuzzDecoder<ElementT>::Decode(decoder);
DecodeTuple<Tuple, I + 1>(decoder, t);
}
}
/// Specialization of FuzzDecoder for std::tuple.
template <typename... Args>
struct FuzzDecoder<std::tuple<Args...>> {
static std::tuple<Args...> Decode(SafeFuzzerDecoder& decoder) {
std::tuple<Args...> t{};
DecodeTuple(decoder, t);
return t;
}
};
} // namespace tint::fuzz
#endif // SRC_TINT_CMD_FUZZ_COMMON_FUZZER_DECODER_H_