blob: 1482a59886f4db764d89d72faebb472083b34564 [file]
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_FLATBUFFERS_DOMAIN_IMPL_H_
#define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_FLATBUFFERS_DOMAIN_IMPL_H_
#include <algorithm>
#include <cstdint>
#include <initializer_list>
#include <limits>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/base/nullability.h"
#include "absl/base/thread_annotations.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/random/bit_gen_ref.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/synchronization/mutex.h"
#include "flatbuffers/base.h"
#include "flatbuffers/flatbuffer_builder.h"
#include "flatbuffers/reflection_generated.h"
#include "flatbuffers/string.h"
#include "flatbuffers/table.h"
#include "flatbuffers/verifier.h"
#include "./common/logging.h"
#include "./fuzztest/domain_core.h"
#include "./fuzztest/internal/any.h"
#include "./fuzztest/internal/domains/arbitrary_impl.h"
#include "./fuzztest/internal/domains/domain_base.h"
#include "./fuzztest/internal/domains/domain_type_erasure.h"
#include "./fuzztest/internal/domains/element_of_impl.h"
#include "./fuzztest/internal/meta.h"
#include "./fuzztest/internal/serialization.h"
#include "./fuzztest/internal/status.h"
namespace fuzztest::internal {
//
// Flatbuffers enum detection.
//
template <typename Underlying,
typename = std::enable_if_t<std::is_integral_v<Underlying> &&
!std::is_same_v<Underlying, bool>>>
struct FlatbuffersEnumTag {
using type = Underlying;
};
template <typename T>
struct is_flatbuffers_enum_tag : std::false_type {};
template <typename Underlying>
struct is_flatbuffers_enum_tag<FlatbuffersEnumTag<Underlying>>
: std::true_type {};
template <typename T>
inline constexpr bool is_flatbuffers_enum_tag_v =
is_flatbuffers_enum_tag<T>::value;
struct FlatbuffersArrayTag;
struct FlatbuffersTableTag;
struct FlatbuffersStructTag;
struct FlatbuffersUnionTag;
struct FlatbuffersVectorTag;
// Dynamic to static dispatch visitor pattern.
template <typename Visitor>
auto VisitFlatbufferField(const reflection::Schema* absl_nonnull schema,
const reflection::Field* absl_nonnull field,
Visitor visitor) {
auto field_index = field->type()->index();
switch (field->type()->base_type()) {
case reflection::BaseType::Bool:
visitor.template Visit<bool>(field);
break;
case reflection::BaseType::Byte:
if (field_index >= 0) {
visitor.template Visit<FlatbuffersEnumTag<int8_t>>(field);
} else {
visitor.template Visit<int8_t>(field);
}
break;
case reflection::BaseType::Short:
if (field_index >= 0) {
visitor.template Visit<FlatbuffersEnumTag<int16_t>>(field);
} else {
visitor.template Visit<int16_t>(field);
}
break;
case reflection::BaseType::Int:
if (field_index >= 0) {
visitor.template Visit<FlatbuffersEnumTag<int32_t>>(field);
} else {
visitor.template Visit<int32_t>(field);
}
break;
case reflection::BaseType::Long:
if (field_index >= 0) {
visitor.template Visit<FlatbuffersEnumTag<int64_t>>(field);
} else {
visitor.template Visit<int64_t>(field);
}
break;
case reflection::BaseType::UByte:
if (field_index >= 0) {
visitor.template Visit<FlatbuffersEnumTag<uint8_t>>(field);
} else {
visitor.template Visit<uint8_t>(field);
}
break;
case reflection::BaseType::UShort:
if (field_index >= 0) {
visitor.template Visit<FlatbuffersEnumTag<uint16_t>>(field);
} else {
visitor.template Visit<uint16_t>(field);
}
break;
case reflection::BaseType::UInt:
if (field_index >= 0) {
visitor.template Visit<FlatbuffersEnumTag<uint32_t>>(field);
} else {
visitor.template Visit<uint32_t>(field);
}
break;
case reflection::BaseType::ULong:
if (field_index >= 0) {
visitor.template Visit<FlatbuffersEnumTag<uint64_t>>(field);
} else {
visitor.template Visit<uint64_t>(field);
}
break;
case reflection::BaseType::Float:
visitor.template Visit<float>(field);
break;
case reflection::BaseType::Double:
visitor.template Visit<double>(field);
break;
case reflection::BaseType::String:
visitor.template Visit<std::string>(field);
break;
case reflection::BaseType::Vector:
case reflection::BaseType::Vector64:
visitor.template Visit<FlatbuffersVectorTag>(field);
break;
case reflection::BaseType::Array:
visitor.template Visit<FlatbuffersArrayTag>(field);
break;
case reflection::BaseType::Obj: {
auto sub_object = schema->objects()->Get(field->type()->index());
if (sub_object->is_struct()) {
visitor.template Visit<FlatbuffersStructTag>(field);
} else {
visitor.template Visit<FlatbuffersTableTag>(field);
}
break;
}
case reflection::BaseType::Union:
visitor.template Visit<FlatbuffersUnionTag>(field);
break;
case reflection::BaseType::UType:
// Noop
break;
default:
FUZZTEST_LOG(FATAL) << "Unsupported base type: "
<< field->type()->base_type();
}
}
// Flatbuffers enum domain implementation.
template <typename Underlying>
class FlatbuffersEnumDomainImpl
: public domain_implementor::DomainBase<
/*Derived=*/FlatbuffersEnumDomainImpl<Underlying>,
/*ValueType=*/Underlying,
/*CorpusType=*/ElementOfImplCorpusType> {
public:
using typename FlatbuffersEnumDomainImpl::DomainBase::corpus_type;
using typename FlatbuffersEnumDomainImpl::DomainBase::value_type;
explicit FlatbuffersEnumDomainImpl(const reflection::Enum* enum_def)
: enum_def_(enum_def), inner_(GetEnumValues(enum_def, {})) {}
FlatbuffersEnumDomainImpl& WithExcludedValues(
std::initializer_list<value_type> excluded_values) {
excluded_values_ = {excluded_values.begin(), excluded_values.end()};
inner_ =
ElementOfImpl<Underlying>(GetEnumValues(enum_def_, excluded_values));
return *this;
}
corpus_type Init(absl::BitGenRef prng) {
if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
return inner_.Init(prng);
}
void Mutate(corpus_type& val, absl::BitGenRef prng,
const domain_implementor::MutationMetadata& metadata,
bool only_shrink) {
inner_.Mutate(val, prng, metadata, only_shrink);
}
value_type GetValue(corpus_type value) const {
return inner_.GetValue(value);
}
std::optional<corpus_type> FromValue(const value_type& v) const {
return inner_.FromValue(v);
}
std::optional<corpus_type> ParseCorpus(const IRObject& obj) const {
return inner_.ParseCorpus(obj);
}
IRObject SerializeCorpus(const corpus_type& v) const {
return inner_.SerializeCorpus(v);
}
absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
return inner_.ValidateCorpusValue(corpus_value);
}
auto GetPrinter() const { return Printer{*this}; }
private:
const reflection::Enum* enum_def_;
absl::flat_hash_set<value_type> excluded_values_;
ElementOfImpl<Underlying> inner_;
static std::vector<value_type> GetEnumValues(
const reflection::Enum* enum_def,
std::initializer_list<value_type> excluded_values) {
std::vector<value_type> values;
values.reserve(enum_def->values()->size());
for (const auto* value : *enum_def->values()) {
FUZZTEST_CHECK(value->value() >= std::numeric_limits<value_type>::min() &&
value->value() <= std::numeric_limits<value_type>::max())
<< "Enum value from reflection is out of range for the target type.";
if (std::find(excluded_values.begin(), excluded_values.end(),
value->value()) == excluded_values.end()) {
values.push_back(static_cast<value_type>(value->value()));
}
}
return values;
}
struct Printer {
const FlatbuffersEnumDomainImpl& self;
void PrintCorpusValue(const corpus_type& value,
domain_implementor::RawSink out,
domain_implementor::PrintMode mode) const {
if (mode == domain_implementor::PrintMode::kHumanReadable) {
auto user_value = self.GetValue(value);
absl::Format(
out, "%s",
self.enum_def_->values()->LookupByKey(user_value)->name()->str());
} else {
absl::Format(out, "%d", value);
}
}
};
};
// Forward declaration of the domain factory for flatbuffers fields.
template <typename T>
auto GetDefaultDomain(const reflection::Schema* absl_nonnull schema,
const reflection::Field* absl_nonnull field);
// Domain implementation for flatbuffers untyped tables.
// The corpus type is a map of field ids to field values.
class FlatbuffersTableUntypedDomainImpl
: public fuzztest::domain_implementor::DomainBase<
/*Derived=*/FlatbuffersTableUntypedDomainImpl,
/*ValueType=*/const flatbuffers::Table* absl_nonnull,
/*CorpusType=*/
absl::flat_hash_map<
decltype(static_cast<reflection::Field*>(nullptr)->id()),
GenericDomainCorpusType>> {
public:
template <typename T>
friend class FlatbuffersTableDomainImpl;
using typename FlatbuffersTableUntypedDomainImpl::DomainBase::corpus_type;
using typename FlatbuffersTableUntypedDomainImpl::DomainBase::value_type;
explicit FlatbuffersTableUntypedDomainImpl(
const reflection::Schema* absl_nonnull schema,
const reflection::Object* absl_nonnull table_object);
FlatbuffersTableUntypedDomainImpl(
const FlatbuffersTableUntypedDomainImpl& other);
FlatbuffersTableUntypedDomainImpl& operator=(
const FlatbuffersTableUntypedDomainImpl& other);
FlatbuffersTableUntypedDomainImpl(FlatbuffersTableUntypedDomainImpl&& other);
FlatbuffersTableUntypedDomainImpl& operator=(
FlatbuffersTableUntypedDomainImpl&& other);
// Initializes the corpus value.
corpus_type Init(absl::BitGenRef prng);
// Mutates the corpus value.
void Mutate(corpus_type& val, absl::BitGenRef prng,
const domain_implementor::MutationMetadata& metadata,
bool only_shrink);
// Counts the number of fields that can be mutated.
uint64_t CountNumberOfFields(corpus_type& val);
// Mutates the selected field.
// The selected field index is based on the flattened tree.
uint64_t MutateSelectedField(
corpus_type& val, absl::BitGenRef prng,
const domain_implementor::MutationMetadata& metadata, bool only_shrink,
uint64_t selected_field_index);
auto GetPrinter() const { return Printer{*this}; }
absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const;
value_type GetValue(const corpus_type& value) const {
FUZZTEST_LOG(FATAL)
<< "GetValue is not supported for the untyped Flatbuffers domain.";
// Untyped domain does not support GetValue since if it is a nested table it
// would need the top level table corpus value to be able to build it.
return nullptr;
}
// Converts the table pointer to a corpus value.
std::optional<corpus_type> FromValue(const value_type& value) const;
// Converts the IRObject to a corpus value.
std::optional<corpus_type> ParseCorpus(const IRObject& obj) const;
// Converts the corpus value to an IRObject.
IRObject SerializeCorpus(const corpus_type& value) const;
private:
const reflection::Schema* absl_nonnull schema_;
const reflection::Object* absl_nonnull table_object_;
mutable absl::Mutex mutex_;
mutable absl::flat_hash_map<typename corpus_type::key_type, CopyableAny>
domains_ ABSL_GUARDED_BY(mutex_);
bool IsSupportedField(const reflection::Field* absl_nonnull field) const;
uint32_t BuildTable(const corpus_type& value,
flatbuffers::FlatBufferBuilder& builder) const;
// Returns the domain for the given field.
// The domain is cached, and the same instance is returned for the same field.
template <typename T>
auto& GetCachedDomain(const reflection::Field* field) const {
auto get_optional_domain = [this, field]() {
auto optional_domain = OptionalOf(GetDefaultDomain<T>(schema_, field));
if (!field->optional()) {
optional_domain.SetWithoutNull();
}
return Domain<value_type_t<decltype(optional_domain)>>{optional_domain};
};
using DomainT = decltype(get_optional_domain());
// Do the operation under a lock to prevent race conditions in `const`
// methods.
absl::MutexLock l(mutex_);
auto it = domains_.find(field->id());
if (it == domains_.end()) {
it = domains_
.try_emplace(field->id(), std::in_place_type<DomainT>,
get_optional_domain())
.first;
}
return it->second.template GetAs<DomainT>();
}
const reflection::Field* absl_nullable GetFieldById(
typename corpus_type::key_type id) const {
const auto it =
absl::c_find_if(*table_object_->fields(),
[id](const auto* field) { return field->id() == id; });
return it != table_object_->fields()->end() ? *it : nullptr;
}
struct SerializeVisitor {
const FlatbuffersTableUntypedDomainImpl& self;
const GenericDomainCorpusType& corpus_value;
IRObject& out;
template <typename T>
void Visit(const reflection::Field* absl_nonnull field) {
out = self.GetCachedDomain<T>(field).SerializeCorpus(corpus_value);
}
};
struct FromValueVisitor {
const FlatbuffersTableUntypedDomainImpl& self;
value_type user_value;
corpus_type& corpus_value;
template <typename T>
void Visit(const reflection::Field* absl_nonnull field) const {
[[maybe_unused]]
reflection::BaseType base_type = field->type()->base_type();
auto& domain = self.GetCachedDomain<T>(field);
value_type_t<std::decay_t<decltype(domain)>> inner_value;
if constexpr (is_flatbuffers_enum_tag_v<T>) {
if (!field->optional() || user_value->CheckField(field->offset())) {
inner_value = user_value->GetField<typename T::type>(
field->offset(), field->default_integer());
}
} else if constexpr (std::is_integral_v<T>) {
if (!field->optional() || user_value->CheckField(field->offset())) {
inner_value = std::optional(user_value->GetField<T>(
field->offset(), field->default_integer()));
}
} else if constexpr (std::is_floating_point_v<T>) {
if (!field->optional() || user_value->CheckField(field->offset())) {
inner_value = std::optional(
user_value->GetField<T>(field->offset(), field->default_real()));
}
} else if constexpr (std::is_same_v<T, std::string>) {
if (user_value->CheckField(field->offset())) {
inner_value = std::optional(
user_value->GetPointer<flatbuffers::String*>(field->offset())
->str());
}
} else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
auto sub_object = self.schema_->objects()->Get(field->type()->index());
FUZZTEST_CHECK(base_type == reflection::BaseType::Obj &&
!sub_object->is_struct())
<< "Field must be a table type.";
inner_value =
user_value->GetPointer<const flatbuffers::Table*>(field->offset());
}
auto inner = domain.FromValue(inner_value);
if (inner) {
corpus_value[field->id()] = *std::move(inner);
}
};
};
// Create out-of-line table fields, see `BuildTable` for details.
struct TableFieldBuilderVisitor {
const FlatbuffersTableUntypedDomainImpl& self;
flatbuffers::FlatBufferBuilder& builder;
absl::flat_hash_map<typename corpus_type::key_type, flatbuffers::uoffset_t>&
offsets;
const typename corpus_type::mapped_type& corpus_value;
template <typename T>
void Visit(const reflection::Field* absl_nonnull field) const {
if constexpr (std::is_same_v<T, std::string>) {
auto& domain = self.GetCachedDomain<T>(field);
auto user_value = domain.GetValue(corpus_value);
if (user_value.has_value()) {
auto offset =
builder.CreateString(user_value->data(), user_value->size()).o;
offsets.insert({field->id(), offset});
}
} else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
FlatbuffersTableUntypedDomainImpl inner_domain(
self.schema_, self.schema_->objects()->Get(field->type()->index()));
auto optional_corpus = corpus_value.GetAs<
std::variant<std::monostate, fuzztest::GenericDomainCorpusType>>();
if (std::holds_alternative<fuzztest::GenericDomainCorpusType>(
optional_corpus)) {
auto inner_corpus =
std::get<fuzztest::GenericDomainCorpusType>(optional_corpus)
.GetAs<corpus_type>();
auto offset = inner_domain.BuildTable(inner_corpus, builder);
offsets.insert({field->id(), offset});
}
// Else if the variant is std::monostate the optional field is null and
// there is no table to build.
}
}
};
// Create complete table: store "inline fields" values inline, and store just
// offsets for "out-of-line fields". See `BuildTable` for details.
struct TableBuilderVisitor {
const FlatbuffersTableUntypedDomainImpl& self;
flatbuffers::FlatBufferBuilder& builder;
const absl::flat_hash_map<typename corpus_type::key_type,
flatbuffers::uoffset_t>& offsets;
const typename corpus_type::value_type::second_type& corpus_value;
template <typename T>
void Visit(const reflection::Field* absl_nonnull field) const {
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T> ||
is_flatbuffers_enum_tag_v<T>) {
auto& domain = self.GetCachedDomain<T>(field);
auto v = domain.GetValue(corpus_value);
if (!v) {
return;
}
// Store "inline field" value inline.
builder.AddElement(field->offset(), v.value());
} else if constexpr (std::is_same_v<T, std::string>) {
// "Out-of-line field". Store just offset.
if (auto it = offsets.find(field->id()); it != offsets.end()) {
builder.AddOffset(
field->offset(),
flatbuffers::Offset<flatbuffers::String>(it->second));
}
} else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
// "Out-of-line field". Store just offset.
if (auto it = offsets.find(field->id()); it != offsets.end()) {
builder.AddOffset(
field->offset(),
flatbuffers::Offset<flatbuffers::Table>(it->second));
}
}
}
};
struct ParseVisitor {
const FlatbuffersTableUntypedDomainImpl& self;
const IRObject& obj;
std::optional<GenericDomainCorpusType>& out;
template <typename T>
void Visit(const reflection::Field* absl_nonnull field) {
out = self.GetCachedDomain<T>(field).ParseCorpus(obj);
}
};
struct ValidateVisitor {
const FlatbuffersTableUntypedDomainImpl& self;
const GenericDomainCorpusType& corpus_value;
absl::Status& out;
template <typename T>
void Visit(const reflection::Field* absl_nonnull field) {
auto& domain = self.GetCachedDomain<T>(field);
out = domain.ValidateCorpusValue(corpus_value);
if (!out.ok()) {
out = Prefix(out, absl::StrCat("Invalid value for field ",
field->name()->str()));
}
}
};
struct InitializeVisitor {
FlatbuffersTableUntypedDomainImpl& self;
absl::BitGenRef prng;
corpus_type& val;
template <typename T>
void Visit(const reflection::Field* absl_nonnull field) {
auto& domain = self.GetCachedDomain<T>(field);
val[field->id()] = domain.Init(prng);
}
};
struct CountNumberOfMutableFieldsVisitor {
const FlatbuffersTableUntypedDomainImpl& self;
uint64_t& total_weight;
corpus_type& val;
bool only_shrink = false;
template <typename T>
void Visit(const reflection::Field* absl_nonnull field) const {
if (!self.IsSupportedField(field)) return;
if (only_shrink && !val.contains(field->id())) return;
// Add the weight of the field itself.
total_weight += 1;
auto& domain = self.GetCachedDomain<T>(field);
if (auto it = val.find(field->id()); it != val.end()) {
// Add the weight of the field corpus.
total_weight += domain.CountNumberOfFields(it->second);
}
}
};
struct MutateVisitor {
FlatbuffersTableUntypedDomainImpl& self;
absl::BitGenRef prng;
const domain_implementor::MutationMetadata& metadata;
bool only_shrink;
corpus_type& corpus_value;
template <typename T>
void Visit(const reflection::Field* absl_nonnull field) {
auto& domain = self.GetCachedDomain<T>(field);
auto it = corpus_value.find(field->id());
if (it == corpus_value.end()) {
if (only_shrink) return;
it = corpus_value.try_emplace(field->id(), domain.Init(prng)).first;
}
domain.Mutate(it->second, prng, metadata, only_shrink);
}
};
struct Printer {
const FlatbuffersTableUntypedDomainImpl& self;
void PrintCorpusValue(const corpus_type& value,
domain_implementor::RawSink out,
domain_implementor::PrintMode mode) const {
std::vector<typename corpus_type::key_type> field_ids;
field_ids.reserve(value.size());
for (const auto& [id, _] : value) {
field_ids.push_back(id);
}
// Sort the field ids to make the output deterministic.
std::sort(field_ids.begin(), field_ids.end());
absl::Format(out, "{");
bool first = true;
for (const auto id : field_ids) {
if (!first) {
absl::Format(out, ", ");
}
const reflection::Field* absl_nullable field = self.GetFieldById(id);
if (field == nullptr) {
absl::Format(out, "<unknown field: %d>", id);
} else {
VisitFlatbufferField(self.schema_, field,
PrinterVisitor{self, value.at(id), out, mode});
}
first = false;
}
absl::Format(out, "}");
}
};
struct PrinterVisitor {
const FlatbuffersTableUntypedDomainImpl& self;
const GenericDomainCorpusType& val;
domain_implementor::RawSink out;
domain_implementor::PrintMode mode;
template <typename T>
void Visit(const reflection::Field* absl_nonnull field) const {
auto& domain = self.GetCachedDomain<T>(field);
absl::Format(out, "%s: ", field->name()->str());
domain_implementor::PrintValue(domain, val, out, mode);
}
};
};
// Domain factory for flatbuffers fields.
template <typename T>
auto GetDefaultDomain(const reflection::Schema* absl_nonnull schema,
const reflection::Field* absl_nonnull field) {
// Used to satisfy the compiler return type deduction rules.
auto placeholder = Arbitrary<bool>();
if constexpr (std::is_same_v<T, FlatbuffersArrayTag>) {
// TODO: support arrays.
return placeholder;
} else if constexpr (is_flatbuffers_enum_tag_v<T>) {
auto enum_object = schema->enums()->Get(field->type()->index());
return FlatbuffersEnumDomainImpl<typename T::type>(enum_object);
} else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
auto table_object = schema->objects()->Get(field->type()->index());
return FlatbuffersTableUntypedDomainImpl{schema, table_object};
} else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
// TODO: support structs.
return placeholder;
} else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) {
// TODO: support unions.
return placeholder;
} else if constexpr (std::is_same_v<T, FlatbuffersVectorTag>) {
// TODO: support vectors.
return placeholder;
} else {
return Arbitrary<T>();
}
}
// Corpus type for the table domain
struct FlatbuffersTableDomainCorpusType {
// Map of field ids to field values.
typename FlatbuffersTableUntypedDomainImpl::corpus_type untyped_corpus;
// Serialized flatbuffer.
mutable std::vector<uint8_t> buffer;
};
// Domain implementation for flatbuffers generated table classes.
// The corpus type is a pair of:
// - A map of field ids to field values.
// - The serialized buffer of the table.
template <typename T>
class FlatbuffersTableDomainImpl
: public fuzztest::domain_implementor::DomainBase<
/*Derived=*/FlatbuffersTableDomainImpl<T>,
/*ValueType=*/const T*,
/*CorpusType=*/FlatbuffersTableDomainCorpusType> {
public:
using typename FlatbuffersTableDomainImpl::DomainBase::corpus_type;
using typename FlatbuffersTableDomainImpl::DomainBase::value_type;
FlatbuffersTableDomainImpl() {
flatbuffers::Verifier verifier(T::BinarySchema::data(),
T::BinarySchema::size());
FUZZTEST_CHECK(reflection::VerifySchemaBuffer(verifier))
<< "Invalid schema for flatbuffers table.";
auto schema = reflection::GetSchema(T::BinarySchema::data());
auto table_object =
schema->objects()->LookupByKey(T::GetFullyQualifiedName());
inner_ = FlatbuffersTableUntypedDomainImpl{schema, table_object};
}
// Initializes the table with random values.
corpus_type Init(absl::BitGenRef prng) {
if (auto seed = this->MaybeGetRandomSeed(prng)) {
return *seed;
}
// Create new map of field ids to field values
auto val = inner_->Init(prng);
// Return corpus value: pair of the map and the serialized buffer.
return FlatbuffersTableDomainCorpusType{val, {}};
}
// Returns the number of fields in the table.
uint64_t CountNumberOfFields(corpus_type& val) {
return inner_->CountNumberOfFields(val.untyped_corpus);
}
// Mutates the given corpus value.
void Mutate(corpus_type& val, absl::BitGenRef prng,
const domain_implementor::MutationMetadata& metadata,
bool only_shrink) {
// Modify values in the map.
inner_->Mutate(val.untyped_corpus, prng, metadata, only_shrink);
}
// Converts corpus value into the exact flatbuffer.
value_type GetValue(const corpus_type& value) const {
flatbuffers::FlatBufferBuilder builder;
const uint32_t offset = inner_->BuildTable(value.untyped_corpus, builder);
builder.Finish(flatbuffers::Offset<flatbuffers::Table>(offset));
value.buffer =
std::vector<uint8_t>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
return flatbuffers::GetRoot<T>(value.buffer.data());
}
// Creates corpus value from the exact flatbuffer.
std::optional<corpus_type> FromValue(const value_type& value) const {
auto val = inner_->FromValue((const flatbuffers::Table*)value);
if (!val.has_value()) return std::nullopt;
return std::optional(FlatbuffersTableDomainCorpusType{*val, {}});
}
// Returns the printer for the table.
auto GetPrinter() const { return Printer{*inner_}; }
// Returns the parsed corpus value.
std::optional<corpus_type> ParseCorpus(const IRObject& obj) const {
auto val = inner_->ParseCorpus(obj);
if (!val.has_value()) return std::nullopt;
return std::optional(FlatbuffersTableDomainCorpusType{*val, {}});
}
// Returns the serialized corpus value.
IRObject SerializeCorpus(const corpus_type& corpus_value) const {
return inner_->SerializeCorpus(corpus_value.untyped_corpus);
}
// Returns the status of the given corpus value.
absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
return inner_->ValidateCorpusValue(corpus_value.untyped_corpus);
}
private:
std::optional<FlatbuffersTableUntypedDomainImpl> inner_;
struct Printer {
const FlatbuffersTableUntypedDomainImpl& inner;
void PrintCorpusValue(const corpus_type& value,
domain_implementor::RawSink out,
domain_implementor::PrintMode mode) const {
inner.GetPrinter().PrintCorpusValue(value.untyped_corpus, out, mode);
}
};
};
template <typename T>
class ArbitraryImpl<const T*, std::enable_if_t<is_flatbuffers_table_v<T>>>
: public FlatbuffersTableDomainImpl<T> {};
} // namespace fuzztest::internal
#endif // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_FLATBUFFERS_DOMAIN_IMPL_H_