| // 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_ENCODER_H_ | |
| #define SRC_TINT_CMD_FUZZ_COMMON_FUZZER_ENCODER_H_ | |
| #include <algorithm> | |
| #include <bitset> | |
| #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/reflection/reflection.h" | |
| #include "src/utils/compiler.h" | |
| namespace tint::fuzz { | |
| /// SafeFuzzerEncoder writes out canonical minimal representations of values. | |
| /// | |
| /// Note: Due to clamping of values, i.e. the number of elements in a container, when decoding | |
| /// multiple different entropy sets will map to the same state. For encoding the minimal | |
| /// version is chosen to be canonical to make the code easier to reason about. This means that | |
| /// in general textual roundtrip between Encode/Decode is not guaranteed, but semantics will | |
| /// roundtrip. The canonical form should always textually roundtrip. | |
| class SafeFuzzerEncoder { | |
| public: | |
| /// Constructor | |
| /// @param buffer the vector to append bytes to. | |
| explicit SafeFuzzerEncoder(std::vector<std::byte>& buffer) : buffer_(buffer) {} | |
| /// Appends the bytes of `val`. | |
| template <typename T> | |
| void EncodeIntegral(T val) { | |
| static_assert(std::is_integral_v<T>); | |
| size_t offset = buffer_.size(); | |
| buffer_.resize(offset + sizeof(T)); | |
| tint::Copy(std::span{buffer_}.subspan(offset, sizeof(T)), val); | |
| } | |
| /// Appends the bytes of `val`. | |
| template <typename T> | |
| void EncodeFloat(T val) { | |
| static_assert(std::is_floating_point_v<T>); | |
| size_t offset = buffer_.size(); | |
| buffer_.resize(offset + sizeof(T)); | |
| tint::Copy(std::span{buffer_}.subspan(offset, sizeof(T)), val); | |
| } | |
| /// Appends `1` for true, `0` for false. | |
| void EncodeBool(bool val) { EncodeIntegral<uint8_t>(val ? 1 : 0); } | |
| /// Appends the length byte, clamped to @p max_len. | |
| /// @param len the original collection length. | |
| /// @param max_len the maximum allowed length. | |
| /// @returns the clamped length actually encoded. | |
| size_t EncodeCollectionLength(size_t len, size_t max_len) { | |
| size_t clamped = std::min(len, max_len); | |
| EncodeIntegral<uint8_t>(static_cast<uint8_t>(clamped)); | |
| return clamped; | |
| } | |
| /// Appends raw bytes. | |
| /// @param bytes the span of bytes to append. | |
| void EncodeRaw(std::span<const std::byte> bytes) { | |
| buffer_.insert(buffer_.end(), bytes.begin(), bytes.end()); | |
| } | |
| private: | |
| std::vector<std::byte>& buffer_; | |
| }; | |
| /// FuzzEncoder encodes type `T` into `SafeFuzzerEncoder`. | |
| template <typename T, typename = void> | |
| struct FuzzEncoder; | |
| /// Encoder specialization for integral types. | |
| template <typename T> | |
| requires(std::is_integral_v<T>) | |
| struct FuzzEncoder<T> { | |
| static void Encode(SafeFuzzerEncoder& encoder, T val) { encoder.EncodeIntegral<T>(val); } | |
| }; | |
| /// Encoder specialization for enum types. | |
| template <typename T> | |
| requires(std::is_enum_v<T>) | |
| struct FuzzEncoder<T> { | |
| static void Encode(SafeFuzzerEncoder& encoder, T val) { | |
| using UnderT = std::underlying_type_t<T>; | |
| encoder.EncodeIntegral<UnderT>(static_cast<UnderT>(val)); | |
| } | |
| }; | |
| /// Encoder specialization for floating point types. | |
| template <typename T> | |
| requires(std::is_floating_point_v<T>) | |
| struct FuzzEncoder<T> { | |
| static void Encode(SafeFuzzerEncoder& encoder, T val) { encoder.EncodeFloat<T>(val); } | |
| }; | |
| /// Encoder specialization for boolean. | |
| template <> | |
| struct FuzzEncoder<bool, void> { | |
| static void Encode(SafeFuzzerEncoder& encoder, bool val) { encoder.EncodeBool(val); } | |
| }; | |
| /// Encoder specialization for std::bitset. | |
| template <size_t N> | |
| struct FuzzEncoder<std::bitset<N>> { | |
| static void Encode(SafeFuzzerEncoder& encoder, const std::bitset<N>& val) { | |
| for (size_t i = 0; i < N; ++i) { | |
| encoder.EncodeBool(val[i]); | |
| } | |
| } | |
| }; | |
| /// Encoder specialization for std::string. | |
| template <> | |
| struct FuzzEncoder<std::string, void> { | |
| static void Encode(SafeFuzzerEncoder& encoder, const std::string& val) { | |
| size_t len = encoder.EncodeCollectionLength(val.length(), kMaxStringLength); | |
| for (size_t i = 0; i < len; ++i) { | |
| encoder.EncodeIntegral<char>(val[i]); | |
| } | |
| } | |
| }; | |
| /// Forward declarations for structural templates. | |
| template <typename T> | |
| requires(HasReflection<T>) | |
| struct FuzzEncoder<T> { | |
| static void Encode(SafeFuzzerEncoder& encoder, const T& val) { | |
| ForeachField(val, [&](const auto& field) { | |
| using FieldT = std::decay_t<decltype(field)>; | |
| FuzzEncoder<FieldT>::Encode(encoder, field); | |
| }); | |
| } | |
| }; | |
| /// Encoder specialization for std::optional. | |
| template <typename T> | |
| struct FuzzEncoder<std::optional<T>> { | |
| static void Encode(SafeFuzzerEncoder& encoder, const std::optional<T>& val) { | |
| encoder.EncodeBool(val.has_value()); | |
| if (val.has_value()) { | |
| FuzzEncoder<T>::Encode(encoder, *val); | |
| } | |
| } | |
| }; | |
| /// Encoder specialization for std::vector. | |
| template <typename T> | |
| struct FuzzEncoder<std::vector<T>> { | |
| static void Encode(SafeFuzzerEncoder& encoder, const std::vector<T>& val) { | |
| size_t len = encoder.EncodeCollectionLength(val.size(), kMaxCollectionLength); | |
| for (size_t i = 0; i < len; ++i) { | |
| FuzzEncoder<T>::Encode(encoder, val[i]); | |
| } | |
| } | |
| }; | |
| /// Encoder specialization for std::unordered_set. | |
| template <typename T> | |
| struct FuzzEncoder<std::unordered_set<T>> { | |
| static void Encode(SafeFuzzerEncoder& encoder, const std::unordered_set<T>& val) { | |
| size_t len = encoder.EncodeCollectionLength(val.size(), kMaxCollectionLength); | |
| // Since all elements may not be written out, need to have a stable ordering for them. | |
| // Encoding the individual elements and sorting by the encoded bytes. | |
| std::vector<std::vector<std::byte>> elems; | |
| elems.reserve(val.size()); | |
| for (const auto& item : val) { | |
| std::vector<std::byte> item_buffer; | |
| SafeFuzzerEncoder item_encoder(item_buffer); | |
| FuzzEncoder<T>::Encode(item_encoder, item); | |
| elems.push_back(std::move(item_buffer)); | |
| } | |
| std::sort(elems.begin(), elems.end()); | |
| for (size_t i = 0; i < len; ++i) { | |
| encoder.EncodeRaw(elems[i]); | |
| } | |
| } | |
| }; | |
| /// Encoder specialization for std::unordered_map. | |
| template <typename K, typename V> | |
| struct FuzzEncoder<std::unordered_map<K, V>> { | |
| static void Encode(SafeFuzzerEncoder& encoder, const std::unordered_map<K, V>& val) { | |
| size_t len = encoder.EncodeCollectionLength(val.size(), kMaxCollectionLength); | |
| // Since all elements may not be written out, need to have a stable ordering for them. | |
| // Encoding the keys and values and sorting by the encoded bytes for the keys. | |
| struct EncodedPair { | |
| std::vector<std::byte> key; | |
| std::vector<std::byte> value; | |
| bool operator<(const EncodedPair& other) const { return key < other.key; } | |
| }; | |
| std::vector<EncodedPair> encoded_items; | |
| encoded_items.reserve(val.size()); | |
| for (const auto& [k, v] : val) { | |
| EncodedPair pair; | |
| SafeFuzzerEncoder key_encoder(pair.key); | |
| FuzzEncoder<K>::Encode(key_encoder, k); | |
| SafeFuzzerEncoder value_encoder(pair.value); | |
| FuzzEncoder<V>::Encode(value_encoder, v); | |
| encoded_items.push_back(std::move(pair)); | |
| } | |
| std::sort(encoded_items.begin(), encoded_items.end()); | |
| for (size_t i = 0; i < len; ++i) { | |
| encoder.EncodeRaw(encoded_items[i].key); | |
| encoder.EncodeRaw(encoded_items[i].value); | |
| } | |
| } | |
| }; | |
| /// Helper templates for handling std::tuple recursively. | |
| template <typename Tuple, size_t I = 0> | |
| void EncodeTuple(SafeFuzzerEncoder& encoder, const Tuple& t) { | |
| if constexpr (I < std::tuple_size_v<Tuple>) { | |
| using ElementT = std::tuple_element_t<I, Tuple>; | |
| FuzzEncoder<ElementT>::Encode(encoder, std::get<I>(t)); | |
| EncodeTuple<Tuple, I + 1>(encoder, t); | |
| } | |
| } | |
| /// Specialization of FuzzEncoder for std::tuple. | |
| template <typename... Args> | |
| struct FuzzEncoder<std::tuple<Args...>> { | |
| static void Encode(SafeFuzzerEncoder& encoder, const std::tuple<Args...>& val) { | |
| EncodeTuple(encoder, val); | |
| } | |
| }; | |
| } // namespace tint::fuzz | |
| #endif // SRC_TINT_CMD_FUZZ_COMMON_FUZZER_ENCODER_H_ |