| // 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_ |