summaryrefslogtreecommitdiffstats
path: root/third_party/rust/wasm-smith/src/core.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/wasm-smith/src/core.rs
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/wasm-smith/src/core.rs')
-rw-r--r--third_party/rust/wasm-smith/src/core.rs2404
1 files changed, 2404 insertions, 0 deletions
diff --git a/third_party/rust/wasm-smith/src/core.rs b/third_party/rust/wasm-smith/src/core.rs
new file mode 100644
index 0000000000..6a2836ae0e
--- /dev/null
+++ b/third_party/rust/wasm-smith/src/core.rs
@@ -0,0 +1,2404 @@
+//! Generating arbitary core Wasm modules.
+
+mod code_builder;
+pub(crate) mod encode;
+mod terminate;
+
+use crate::{arbitrary_loop, limited_string, unique_string, Config};
+use arbitrary::{Arbitrary, Result, Unstructured};
+use code_builder::CodeBuilderAllocations;
+use flagset::{flags, FlagSet};
+use std::collections::{HashMap, HashSet};
+use std::convert::TryFrom;
+use std::ops::Range;
+use std::rc::Rc;
+use std::str::{self, FromStr};
+use wasm_encoder::{
+ ArrayType, BlockType, ConstExpr, ExportKind, FieldType, HeapType, RefType, StorageType,
+ StructType, ValType,
+};
+pub(crate) use wasm_encoder::{GlobalType, MemoryType, TableType};
+
+// NB: these constants are used to control the rate at which various events
+// occur. For more information see where these constants are used. Their values
+// are somewhat random in the sense that they're not scientifically determined
+// or anything like that, I just threw a bunch of random data at wasm-smith and
+// measured various rates of ooms/traps/etc and adjusted these so abnormal
+// events were ~1% of the time.
+const CHANCE_OFFSET_INBOUNDS: usize = 10; // bigger = less traps
+const CHANCE_SEGMENT_ON_EMPTY: usize = 10; // bigger = less traps
+const PCT_INBOUNDS: f64 = 0.995; // bigger = less traps
+
+type Instruction = wasm_encoder::Instruction<'static>;
+
+/// A pseudo-random WebAssembly module.
+///
+/// Construct instances of this type (with default configuration) with [the
+/// `Arbitrary`
+/// trait](https://docs.rs/arbitrary/*/arbitrary/trait.Arbitrary.html).
+///
+/// ## Configuring Generated Modules
+///
+/// To configure the shape of generated module, create a
+/// [`Config`][crate::Config] and then call [`Module::new`][crate::Module::new]
+/// with it.
+#[derive(Debug)]
+pub struct Module {
+ config: Config,
+ duplicate_imports_behavior: DuplicateImportsBehavior,
+ valtypes: Vec<ValType>,
+
+ /// All types locally defined in this module (available in the type index
+ /// space).
+ types: Vec<SubType>,
+
+ /// Non-overlapping ranges within `types` that belong to the same rec
+ /// group. All of `types` is covered by these ranges. When GC is not
+ /// enabled, these are all single-element ranges.
+ rec_groups: Vec<Range<usize>>,
+
+ /// A map from a super type to all of its sub types.
+ super_to_sub_types: HashMap<u32, Vec<u32>>,
+
+ /// Indices within `types` that are not final types.
+ can_subtype: Vec<u32>,
+
+ /// Whether we should encode a types section, even if `self.types` is empty.
+ should_encode_types: bool,
+
+ /// All of this module's imports. These don't have their own index space,
+ /// but instead introduce entries to each imported entity's associated index
+ /// space.
+ imports: Vec<Import>,
+
+ /// Whether we should encode an imports section, even if `self.imports` is
+ /// empty.
+ should_encode_imports: bool,
+
+ /// Indices within `types` that are array types.
+ array_types: Vec<u32>,
+
+ /// Indices within `types` that are function types.
+ func_types: Vec<u32>,
+
+ /// Indices within `types that are struct types.
+ struct_types: Vec<u32>,
+
+ /// Number of imported items into this module.
+ num_imports: usize,
+
+ /// The number of tags defined in this module (not imported or
+ /// aliased).
+ num_defined_tags: usize,
+
+ /// The number of functions defined in this module (not imported or
+ /// aliased).
+ num_defined_funcs: usize,
+
+ /// The number of tables defined in this module (not imported or
+ /// aliased).
+ num_defined_tables: usize,
+
+ /// The number of memories defined in this module (not imported or
+ /// aliased).
+ num_defined_memories: usize,
+
+ /// The indexes and initialization expressions of globals defined in this
+ /// module.
+ defined_globals: Vec<(u32, GlobalInitExpr)>,
+
+ /// All tags available to this module, sorted by their index. The list
+ /// entry is the type of each tag.
+ tags: Vec<TagType>,
+
+ /// All functions available to this module, sorted by their index. The list
+ /// entry points to the index in this module where the function type is
+ /// defined (if available) and provides the type of the function.
+ funcs: Vec<(u32, Rc<FuncType>)>,
+
+ /// All tables available to this module, sorted by their index. The list
+ /// entry is the type of each table.
+ tables: Vec<TableType>,
+
+ /// All globals available to this module, sorted by their index. The list
+ /// entry is the type of each global.
+ globals: Vec<GlobalType>,
+
+ /// All memories available to this module, sorted by their index. The list
+ /// entry is the type of each memory.
+ memories: Vec<MemoryType>,
+
+ exports: Vec<(String, ExportKind, u32)>,
+ start: Option<u32>,
+ elems: Vec<ElementSegment>,
+ code: Vec<Code>,
+ data: Vec<DataSegment>,
+
+ /// The predicted size of the effective type of this module, based on this
+ /// module's size of the types of imports/exports.
+ type_size: u32,
+
+ /// Names currently exported from this module.
+ export_names: HashSet<String>,
+}
+
+impl<'a> Arbitrary<'a> for Module {
+ fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
+ Module::new(Config::default(), u)
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum DuplicateImportsBehavior {
+ Allowed,
+ Disallowed,
+}
+
+impl Module {
+ /// Returns a reference to the internal configuration.
+ pub fn config(&self) -> &Config {
+ &self.config
+ }
+
+ /// Creates a new `Module` with the specified `config` for
+ /// configuration and `Unstructured` for the DNA of this module.
+ pub fn new(config: Config, u: &mut Unstructured<'_>) -> Result<Self> {
+ Self::new_internal(config, u, DuplicateImportsBehavior::Allowed)
+ }
+
+ pub(crate) fn new_internal(
+ config: Config,
+ u: &mut Unstructured<'_>,
+ duplicate_imports_behavior: DuplicateImportsBehavior,
+ ) -> Result<Self> {
+ let mut module = Module::empty(config, duplicate_imports_behavior);
+ module.build(u, false)?;
+ Ok(module)
+ }
+
+ fn empty(config: Config, duplicate_imports_behavior: DuplicateImportsBehavior) -> Self {
+ Module {
+ config,
+ duplicate_imports_behavior,
+ valtypes: Vec::new(),
+ types: Vec::new(),
+ rec_groups: Vec::new(),
+ can_subtype: Vec::new(),
+ super_to_sub_types: HashMap::new(),
+ should_encode_types: false,
+ imports: Vec::new(),
+ should_encode_imports: false,
+ array_types: Vec::new(),
+ func_types: Vec::new(),
+ struct_types: Vec::new(),
+ num_imports: 0,
+ num_defined_tags: 0,
+ num_defined_funcs: 0,
+ num_defined_tables: 0,
+ num_defined_memories: 0,
+ defined_globals: Vec::new(),
+ tags: Vec::new(),
+ funcs: Vec::new(),
+ tables: Vec::new(),
+ globals: Vec::new(),
+ memories: Vec::new(),
+ exports: Vec::new(),
+ start: None,
+ elems: Vec::new(),
+ code: Vec::new(),
+ data: Vec::new(),
+ type_size: 0,
+ export_names: HashSet::new(),
+ }
+ }
+}
+
+/// Same as [`Module`], but may be invalid.
+///
+/// This module generates function bodies differnetly than `Module` to try to
+/// better explore wasm decoders and such.
+#[derive(Debug)]
+pub struct MaybeInvalidModule {
+ module: Module,
+}
+
+impl MaybeInvalidModule {
+ /// Encode this Wasm module into bytes.
+ pub fn to_bytes(&self) -> Vec<u8> {
+ self.module.to_bytes()
+ }
+}
+
+impl<'a> Arbitrary<'a> for MaybeInvalidModule {
+ fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
+ let mut module = Module::empty(Config::default(), DuplicateImportsBehavior::Allowed);
+ module.build(u, true)?;
+ Ok(MaybeInvalidModule { module })
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) struct RecGroup {
+ pub(crate) types: Vec<SubType>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) struct SubType {
+ pub(crate) is_final: bool,
+ pub(crate) supertype: Option<u32>,
+ pub(crate) composite_type: CompositeType,
+}
+
+impl SubType {
+ fn unwrap_struct(&self) -> &StructType {
+ self.composite_type.unwrap_struct()
+ }
+
+ fn unwrap_func(&self) -> &Rc<FuncType> {
+ self.composite_type.unwrap_func()
+ }
+
+ fn unwrap_array(&self) -> &ArrayType {
+ self.composite_type.unwrap_array()
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) enum CompositeType {
+ Array(ArrayType),
+ Func(Rc<FuncType>),
+ Struct(StructType),
+}
+
+impl CompositeType {
+ fn unwrap_struct(&self) -> &StructType {
+ match self {
+ CompositeType::Struct(s) => s,
+ _ => panic!("not a struct"),
+ }
+ }
+
+ fn unwrap_func(&self) -> &Rc<FuncType> {
+ match self {
+ CompositeType::Func(f) => f,
+ _ => panic!("not a func"),
+ }
+ }
+
+ fn unwrap_array(&self) -> &ArrayType {
+ match self {
+ CompositeType::Array(a) => a,
+ _ => panic!("not an array"),
+ }
+ }
+}
+
+/// A function signature.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub(crate) struct FuncType {
+ /// Types of the parameter values.
+ pub(crate) params: Vec<ValType>,
+ /// Types of the result values.
+ pub(crate) results: Vec<ValType>,
+}
+
+/// An import of an entity provided externally or by a component.
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) struct Import {
+ /// The name of the module providing this entity.
+ pub(crate) module: String,
+ /// The name of the entity.
+ pub(crate) field: String,
+ /// The type of this entity.
+ pub(crate) entity_type: EntityType,
+}
+
+/// Type of an entity.
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) enum EntityType {
+ /// A global entity.
+ Global(GlobalType),
+ /// A table entity.
+ Table(TableType),
+ /// A memory entity.
+ Memory(MemoryType),
+ /// A tag entity.
+ Tag(TagType),
+ /// A function entity.
+ Func(u32, Rc<FuncType>),
+}
+
+/// Type of a tag.
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) struct TagType {
+ /// Index of the function type.
+ func_type_idx: u32,
+ /// Type of the function.
+ func_type: Rc<FuncType>,
+}
+
+#[derive(Debug)]
+struct ElementSegment {
+ kind: ElementKind,
+ ty: RefType,
+ items: Elements,
+}
+
+#[derive(Debug)]
+enum ElementKind {
+ Passive,
+ Declared,
+ Active {
+ table: Option<u32>, // None == table 0 implicitly
+ offset: Offset,
+ },
+}
+
+#[derive(Debug)]
+enum Elements {
+ Functions(Vec<u32>),
+ Expressions(Vec<Option<u32>>),
+}
+
+#[derive(Debug)]
+struct Code {
+ locals: Vec<ValType>,
+ instructions: Instructions,
+}
+
+#[derive(Debug)]
+enum Instructions {
+ Generated(Vec<Instruction>),
+ Arbitrary(Vec<u8>),
+}
+
+#[derive(Debug)]
+struct DataSegment {
+ kind: DataSegmentKind,
+ init: Vec<u8>,
+}
+
+#[derive(Debug)]
+enum DataSegmentKind {
+ Passive,
+ Active { memory_index: u32, offset: Offset },
+}
+
+#[derive(Debug)]
+pub(crate) enum Offset {
+ Const32(i32),
+ Const64(i64),
+ Global(u32),
+}
+
+#[derive(Debug)]
+pub(crate) enum GlobalInitExpr {
+ FuncRef(u32),
+ ConstExpr(ConstExpr),
+}
+
+impl Module {
+ fn build(&mut self, u: &mut Unstructured, allow_invalid: bool) -> Result<()> {
+ self.valtypes = configured_valtypes(&self.config);
+
+ // We attempt to figure out our available imports *before* creating the types section here,
+ // because the types for the imports are already well-known (specified by the user) and we
+ // must have those populated for all function/etc. imports, no matter what.
+ //
+ // This can affect the available capacity for types and such.
+ if self.arbitrary_imports_from_available(u)? {
+ self.arbitrary_types(u)?;
+ } else {
+ self.arbitrary_types(u)?;
+ self.arbitrary_imports(u)?;
+ }
+
+ self.should_encode_types = !self.types.is_empty() || u.arbitrary()?;
+ self.should_encode_imports = !self.imports.is_empty() || u.arbitrary()?;
+
+ self.arbitrary_tags(u)?;
+ self.arbitrary_funcs(u)?;
+ self.arbitrary_tables(u)?;
+ self.arbitrary_memories(u)?;
+ self.arbitrary_globals(u)?;
+ self.arbitrary_exports(u)?;
+ self.arbitrary_start(u)?;
+ self.arbitrary_elems(u)?;
+ self.arbitrary_data(u)?;
+ self.arbitrary_code(u, allow_invalid)?;
+ Ok(())
+ }
+
+ #[inline]
+ fn val_type_is_sub_type(&self, a: ValType, b: ValType) -> bool {
+ match (a, b) {
+ (a, b) if a == b => true,
+ (ValType::Ref(a), ValType::Ref(b)) => self.ref_type_is_sub_type(a, b),
+ _ => false,
+ }
+ }
+
+ /// Is `a` a subtype of `b`?
+ fn ref_type_is_sub_type(&self, a: RefType, b: RefType) -> bool {
+ if a == b {
+ return true;
+ }
+
+ if a.nullable && !b.nullable {
+ return false;
+ }
+
+ self.heap_type_is_sub_type(a.heap_type, b.heap_type)
+ }
+
+ fn heap_type_is_sub_type(&self, a: HeapType, b: HeapType) -> bool {
+ use HeapType as HT;
+ match (a, b) {
+ (a, b) if a == b => true,
+
+ (HT::Eq | HT::I31 | HT::Struct | HT::Array | HT::None, HT::Any) => true,
+ (HT::I31 | HT::Struct | HT::Array | HT::None, HT::Eq) => true,
+ (HT::NoExtern, HT::Extern) => true,
+ (HT::NoFunc, HT::Func) => true,
+ (HT::None, HT::I31 | HT::Array | HT::Struct) => true,
+
+ (HT::Concrete(a), HT::Eq | HT::Any) => matches!(
+ self.ty(a).composite_type,
+ CompositeType::Array(_) | CompositeType::Struct(_)
+ ),
+
+ (HT::Concrete(a), HT::Struct) => {
+ matches!(self.ty(a).composite_type, CompositeType::Struct(_))
+ }
+
+ (HT::Concrete(a), HT::Array) => {
+ matches!(self.ty(a).composite_type, CompositeType::Array(_))
+ }
+
+ (HT::Concrete(a), HT::Func) => {
+ matches!(self.ty(a).composite_type, CompositeType::Func(_))
+ }
+
+ (HT::Concrete(mut a), HT::Concrete(b)) => loop {
+ if a == b {
+ return true;
+ }
+ if let Some(supertype) = self.ty(a).supertype {
+ a = supertype;
+ } else {
+ return false;
+ }
+ },
+
+ (HT::None, HT::Concrete(b)) => matches!(
+ self.ty(b).composite_type,
+ CompositeType::Array(_) | CompositeType::Struct(_)
+ ),
+
+ (HT::NoFunc, HT::Concrete(b)) => {
+ matches!(self.ty(b).composite_type, CompositeType::Func(_))
+ }
+
+ // Nothing else matches. (Avoid full wildcard matches so that
+ // adding/modifying variants is easier in the future.)
+ (HT::Concrete(_), _)
+ | (HT::Func, _)
+ | (HT::Extern, _)
+ | (HT::Any, _)
+ | (HT::None, _)
+ | (HT::NoExtern, _)
+ | (HT::NoFunc, _)
+ | (HT::Eq, _)
+ | (HT::Struct, _)
+ | (HT::Array, _)
+ | (HT::I31, _) => false,
+
+ // TODO: `exn` probably will be its own type hierarchy and will
+ // probably get `noexn` as well.
+ (HT::Exn, _) => false,
+ }
+ }
+
+ fn arbitrary_types(&mut self, u: &mut Unstructured) -> Result<()> {
+ assert!(self.config.min_types <= self.config.max_types);
+ while self.types.len() < self.config.min_types {
+ self.arbitrary_rec_group(u)?;
+ }
+ while self.types.len() < self.config.max_types {
+ let keep_going = u.arbitrary().unwrap_or(false);
+ if !keep_going {
+ break;
+ }
+ self.arbitrary_rec_group(u)?;
+ }
+ Ok(())
+ }
+
+ fn add_type(&mut self, ty: SubType) -> u32 {
+ let index = u32::try_from(self.types.len()).unwrap();
+
+ if let Some(supertype) = ty.supertype {
+ self.super_to_sub_types
+ .entry(supertype)
+ .or_default()
+ .push(index);
+ }
+
+ let list = match &ty.composite_type {
+ CompositeType::Array(_) => &mut self.array_types,
+ CompositeType::Func(_) => &mut self.func_types,
+ CompositeType::Struct(_) => &mut self.struct_types,
+ };
+ list.push(index);
+
+ if !ty.is_final {
+ self.can_subtype.push(index);
+ }
+
+ self.types.push(ty);
+ index
+ }
+
+ fn arbitrary_rec_group(&mut self, u: &mut Unstructured) -> Result<()> {
+ let rec_group_start = self.types.len();
+
+ if self.config.gc_enabled {
+ // With small probability, clone an existing rec group.
+ if self.clonable_rec_groups().next().is_some() && u.ratio(1, u8::MAX)? {
+ return self.clone_rec_group(u);
+ }
+
+ // Otherwise, create a new rec group with multiple types inside.
+ let max_rec_group_size = self.config.max_types - self.types.len();
+ let rec_group_size = u.int_in_range(0..=max_rec_group_size)?;
+ let type_ref_limit = u32::try_from(self.types.len() + rec_group_size).unwrap();
+ for _ in 0..rec_group_size {
+ let ty = self.arbitrary_sub_type(u, type_ref_limit)?;
+ self.add_type(ty);
+ }
+ } else {
+ let type_ref_limit = u32::try_from(self.types.len()).unwrap();
+ let ty = self.arbitrary_sub_type(u, type_ref_limit)?;
+ self.add_type(ty);
+ }
+
+ self.rec_groups.push(rec_group_start..self.types.len());
+ Ok(())
+ }
+
+ /// Returns an iterator of rec groups that we could currently clone while
+ /// still staying within the max types limit.
+ fn clonable_rec_groups(&self) -> impl Iterator<Item = Range<usize>> + '_ {
+ self.rec_groups
+ .iter()
+ .filter(|r| r.end - r.start <= self.config.max_types.saturating_sub(self.types.len()))
+ .cloned()
+ }
+
+ fn clone_rec_group(&mut self, u: &mut Unstructured) -> Result<()> {
+ // NB: this does *not* guarantee that the cloned rec group will
+ // canonicalize the same as the original rec group and be
+ // deduplicated. That would reqiure a second pass over the cloned types
+ // to rewrite references within the original rec group to be references
+ // into the new rec group. That might make sense to do one day, but for
+ // now we don't do it. That also means that we can't mark the new types
+ // as "subtypes" of the old types and vice versa.
+ let candidates: Vec<_> = self.clonable_rec_groups().collect();
+ let group = u.choose(&candidates)?.clone();
+ let new_rec_group_start = self.types.len();
+ for index in group {
+ let orig_ty_index = u32::try_from(index).unwrap();
+ let ty = self.ty(orig_ty_index).clone();
+ self.add_type(ty);
+ }
+ self.rec_groups.push(new_rec_group_start..self.types.len());
+ Ok(())
+ }
+
+ fn arbitrary_sub_type(
+ &mut self,
+ u: &mut Unstructured,
+ // NB: Types can be referenced up to (but not including)
+ // `type_ref_limit`. It is an exclusive bound to avoid an option: to
+ // disallow any reference to a concrete type (e.g. `(ref $type)`) you
+ // can simply pass `0` here.
+ type_ref_limit: u32,
+ ) -> Result<SubType> {
+ if !self.config.gc_enabled {
+ debug_assert_eq!(type_ref_limit, u32::try_from(self.types.len()).unwrap());
+ return Ok(SubType {
+ is_final: true,
+ supertype: None,
+ composite_type: CompositeType::Func(self.arbitrary_func_type(u, type_ref_limit)?),
+ });
+ }
+
+ if !self.can_subtype.is_empty() && u.ratio(1, 32_u8)? {
+ self.arbitrary_sub_type_of_super_type(u, type_ref_limit)
+ } else {
+ Ok(SubType {
+ is_final: u.arbitrary()?,
+ supertype: None,
+ composite_type: self.arbitrary_composite_type(u, type_ref_limit)?,
+ })
+ }
+ }
+
+ fn arbitrary_sub_type_of_super_type(
+ &mut self,
+ u: &mut Unstructured,
+ type_ref_limit: u32,
+ ) -> Result<SubType> {
+ let supertype = *u.choose(&self.can_subtype)?;
+ let mut composite_type = self.types[usize::try_from(supertype).unwrap()]
+ .composite_type
+ .clone();
+ match &mut composite_type {
+ CompositeType::Array(a) => {
+ a.0 = self.arbitrary_matching_field_type(u, a.0)?;
+ }
+ CompositeType::Func(f) => {
+ *f = self.arbitrary_matching_func_type(u, f)?;
+ }
+ CompositeType::Struct(s) => {
+ *s = self.arbitrary_matching_struct_type(u, s, type_ref_limit)?;
+ }
+ }
+ Ok(SubType {
+ is_final: u.arbitrary()?,
+ supertype: Some(supertype),
+ composite_type,
+ })
+ }
+
+ fn arbitrary_matching_struct_type(
+ &mut self,
+ u: &mut Unstructured,
+ ty: &StructType,
+ type_ref_limit: u32,
+ ) -> Result<StructType> {
+ let len_extra_fields = u.int_in_range(0..=5)?;
+ let mut fields = Vec::with_capacity(ty.fields.len() + len_extra_fields);
+ for field in ty.fields.iter() {
+ fields.push(self.arbitrary_matching_field_type(u, *field)?);
+ }
+ for _ in 0..len_extra_fields {
+ fields.push(self.arbitrary_field_type(u, type_ref_limit)?);
+ }
+ Ok(StructType {
+ fields: fields.into_boxed_slice(),
+ })
+ }
+
+ fn arbitrary_matching_field_type(
+ &mut self,
+ u: &mut Unstructured,
+ ty: FieldType,
+ ) -> Result<FieldType> {
+ Ok(FieldType {
+ element_type: self.arbitrary_matching_storage_type(u, ty.element_type)?,
+ mutable: if ty.mutable { u.arbitrary()? } else { false },
+ })
+ }
+
+ fn arbitrary_matching_storage_type(
+ &mut self,
+ u: &mut Unstructured,
+ ty: StorageType,
+ ) -> Result<StorageType> {
+ match ty {
+ StorageType::I8 => Ok(StorageType::I8),
+ StorageType::I16 => Ok(StorageType::I16),
+ StorageType::Val(ty) => Ok(StorageType::Val(self.arbitrary_matching_val_type(u, ty)?)),
+ }
+ }
+
+ fn arbitrary_matching_val_type(
+ &mut self,
+ u: &mut Unstructured,
+ ty: ValType,
+ ) -> Result<ValType> {
+ match ty {
+ ValType::I32 => Ok(ValType::I32),
+ ValType::I64 => Ok(ValType::I64),
+ ValType::F32 => Ok(ValType::F32),
+ ValType::F64 => Ok(ValType::F64),
+ ValType::V128 => Ok(ValType::V128),
+ ValType::Ref(ty) => Ok(ValType::Ref(self.arbitrary_matching_ref_type(u, ty)?)),
+ }
+ }
+
+ fn arbitrary_matching_ref_type(&self, u: &mut Unstructured, ty: RefType) -> Result<RefType> {
+ Ok(RefType {
+ nullable: ty.nullable,
+ heap_type: self.arbitrary_matching_heap_type(u, ty.heap_type)?,
+ })
+ }
+
+ fn arbitrary_matching_heap_type(&self, u: &mut Unstructured, ty: HeapType) -> Result<HeapType> {
+ if !self.config.gc_enabled {
+ return Ok(ty);
+ }
+ use HeapType as HT;
+ let mut choices = vec![ty];
+ match ty {
+ HT::Any => {
+ choices.extend([HT::Eq, HT::Struct, HT::Array, HT::I31, HT::None]);
+ choices.extend(self.array_types.iter().copied().map(HT::Concrete));
+ choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
+ }
+ HT::Eq => {
+ choices.extend([HT::Struct, HT::Array, HT::I31, HT::None]);
+ choices.extend(self.array_types.iter().copied().map(HT::Concrete));
+ choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
+ }
+ HT::Struct => {
+ choices.extend([HT::Struct, HT::None]);
+ choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
+ }
+ HT::Array => {
+ choices.extend([HT::Array, HT::None]);
+ choices.extend(self.array_types.iter().copied().map(HT::Concrete));
+ }
+ HT::I31 => {
+ choices.push(HT::None);
+ }
+ HT::Concrete(idx) => {
+ if let Some(subs) = self.super_to_sub_types.get(&idx) {
+ choices.extend(subs.iter().copied().map(HT::Concrete));
+ }
+ match self
+ .types
+ .get(usize::try_from(idx).unwrap())
+ .map(|ty| &ty.composite_type)
+ {
+ Some(CompositeType::Array(_)) | Some(CompositeType::Struct(_)) => {
+ choices.push(HT::None)
+ }
+ Some(CompositeType::Func(_)) => choices.push(HT::NoFunc),
+ None => {
+ // The referenced type might be part of this same rec
+ // group we are currently generating, but not generated
+ // yet. In this case, leave `choices` as it is, and we
+ // will just end up choosing the original type again
+ // down below, which is fine.
+ }
+ }
+ }
+ HT::Func => {
+ choices.extend(self.func_types.iter().copied().map(HT::Concrete));
+ choices.push(HT::NoFunc);
+ }
+ HT::Extern => {
+ choices.push(HT::NoExtern);
+ }
+ HT::Exn | HT::None | HT::NoExtern | HT::NoFunc => {}
+ }
+ Ok(*u.choose(&choices)?)
+ }
+
+ fn arbitrary_matching_func_type(
+ &mut self,
+ u: &mut Unstructured,
+ ty: &FuncType,
+ ) -> Result<Rc<FuncType>> {
+ // Note: parameters are contravariant, results are covariant. See
+ // https://github.com/bytecodealliance/wasm-tools/blob/0616ef196a183cf137ee06b4a5993b7d590088bf/crates/wasmparser/src/readers/core/types/matches.rs#L137-L174
+ // for details.
+ let mut params = Vec::with_capacity(ty.params.len());
+ for param in &ty.params {
+ params.push(self.arbitrary_super_type_of_val_type(u, *param)?);
+ }
+ let mut results = Vec::with_capacity(ty.results.len());
+ for result in &ty.results {
+ results.push(self.arbitrary_matching_val_type(u, *result)?);
+ }
+ Ok(Rc::new(FuncType { params, results }))
+ }
+
+ fn arbitrary_super_type_of_val_type(
+ &mut self,
+ u: &mut Unstructured,
+ ty: ValType,
+ ) -> Result<ValType> {
+ match ty {
+ ValType::I32 => Ok(ValType::I32),
+ ValType::I64 => Ok(ValType::I64),
+ ValType::F32 => Ok(ValType::F32),
+ ValType::F64 => Ok(ValType::F64),
+ ValType::V128 => Ok(ValType::V128),
+ ValType::Ref(ty) => Ok(ValType::Ref(self.arbitrary_super_type_of_ref_type(u, ty)?)),
+ }
+ }
+
+ fn arbitrary_super_type_of_ref_type(
+ &self,
+ u: &mut Unstructured,
+ ty: RefType,
+ ) -> Result<RefType> {
+ Ok(RefType {
+ // TODO: For now, only create allow nullable reference
+ // types. Eventually we should support non-nullable reference types,
+ // but this means that we will also need to recognize when it is
+ // impossible to create an instance of the reference (eg `(ref
+ // nofunc)` has no instances, and self-referential types that
+ // contain a non-null self-reference are also impossible to create).
+ nullable: true,
+ heap_type: self.arbitrary_super_type_of_heap_type(u, ty.heap_type)?,
+ })
+ }
+
+ fn arbitrary_super_type_of_heap_type(
+ &self,
+ u: &mut Unstructured,
+ ty: HeapType,
+ ) -> Result<HeapType> {
+ if !self.config.gc_enabled {
+ return Ok(ty);
+ }
+ use HeapType as HT;
+ let mut choices = vec![ty];
+ match ty {
+ HT::None => {
+ choices.extend([HT::Any, HT::Eq, HT::Struct, HT::Array, HT::I31]);
+ choices.extend(self.array_types.iter().copied().map(HT::Concrete));
+ choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
+ }
+ HT::NoExtern => {
+ choices.push(HT::Extern);
+ }
+ HT::NoFunc => {
+ choices.extend(self.func_types.iter().copied().map(HT::Concrete));
+ choices.push(HT::Func);
+ }
+ HT::Concrete(mut idx) => {
+ match &self
+ .types
+ .get(usize::try_from(idx).unwrap())
+ .map(|ty| &ty.composite_type)
+ {
+ Some(CompositeType::Array(_)) => {
+ choices.extend([HT::Any, HT::Eq, HT::Array]);
+ }
+ Some(CompositeType::Func(_)) => {
+ choices.push(HT::Func);
+ }
+ Some(CompositeType::Struct(_)) => {
+ choices.extend([HT::Any, HT::Eq, HT::Struct]);
+ }
+ None => {
+ // Same as in `arbitrary_matching_heap_type`: this was a
+ // forward reference to a concrete type that is part of
+ // this same rec group we are generating right now, and
+ // therefore we haven't generated that type yet. Just
+ // leave `choices` as it is and we will choose the
+ // original type again down below.
+ }
+ }
+ while let Some(supertype) = self
+ .types
+ .get(usize::try_from(idx).unwrap())
+ .and_then(|ty| ty.supertype)
+ {
+ choices.push(HT::Concrete(supertype));
+ idx = supertype;
+ }
+ }
+ HT::Struct | HT::Array | HT::I31 => {
+ choices.extend([HT::Any, HT::Eq]);
+ }
+ HT::Eq => {
+ choices.push(HT::Any);
+ }
+ HT::Exn | HT::Any | HT::Func | HT::Extern => {}
+ }
+ Ok(*u.choose(&choices)?)
+ }
+
+ fn arbitrary_composite_type(
+ &mut self,
+ u: &mut Unstructured,
+ type_ref_limit: u32,
+ ) -> Result<CompositeType> {
+ if !self.config.gc_enabled {
+ return Ok(CompositeType::Func(
+ self.arbitrary_func_type(u, type_ref_limit)?,
+ ));
+ }
+
+ match u.int_in_range(0..=2)? {
+ 0 => Ok(CompositeType::Array(ArrayType(
+ self.arbitrary_field_type(u, type_ref_limit)?,
+ ))),
+ 1 => Ok(CompositeType::Func(
+ self.arbitrary_func_type(u, type_ref_limit)?,
+ )),
+ 2 => Ok(CompositeType::Struct(
+ self.arbitrary_struct_type(u, type_ref_limit)?,
+ )),
+ _ => unreachable!(),
+ }
+ }
+
+ fn arbitrary_struct_type(
+ &mut self,
+ u: &mut Unstructured,
+ type_ref_limit: u32,
+ ) -> Result<StructType> {
+ let len = u.int_in_range(0..=20)?;
+ let mut fields = Vec::with_capacity(len);
+ for _ in 0..len {
+ fields.push(self.arbitrary_field_type(u, type_ref_limit)?);
+ }
+ Ok(StructType {
+ fields: fields.into_boxed_slice(),
+ })
+ }
+
+ fn arbitrary_field_type(
+ &mut self,
+ u: &mut Unstructured,
+ type_ref_limit: u32,
+ ) -> Result<FieldType> {
+ Ok(FieldType {
+ element_type: self.arbitrary_storage_type(u, type_ref_limit)?,
+ mutable: u.arbitrary()?,
+ })
+ }
+
+ fn arbitrary_storage_type(
+ &mut self,
+ u: &mut Unstructured,
+ type_ref_limit: u32,
+ ) -> Result<StorageType> {
+ match u.int_in_range(0..=2)? {
+ 0 => Ok(StorageType::I8),
+ 1 => Ok(StorageType::I16),
+ 2 => Ok(StorageType::Val(self.arbitrary_valtype(u, type_ref_limit)?)),
+ _ => unreachable!(),
+ }
+ }
+
+ fn arbitrary_ref_type(&self, u: &mut Unstructured) -> Result<RefType> {
+ Ok(RefType {
+ nullable: true,
+ heap_type: self.arbitrary_heap_type(u)?,
+ })
+ }
+
+ fn arbitrary_heap_type(&self, u: &mut Unstructured) -> Result<HeapType> {
+ assert!(self.config.reference_types_enabled);
+
+ if self.config.gc_enabled && !self.types.is_empty() && u.arbitrary()? {
+ let type_ref_limit = u32::try_from(self.types.len()).unwrap();
+ let idx = u.int_in_range(0..=type_ref_limit)?;
+ return Ok(HeapType::Concrete(idx));
+ }
+
+ let mut choices = vec![HeapType::Func, HeapType::Extern];
+ if self.config.exceptions_enabled {
+ choices.push(HeapType::Exn);
+ }
+ if self.config.gc_enabled {
+ choices.extend(
+ [
+ HeapType::Any,
+ HeapType::None,
+ HeapType::NoExtern,
+ HeapType::NoFunc,
+ HeapType::Eq,
+ HeapType::Struct,
+ HeapType::Array,
+ HeapType::I31,
+ ]
+ .iter()
+ .copied(),
+ );
+ }
+ u.choose(&choices).copied()
+ }
+
+ fn arbitrary_func_type(
+ &mut self,
+ u: &mut Unstructured,
+ type_ref_limit: u32,
+ ) -> Result<Rc<FuncType>> {
+ arbitrary_func_type(
+ u,
+ &self.config,
+ &self.valtypes,
+ if !self.config.multi_value_enabled {
+ Some(1)
+ } else {
+ None
+ },
+ type_ref_limit,
+ )
+ }
+
+ fn can_add_local_or_import_tag(&self) -> bool {
+ self.config.exceptions_enabled
+ && self.has_tag_func_types()
+ && self.tags.len() < self.config.max_tags
+ }
+
+ fn can_add_local_or_import_func(&self) -> bool {
+ !self.func_types.is_empty() && self.funcs.len() < self.config.max_funcs
+ }
+
+ fn can_add_local_or_import_table(&self) -> bool {
+ self.tables.len() < self.config.max_tables
+ }
+
+ fn can_add_local_or_import_global(&self) -> bool {
+ self.globals.len() < self.config.max_globals
+ }
+
+ fn can_add_local_or_import_memory(&self) -> bool {
+ self.memories.len() < self.config.max_memories
+ }
+
+ fn arbitrary_imports(&mut self, u: &mut Unstructured) -> Result<()> {
+ if self.config.max_type_size < self.type_size {
+ return Ok(());
+ }
+
+ let mut import_strings = HashSet::new();
+ let mut choices: Vec<fn(&mut Unstructured, &mut Module) -> Result<EntityType>> =
+ Vec::with_capacity(5);
+ let min = self.config.min_imports.saturating_sub(self.num_imports);
+ let max = self.config.max_imports.saturating_sub(self.num_imports);
+ arbitrary_loop(u, min, max, |u| {
+ choices.clear();
+ if self.can_add_local_or_import_tag() {
+ choices.push(|u, m| {
+ let ty = m.arbitrary_tag_type(u)?;
+ Ok(EntityType::Tag(ty))
+ });
+ }
+ if self.can_add_local_or_import_func() {
+ choices.push(|u, m| {
+ let idx = *u.choose(&m.func_types)?;
+ let ty = m.func_type(idx).clone();
+ Ok(EntityType::Func(idx, ty))
+ });
+ }
+ if self.can_add_local_or_import_global() {
+ choices.push(|u, m| {
+ let ty = m.arbitrary_global_type(u)?;
+ Ok(EntityType::Global(ty))
+ });
+ }
+ if self.can_add_local_or_import_memory() {
+ choices.push(|u, m| {
+ let ty = arbitrary_memtype(u, m.config())?;
+ Ok(EntityType::Memory(ty))
+ });
+ }
+ if self.can_add_local_or_import_table() {
+ choices.push(|u, m| {
+ let ty = arbitrary_table_type(u, m.config())?;
+ Ok(EntityType::Table(ty))
+ });
+ }
+
+ if choices.is_empty() {
+ // We are out of choices. If we have not have reached the
+ // minimum yet, then we have no way to satisfy the constraint,
+ // but we follow max-constraints before the min-import
+ // constraint.
+ return Ok(false);
+ }
+
+ // Generate a type to import, but only actually add the item if the
+ // type size budget allows us to.
+ let f = u.choose(&choices)?;
+ let entity_type = f(u, self)?;
+ let budget = self.config.max_type_size - self.type_size;
+ if entity_type.size() + 1 > budget {
+ return Ok(false);
+ }
+ self.type_size += entity_type.size() + 1;
+
+ // Generate an arbitrary module/name pair to name this import.
+ let mut import_pair = unique_import_strings(1_000, u)?;
+ if self.duplicate_imports_behavior == DuplicateImportsBehavior::Disallowed {
+ while import_strings.contains(&import_pair) {
+ use std::fmt::Write;
+ write!(&mut import_pair.1, "{}", import_strings.len()).unwrap();
+ }
+ import_strings.insert(import_pair.clone());
+ }
+ let (module, field) = import_pair;
+
+ // Once our name is determined, then we push the typed item into the
+ // appropriate namespace.
+ match &entity_type {
+ EntityType::Tag(ty) => self.tags.push(ty.clone()),
+ EntityType::Func(idx, ty) => self.funcs.push((*idx, ty.clone())),
+ EntityType::Global(ty) => self.globals.push(*ty),
+ EntityType::Table(ty) => self.tables.push(*ty),
+ EntityType::Memory(ty) => self.memories.push(*ty),
+ }
+
+ self.num_imports += 1;
+ self.imports.push(Import {
+ module,
+ field,
+ entity_type,
+ });
+ Ok(true)
+ })?;
+
+ Ok(())
+ }
+
+ /// Generate some arbitrary imports from the list of available imports.
+ ///
+ /// Returns `true` if there was a list of available imports
+ /// configured. Otherwise `false` and the caller should generate arbitrary
+ /// imports.
+ fn arbitrary_imports_from_available(&mut self, u: &mut Unstructured) -> Result<bool> {
+ let example_module = if let Some(wasm) = self.config.available_imports.take() {
+ wasm
+ } else {
+ return Ok(false);
+ };
+
+ #[cfg(feature = "wasmparser")]
+ {
+ self._arbitrary_imports_from_available(u, &example_module)?;
+ Ok(true)
+ }
+ #[cfg(not(feature = "wasmparser"))]
+ {
+ let _ = (example_module, u);
+ panic!("support for `available_imports` was disabled at compile time");
+ }
+ }
+
+ #[cfg(feature = "wasmparser")]
+ fn _arbitrary_imports_from_available(
+ &mut self,
+ u: &mut Unstructured,
+ example_module: &[u8],
+ ) -> Result<()> {
+ // First, parse the module-by-example to collect the types and imports.
+ //
+ // `available_types` will map from a signature index (which is the same as the index into
+ // this vector) as it appears in the parsed code, to the type itself as well as to the
+ // index in our newly generated module. Initially the option is `None` and will become a
+ // `Some` when we encounter an import that uses this signature in the next portion of this
+ // function. See also the `make_func_type` closure below.
+ let mut available_types = Vec::new();
+ let mut available_imports = Vec::<wasmparser::Import>::new();
+ for payload in wasmparser::Parser::new(0).parse_all(&example_module) {
+ match payload.expect("could not parse the available import payload") {
+ wasmparser::Payload::TypeSection(type_reader) => {
+ for ty in type_reader.into_iter_err_on_gc_types() {
+ let ty = ty.expect("could not parse type section");
+ available_types.push((ty, None));
+ }
+ }
+ wasmparser::Payload::ImportSection(import_reader) => {
+ for im in import_reader {
+ let im = im.expect("could not read import");
+ // We can immediately filter whether this is an import we want to
+ // use.
+ let use_import = u.arbitrary().unwrap_or(false);
+ if !use_import {
+ continue;
+ }
+ available_imports.push(im);
+ }
+ }
+ _ => {}
+ }
+ }
+
+ // In this function we need to place imported function/tag types in the types section and
+ // generate import entries (which refer to said types) at the same time.
+ let max_types = self.config.max_types;
+ let multi_value_enabled = self.config.multi_value_enabled;
+ let mut new_imports = Vec::with_capacity(available_imports.len());
+ let first_type_index = self.types.len();
+ let mut new_types = Vec::<SubType>::new();
+
+ // Returns the index to the translated type in the to-be type section, and the reference to
+ // the type itself.
+ let mut make_func_type = |parsed_sig_idx: u32| {
+ let serialized_sig_idx = match available_types.get_mut(parsed_sig_idx as usize) {
+ None => panic!("signature index refers to a type out of bounds"),
+ Some((_, Some(idx))) => *idx as usize,
+ Some((func_type, index_store)) => {
+ let multi_value_required = func_type.results().len() > 1;
+ let new_index = first_type_index + new_types.len();
+ if new_index >= max_types || (multi_value_required && !multi_value_enabled) {
+ return None;
+ }
+ let func_type = Rc::new(FuncType {
+ params: func_type
+ .params()
+ .iter()
+ .map(|t| (*t).try_into().unwrap())
+ .collect(),
+ results: func_type
+ .results()
+ .iter()
+ .map(|t| (*t).try_into().unwrap())
+ .collect(),
+ });
+ index_store.replace(new_index as u32);
+ new_types.push(SubType {
+ is_final: true,
+ supertype: None,
+ composite_type: CompositeType::Func(Rc::clone(&func_type)),
+ });
+ new_index
+ }
+ };
+ match &new_types[serialized_sig_idx - first_type_index].composite_type {
+ CompositeType::Func(f) => Some((serialized_sig_idx as u32, Rc::clone(f))),
+ _ => unimplemented!(),
+ }
+ };
+
+ for import in available_imports {
+ let type_size_budget = self.config.max_type_size - self.type_size;
+ let entity_type = match &import.ty {
+ wasmparser::TypeRef::Func(sig_idx) => {
+ if self.funcs.len() >= self.config.max_funcs {
+ continue;
+ } else if let Some((sig_idx, func_type)) = make_func_type(*sig_idx) {
+ let entity = EntityType::Func(sig_idx as u32, Rc::clone(&func_type));
+ if type_size_budget < entity.size() {
+ continue;
+ }
+ self.funcs.push((sig_idx, func_type));
+ entity
+ } else {
+ continue;
+ }
+ }
+
+ wasmparser::TypeRef::Tag(wasmparser::TagType { func_type_idx, .. }) => {
+ let can_add_tag = self.tags.len() < self.config.max_tags;
+ if !self.config.exceptions_enabled || !can_add_tag {
+ continue;
+ } else if let Some((sig_idx, func_type)) = make_func_type(*func_type_idx) {
+ let tag_type = TagType {
+ func_type_idx: sig_idx,
+ func_type,
+ };
+ let entity = EntityType::Tag(tag_type.clone());
+ if type_size_budget < entity.size() {
+ continue;
+ }
+ self.tags.push(tag_type);
+ entity
+ } else {
+ continue;
+ }
+ }
+
+ wasmparser::TypeRef::Table(table_ty) => {
+ let table_ty = TableType::try_from(*table_ty).unwrap();
+ let entity = EntityType::Table(table_ty);
+ let type_size = entity.size();
+ if type_size_budget < type_size || !self.can_add_local_or_import_table() {
+ continue;
+ }
+ self.type_size += type_size;
+ self.tables.push(table_ty);
+ entity
+ }
+
+ wasmparser::TypeRef::Memory(memory_ty) => {
+ let memory_ty = MemoryType::try_from(*memory_ty).unwrap();
+ let entity = EntityType::Memory(memory_ty);
+ let type_size = entity.size();
+ if type_size_budget < type_size || !self.can_add_local_or_import_memory() {
+ continue;
+ }
+ self.type_size += type_size;
+ self.memories.push(memory_ty);
+ entity
+ }
+
+ wasmparser::TypeRef::Global(global_ty) => {
+ let global_ty = (*global_ty).try_into().unwrap();
+ let entity = EntityType::Global(global_ty);
+ let type_size = entity.size();
+ if type_size_budget < type_size || !self.can_add_local_or_import_global() {
+ continue;
+ }
+ self.type_size += type_size;
+ self.globals.push(global_ty);
+ entity
+ }
+ };
+ new_imports.push(Import {
+ module: import.module.to_string(),
+ field: import.name.to_string(),
+ entity_type,
+ });
+ self.num_imports += 1;
+ }
+
+ // Finally, add the entities we just generated.
+ for ty in new_types {
+ self.rec_groups.push(self.types.len()..self.types.len() + 1);
+ self.add_type(ty);
+ }
+ self.imports.extend(new_imports);
+
+ Ok(())
+ }
+
+ fn type_of(&self, kind: ExportKind, index: u32) -> EntityType {
+ match kind {
+ ExportKind::Global => EntityType::Global(self.globals[index as usize]),
+ ExportKind::Memory => EntityType::Memory(self.memories[index as usize]),
+ ExportKind::Table => EntityType::Table(self.tables[index as usize]),
+ ExportKind::Func => {
+ let (_idx, ty) = &self.funcs[index as usize];
+ EntityType::Func(u32::max_value(), ty.clone())
+ }
+ ExportKind::Tag => EntityType::Tag(self.tags[index as usize].clone()),
+ }
+ }
+
+ fn ty(&self, idx: u32) -> &SubType {
+ &self.types[idx as usize]
+ }
+
+ fn func_types(&self) -> impl Iterator<Item = (u32, &FuncType)> + '_ {
+ self.func_types
+ .iter()
+ .copied()
+ .map(move |type_i| (type_i, &**self.func_type(type_i)))
+ }
+
+ fn func_type(&self, idx: u32) -> &Rc<FuncType> {
+ match &self.ty(idx).composite_type {
+ CompositeType::Func(f) => f,
+ _ => panic!("types[{idx}] is not a func type"),
+ }
+ }
+
+ fn tags(&self) -> impl Iterator<Item = (u32, &TagType)> + '_ {
+ self.tags
+ .iter()
+ .enumerate()
+ .map(move |(i, ty)| (i as u32, ty))
+ }
+
+ fn funcs(&self) -> impl Iterator<Item = (u32, &Rc<FuncType>)> + '_ {
+ self.funcs
+ .iter()
+ .enumerate()
+ .map(move |(i, (_, ty))| (i as u32, ty))
+ }
+
+ fn has_tag_func_types(&self) -> bool {
+ self.tag_func_types().next().is_some()
+ }
+
+ fn tag_func_types(&self) -> impl Iterator<Item = u32> + '_ {
+ self.func_types
+ .iter()
+ .copied()
+ .filter(move |i| self.func_type(*i).results.is_empty())
+ }
+
+ fn arbitrary_valtype(&self, u: &mut Unstructured, type_ref_limit: u32) -> Result<ValType> {
+ arbitrary_valtype(u, &self.config, &self.valtypes, type_ref_limit)
+ }
+
+ fn arbitrary_global_type(&self, u: &mut Unstructured) -> Result<GlobalType> {
+ Ok(GlobalType {
+ val_type: self.arbitrary_valtype(u, u32::try_from(self.types.len()).unwrap())?,
+ mutable: u.arbitrary()?,
+ })
+ }
+
+ fn arbitrary_tag_type(&self, u: &mut Unstructured) -> Result<TagType> {
+ let candidate_func_types: Vec<_> = self.tag_func_types().collect();
+ arbitrary_tag_type(u, &candidate_func_types, |ty_idx| {
+ self.func_type(ty_idx).clone()
+ })
+ }
+
+ fn arbitrary_tags(&mut self, u: &mut Unstructured) -> Result<()> {
+ if !self.config.exceptions_enabled || !self.has_tag_func_types() {
+ return Ok(());
+ }
+
+ arbitrary_loop(u, self.config.min_tags, self.config.max_tags, |u| {
+ if !self.can_add_local_or_import_tag() {
+ return Ok(false);
+ }
+ self.tags.push(self.arbitrary_tag_type(u)?);
+ self.num_defined_tags += 1;
+ Ok(true)
+ })
+ }
+
+ fn arbitrary_funcs(&mut self, u: &mut Unstructured) -> Result<()> {
+ if self.func_types.is_empty() {
+ return Ok(());
+ }
+
+ arbitrary_loop(u, self.config.min_funcs, self.config.max_funcs, |u| {
+ if !self.can_add_local_or_import_func() {
+ return Ok(false);
+ }
+ let max = self.func_types.len() - 1;
+ let ty = self.func_types[u.int_in_range(0..=max)?];
+ self.funcs.push((ty, self.func_type(ty).clone()));
+ self.num_defined_funcs += 1;
+ Ok(true)
+ })
+ }
+
+ fn arbitrary_tables(&mut self, u: &mut Unstructured) -> Result<()> {
+ arbitrary_loop(
+ u,
+ self.config.min_tables as usize,
+ self.config.max_tables as usize,
+ |u| {
+ if !self.can_add_local_or_import_table() {
+ return Ok(false);
+ }
+ self.num_defined_tables += 1;
+ let ty = arbitrary_table_type(u, self.config())?;
+ self.tables.push(ty);
+ Ok(true)
+ },
+ )
+ }
+
+ fn arbitrary_memories(&mut self, u: &mut Unstructured) -> Result<()> {
+ arbitrary_loop(
+ u,
+ self.config.min_memories as usize,
+ self.config.max_memories as usize,
+ |u| {
+ if !self.can_add_local_or_import_memory() {
+ return Ok(false);
+ }
+ self.num_defined_memories += 1;
+ self.memories.push(arbitrary_memtype(u, self.config())?);
+ Ok(true)
+ },
+ )
+ }
+
+ fn arbitrary_globals(&mut self, u: &mut Unstructured) -> Result<()> {
+ let mut choices: Vec<Box<dyn Fn(&mut Unstructured, ValType) -> Result<GlobalInitExpr>>> =
+ vec![];
+ let num_imported_globals = self.globals.len();
+
+ arbitrary_loop(u, self.config.min_globals, self.config.max_globals, |u| {
+ if !self.can_add_local_or_import_global() {
+ return Ok(false);
+ }
+
+ let ty = self.arbitrary_global_type(u)?;
+
+ choices.clear();
+ let num_funcs = self.funcs.len() as u32;
+ choices.push(Box::new(move |u, ty| {
+ Ok(GlobalInitExpr::ConstExpr(match ty {
+ ValType::I32 => ConstExpr::i32_const(u.arbitrary()?),
+ ValType::I64 => ConstExpr::i64_const(u.arbitrary()?),
+ ValType::F32 => ConstExpr::f32_const(u.arbitrary()?),
+ ValType::F64 => ConstExpr::f64_const(u.arbitrary()?),
+ ValType::V128 => ConstExpr::v128_const(u.arbitrary()?),
+ ValType::Ref(ty) => {
+ assert!(ty.nullable);
+ if ty.heap_type == HeapType::Func && num_funcs > 0 && u.arbitrary()? {
+ let func = u.int_in_range(0..=num_funcs - 1)?;
+ return Ok(GlobalInitExpr::FuncRef(func));
+ }
+ ConstExpr::ref_null(ty.heap_type)
+ }
+ }))
+ }));
+
+ for (i, g) in self.globals[..num_imported_globals].iter().enumerate() {
+ if !g.mutable && g.val_type == ty.val_type {
+ choices.push(Box::new(move |_, _| {
+ Ok(GlobalInitExpr::ConstExpr(ConstExpr::global_get(i as u32)))
+ }));
+ }
+ }
+
+ let f = u.choose(&choices)?;
+ let expr = f(u, ty.val_type)?;
+ let global_idx = self.globals.len() as u32;
+ self.globals.push(ty);
+ self.defined_globals.push((global_idx, expr));
+ Ok(true)
+ })
+ }
+
+ fn arbitrary_exports(&mut self, u: &mut Unstructured) -> Result<()> {
+ if self.config.max_type_size < self.type_size && !self.config.export_everything {
+ return Ok(());
+ }
+
+ // Build up a list of candidates for each class of import
+ let mut choices: Vec<Vec<(ExportKind, u32)>> = Vec::with_capacity(6);
+ choices.push(
+ (0..self.funcs.len())
+ .map(|i| (ExportKind::Func, i as u32))
+ .collect(),
+ );
+ choices.push(
+ (0..self.tables.len())
+ .map(|i| (ExportKind::Table, i as u32))
+ .collect(),
+ );
+ choices.push(
+ (0..self.memories.len())
+ .map(|i| (ExportKind::Memory, i as u32))
+ .collect(),
+ );
+ choices.push(
+ (0..self.globals.len())
+ .map(|i| (ExportKind::Global, i as u32))
+ .collect(),
+ );
+
+ // If the configuration demands exporting everything, we do so here and
+ // early-return.
+ if self.config.export_everything {
+ for choices_by_kind in choices {
+ for (kind, idx) in choices_by_kind {
+ let name = unique_string(1_000, &mut self.export_names, u)?;
+ self.add_arbitrary_export(name, kind, idx)?;
+ }
+ }
+ return Ok(());
+ }
+
+ arbitrary_loop(u, self.config.min_exports, self.config.max_exports, |u| {
+ // Remove all candidates for export whose type size exceeds our
+ // remaining budget for type size. Then also remove any classes
+ // of exports which no longer have any candidates.
+ //
+ // If there's nothing remaining after this, then we're done.
+ let max_size = self.config.max_type_size - self.type_size;
+ for list in choices.iter_mut() {
+ list.retain(|(kind, idx)| self.type_of(*kind, *idx).size() + 1 < max_size);
+ }
+ choices.retain(|list| !list.is_empty());
+ if choices.is_empty() {
+ return Ok(false);
+ }
+
+ // Pick a name, then pick the export, and then we can record
+ // information about the chosen export.
+ let name = unique_string(1_000, &mut self.export_names, u)?;
+ let list = u.choose(&choices)?;
+ let (kind, idx) = *u.choose(list)?;
+ self.add_arbitrary_export(name, kind, idx)?;
+ Ok(true)
+ })
+ }
+
+ fn add_arbitrary_export(&mut self, name: String, kind: ExportKind, idx: u32) -> Result<()> {
+ let ty = self.type_of(kind, idx);
+ self.type_size += 1 + ty.size();
+ if self.type_size <= self.config.max_type_size {
+ self.exports.push((name, kind, idx));
+ Ok(())
+ } else {
+ // If our addition of exports takes us above the allowed number of
+ // types, we fail; this error code is not the most illustrative of
+ // the cause but is the best available from `arbitrary`.
+ Err(arbitrary::Error::IncorrectFormat)
+ }
+ }
+
+ fn arbitrary_start(&mut self, u: &mut Unstructured) -> Result<()> {
+ if !self.config.allow_start_export {
+ return Ok(());
+ }
+
+ let mut choices = Vec::with_capacity(self.funcs.len() as usize);
+
+ for (func_idx, ty) in self.funcs() {
+ if ty.params.is_empty() && ty.results.is_empty() {
+ choices.push(func_idx);
+ }
+ }
+
+ if !choices.is_empty() && u.arbitrary().unwrap_or(false) {
+ let f = *u.choose(&choices)?;
+ self.start = Some(f);
+ }
+
+ Ok(())
+ }
+
+ fn arbitrary_elems(&mut self, u: &mut Unstructured) -> Result<()> {
+ let func_max = self.funcs.len() as u32;
+
+ // Create a helper closure to choose an arbitrary offset.
+ let mut offset_global_choices = vec![];
+ if !self.config.disallow_traps {
+ for (i, g) in self.globals[..self.globals.len() - self.defined_globals.len()]
+ .iter()
+ .enumerate()
+ {
+ if !g.mutable && g.val_type == ValType::I32 {
+ offset_global_choices.push(i as u32);
+ }
+ }
+ }
+ let arbitrary_active_elem = |u: &mut Unstructured,
+ min_mem_size: u32,
+ table: Option<u32>,
+ disallow_traps: bool,
+ table_ty: &TableType| {
+ let (offset, max_size_hint) = if !offset_global_choices.is_empty() && u.arbitrary()? {
+ let g = u.choose(&offset_global_choices)?;
+ (Offset::Global(*g), None)
+ } else {
+ let max_mem_size = if disallow_traps {
+ table_ty.minimum
+ } else {
+ u32::MAX
+ };
+ let offset =
+ arbitrary_offset(u, min_mem_size.into(), max_mem_size.into(), 0)? as u32;
+ let max_size_hint = if disallow_traps
+ || (offset <= min_mem_size && u.int_in_range(0..=CHANCE_OFFSET_INBOUNDS)? != 0)
+ {
+ Some(min_mem_size - offset)
+ } else {
+ None
+ };
+ (Offset::Const32(offset as i32), max_size_hint)
+ };
+ Ok((ElementKind::Active { table, offset }, max_size_hint))
+ };
+
+ type GenElemSegment<'a> =
+ dyn Fn(&mut Unstructured) -> Result<(ElementKind, Option<u32>)> + 'a;
+ let mut funcrefs: Vec<Box<GenElemSegment>> = Vec::new();
+ let mut externrefs: Vec<Box<GenElemSegment>> = Vec::new();
+ let disallow_traps = self.config.disallow_traps;
+ for (i, ty) in self.tables.iter().enumerate() {
+ // If this table starts with no capacity then any non-empty element
+ // segment placed onto it will immediately trap, which isn't too
+ // too interesting. If that's the case give it an unlikely chance
+ // of proceeding.
+ if ty.minimum == 0 && u.int_in_range(0..=CHANCE_SEGMENT_ON_EMPTY)? != 0 {
+ continue;
+ }
+
+ let dst = if ty.element_type == RefType::FUNCREF {
+ &mut funcrefs
+ } else {
+ &mut externrefs
+ };
+ let minimum = ty.minimum;
+ // If the first table is a funcref table then it's a candidate for
+ // the MVP encoding of element segments.
+ if i == 0 && ty.element_type == RefType::FUNCREF {
+ dst.push(Box::new(move |u| {
+ arbitrary_active_elem(u, minimum, None, disallow_traps, ty)
+ }));
+ }
+ if self.config.bulk_memory_enabled {
+ let idx = Some(i as u32);
+ dst.push(Box::new(move |u| {
+ arbitrary_active_elem(u, minimum, idx, disallow_traps, ty)
+ }));
+ }
+ }
+
+ // Bulk memory enables passive/declared segments for funcrefs, and
+ // reference types additionally enables the segments for externrefs.
+ if self.config.bulk_memory_enabled {
+ funcrefs.push(Box::new(|_| Ok((ElementKind::Passive, None))));
+ funcrefs.push(Box::new(|_| Ok((ElementKind::Declared, None))));
+ if self.config.reference_types_enabled {
+ externrefs.push(Box::new(|_| Ok((ElementKind::Passive, None))));
+ externrefs.push(Box::new(|_| Ok((ElementKind::Declared, None))));
+ }
+ }
+
+ let mut choices = Vec::new();
+ if !funcrefs.is_empty() {
+ choices.push((&funcrefs, RefType::FUNCREF));
+ }
+ if !externrefs.is_empty() {
+ choices.push((&externrefs, RefType::EXTERNREF));
+ }
+
+ if choices.is_empty() {
+ return Ok(());
+ }
+ arbitrary_loop(
+ u,
+ self.config.min_element_segments,
+ self.config.max_element_segments,
+ |u| {
+ // Choose whether to generate a segment whose elements are initialized via
+ // expressions, or one whose elements are initialized via function indices.
+ let (kind_candidates, ty) = *u.choose(&choices)?;
+
+ // Select a kind for this segment now that we know the number of
+ // items the segment will hold.
+ let (kind, max_size_hint) = u.choose(kind_candidates)?(u)?;
+ let max = max_size_hint
+ .map(|i| usize::try_from(i).unwrap())
+ .unwrap_or_else(|| self.config.max_elements);
+
+ // Pick whether we're going to use expression elements or
+ // indices. Note that externrefs must use expressions,
+ // and functions without reference types must use indices.
+ let items = if ty == RefType::EXTERNREF
+ || (self.config.reference_types_enabled && u.arbitrary()?)
+ {
+ let mut init = vec![];
+ arbitrary_loop(u, self.config.min_elements, max, |u| {
+ init.push(
+ if ty == RefType::EXTERNREF || func_max == 0 || u.arbitrary()? {
+ None
+ } else {
+ Some(u.int_in_range(0..=func_max - 1)?)
+ },
+ );
+ Ok(true)
+ })?;
+ Elements::Expressions(init)
+ } else {
+ let mut init = vec![];
+ if func_max > 0 {
+ arbitrary_loop(u, self.config.min_elements, max, |u| {
+ let func_idx = u.int_in_range(0..=func_max - 1)?;
+ init.push(func_idx);
+ Ok(true)
+ })?;
+ }
+ Elements::Functions(init)
+ };
+
+ self.elems.push(ElementSegment { kind, ty, items });
+ Ok(true)
+ },
+ )
+ }
+
+ fn arbitrary_code(&mut self, u: &mut Unstructured, allow_invalid: bool) -> Result<()> {
+ self.code.reserve(self.num_defined_funcs);
+ let mut allocs = CodeBuilderAllocations::new(self);
+ for (_, ty) in self.funcs[self.funcs.len() - self.num_defined_funcs..].iter() {
+ let body = self.arbitrary_func_body(u, ty, &mut allocs, allow_invalid)?;
+ self.code.push(body);
+ }
+ allocs.finish(u, self)?;
+ Ok(())
+ }
+
+ fn arbitrary_func_body(
+ &self,
+ u: &mut Unstructured,
+ ty: &FuncType,
+ allocs: &mut CodeBuilderAllocations,
+ allow_invalid: bool,
+ ) -> Result<Code> {
+ let mut locals = self.arbitrary_locals(u)?;
+ let builder = allocs.builder(ty, &mut locals);
+ let instructions = if allow_invalid && u.arbitrary().unwrap_or(false) {
+ Instructions::Arbitrary(arbitrary_vec_u8(u)?)
+ } else {
+ Instructions::Generated(builder.arbitrary(u, self)?)
+ };
+
+ Ok(Code {
+ locals,
+ instructions,
+ })
+ }
+
+ fn arbitrary_locals(&self, u: &mut Unstructured) -> Result<Vec<ValType>> {
+ let mut ret = Vec::new();
+ arbitrary_loop(u, 0, 100, |u| {
+ ret.push(self.arbitrary_valtype(u, u32::try_from(self.types.len()).unwrap())?);
+ Ok(true)
+ })?;
+ Ok(ret)
+ }
+
+ fn arbitrary_data(&mut self, u: &mut Unstructured) -> Result<()> {
+ // With bulk-memory we can generate passive data, otherwise if there are
+ // no memories we can't generate any data.
+ let memories = self.memories.len() as u32;
+ if memories == 0 && !self.config.bulk_memory_enabled {
+ return Ok(());
+ }
+ let disallow_traps = self.config.disallow_traps;
+ let mut choices32: Vec<Box<dyn Fn(&mut Unstructured, u64, usize) -> Result<Offset>>> =
+ vec![];
+ choices32.push(Box::new(|u, min_size, data_len| {
+ let min = u32::try_from(min_size.saturating_mul(64 * 1024))
+ .unwrap_or(u32::MAX)
+ .into();
+ let max = if disallow_traps { min } else { u32::MAX.into() };
+ Ok(Offset::Const32(
+ arbitrary_offset(u, min, max, data_len)? as i32
+ ))
+ }));
+ let mut choices64: Vec<Box<dyn Fn(&mut Unstructured, u64, usize) -> Result<Offset>>> =
+ vec![];
+ choices64.push(Box::new(|u, min_size, data_len| {
+ let min = min_size.saturating_mul(64 * 1024);
+ let max = if disallow_traps { min } else { u64::MAX };
+ Ok(Offset::Const64(
+ arbitrary_offset(u, min, max, data_len)? as i64
+ ))
+ }));
+ if !self.config.disallow_traps {
+ for (i, g) in self.globals[..self.globals.len() - self.defined_globals.len()]
+ .iter()
+ .enumerate()
+ {
+ if g.mutable {
+ continue;
+ }
+ if g.val_type == ValType::I32 {
+ choices32.push(Box::new(move |_, _, _| Ok(Offset::Global(i as u32))));
+ } else if g.val_type == ValType::I64 {
+ choices64.push(Box::new(move |_, _, _| Ok(Offset::Global(i as u32))));
+ }
+ }
+ }
+
+ // Build a list of candidate memories that we'll add data initializers
+ // for. If a memory doesn't have an initial size then any initializers
+ // for that memory will trap instantiation, which isn't too
+ // interesting. Try to make this happen less often by making it less
+ // likely that a memory with 0 size will have a data segment.
+ let mut memories = Vec::new();
+ for (i, mem) in self.memories.iter().enumerate() {
+ if mem.minimum > 0 || u.int_in_range(0..=CHANCE_SEGMENT_ON_EMPTY)? == 0 {
+ memories.push(i as u32);
+ }
+ }
+
+ // With memories we can generate data segments, and with bulk memory we
+ // can generate passive segments. Without these though we can't create
+ // a valid module with data segments.
+ if memories.is_empty() && !self.config.bulk_memory_enabled {
+ return Ok(());
+ }
+
+ arbitrary_loop(
+ u,
+ self.config.min_data_segments,
+ self.config.max_data_segments,
+ |u| {
+ let mut init: Vec<u8> = u.arbitrary()?;
+
+ // Passive data can only be generated if bulk memory is enabled.
+ // Otherwise if there are no memories we *only* generate passive
+ // data. Finally if all conditions are met we use an input byte to
+ // determine if it should be passive or active.
+ let kind =
+ if self.config.bulk_memory_enabled && (memories.is_empty() || u.arbitrary()?) {
+ DataSegmentKind::Passive
+ } else {
+ let memory_index = *u.choose(&memories)?;
+ let mem = &self.memories[memory_index as usize];
+ let f = if mem.memory64 {
+ u.choose(&choices64)?
+ } else {
+ u.choose(&choices32)?
+ };
+ let mut offset = f(u, mem.minimum, init.len())?;
+
+ // If traps are disallowed then truncate the size of the
+ // data segment to the minimum size of memory to guarantee
+ // it will fit. Afterwards ensure that the offset of the
+ // data segment is in-bounds by clamping it to the
+ if self.config.disallow_traps {
+ let max_size = (u64::MAX / 64 / 1024).min(mem.minimum) * 64 * 1024;
+ init.truncate(max_size as usize);
+ let max_offset = max_size - init.len() as u64;
+ match &mut offset {
+ Offset::Const32(x) => {
+ *x = (*x as u64).min(max_offset) as i32;
+ }
+ Offset::Const64(x) => {
+ *x = (*x as u64).min(max_offset) as i64;
+ }
+ Offset::Global(_) => unreachable!(),
+ }
+ }
+ DataSegmentKind::Active {
+ offset,
+ memory_index,
+ }
+ };
+ self.data.push(DataSegment { kind, init });
+ Ok(true)
+ },
+ )
+ }
+
+ fn params_results(&self, ty: &BlockType) -> (Vec<ValType>, Vec<ValType>) {
+ match ty {
+ BlockType::Empty => (vec![], vec![]),
+ BlockType::Result(t) => (vec![], vec![*t]),
+ BlockType::FunctionType(ty) => {
+ let ty = self.func_type(*ty);
+ (ty.params.to_vec(), ty.results.to_vec())
+ }
+ }
+ }
+}
+
+pub(crate) fn arbitrary_limits32(
+ u: &mut Unstructured,
+ min_minimum: Option<u32>,
+ max_minimum: u32,
+ max_required: bool,
+ max_inbounds: u32,
+) -> Result<(u32, Option<u32>)> {
+ let (min, max) = arbitrary_limits64(
+ u,
+ min_minimum.map(Into::into),
+ max_minimum.into(),
+ max_required,
+ max_inbounds.into(),
+ )?;
+ Ok((
+ u32::try_from(min).unwrap(),
+ max.map(|i| u32::try_from(i).unwrap()),
+ ))
+}
+
+pub(crate) fn arbitrary_limits64(
+ u: &mut Unstructured,
+ min_minimum: Option<u64>,
+ max_minimum: u64,
+ max_required: bool,
+ max_inbounds: u64,
+) -> Result<(u64, Option<u64>)> {
+ let min = gradually_grow(u, min_minimum.unwrap_or(0), max_inbounds, max_minimum)?;
+ let max = if max_required || u.arbitrary().unwrap_or(false) {
+ Some(u.int_in_range(min..=max_minimum)?)
+ } else {
+ None
+ };
+ Ok((min, max))
+}
+
+pub(crate) fn configured_valtypes(config: &Config) -> Vec<ValType> {
+ let mut valtypes = Vec::with_capacity(25);
+ valtypes.push(ValType::I32);
+ valtypes.push(ValType::I64);
+ valtypes.push(ValType::F32);
+ valtypes.push(ValType::F64);
+ if config.simd_enabled {
+ valtypes.push(ValType::V128);
+ }
+ if config.gc_enabled {
+ for nullable in [
+ // TODO: For now, only create allow nullable reference
+ // types. Eventually we should support non-nullable reference types,
+ // but this means that we will also need to recognize when it is
+ // impossible to create an instance of the reference (eg `(ref
+ // nofunc)` has no instances, and self-referential types that
+ // contain a non-null self-reference are also impossible to create).
+ true,
+ ] {
+ for heap_type in [
+ HeapType::Any,
+ HeapType::Eq,
+ HeapType::I31,
+ HeapType::Array,
+ HeapType::Struct,
+ HeapType::None,
+ HeapType::Func,
+ HeapType::NoFunc,
+ HeapType::Extern,
+ HeapType::NoExtern,
+ ] {
+ valtypes.push(ValType::Ref(RefType {
+ nullable,
+ heap_type,
+ }));
+ }
+ }
+ } else if config.reference_types_enabled {
+ valtypes.push(ValType::EXTERNREF);
+ valtypes.push(ValType::FUNCREF);
+ }
+ valtypes
+}
+
+pub(crate) fn arbitrary_func_type(
+ u: &mut Unstructured,
+ config: &Config,
+ valtypes: &[ValType],
+ max_results: Option<usize>,
+ type_ref_limit: u32,
+) -> Result<Rc<FuncType>> {
+ let mut params = vec![];
+ let mut results = vec![];
+ arbitrary_loop(u, 0, 20, |u| {
+ params.push(arbitrary_valtype(u, config, valtypes, type_ref_limit)?);
+ Ok(true)
+ })?;
+ arbitrary_loop(u, 0, max_results.unwrap_or(20), |u| {
+ results.push(arbitrary_valtype(u, config, valtypes, type_ref_limit)?);
+ Ok(true)
+ })?;
+ Ok(Rc::new(FuncType { params, results }))
+}
+
+fn arbitrary_valtype(
+ u: &mut Unstructured,
+ config: &Config,
+ valtypes: &[ValType],
+ type_ref_limit: u32,
+) -> Result<ValType> {
+ if config.gc_enabled && type_ref_limit > 0 && u.ratio(1, 20)? {
+ Ok(ValType::Ref(RefType {
+ // TODO: For now, only create allow nullable reference
+ // types. Eventually we should support non-nullable reference types,
+ // but this means that we will also need to recognize when it is
+ // impossible to create an instance of the reference (eg `(ref
+ // nofunc)` has no instances, and self-referential types that
+ // contain a non-null self-reference are also impossible to create).
+ nullable: true,
+ heap_type: HeapType::Concrete(u.int_in_range(0..=type_ref_limit - 1)?),
+ }))
+ } else {
+ Ok(*u.choose(valtypes)?)
+ }
+}
+
+pub(crate) fn arbitrary_table_type(u: &mut Unstructured, config: &Config) -> Result<TableType> {
+ // We don't want to generate tables that are too large on average, so
+ // keep the "inbounds" limit here a bit smaller.
+ let max_inbounds = 10_000;
+ let min_elements = if config.disallow_traps { Some(1) } else { None };
+ let max_elements = min_elements.unwrap_or(0).max(config.max_table_elements);
+ let (minimum, maximum) = arbitrary_limits32(
+ u,
+ min_elements,
+ max_elements,
+ config.table_max_size_required,
+ max_inbounds.min(max_elements),
+ )?;
+ if config.disallow_traps {
+ assert!(minimum > 0);
+ }
+ Ok(TableType {
+ element_type: if config.reference_types_enabled {
+ *u.choose(&[RefType::FUNCREF, RefType::EXTERNREF])?
+ } else {
+ RefType::FUNCREF
+ },
+ minimum,
+ maximum,
+ })
+}
+
+pub(crate) fn arbitrary_memtype(u: &mut Unstructured, config: &Config) -> Result<MemoryType> {
+ // When threads are enabled, we only want to generate shared memories about
+ // 25% of the time.
+ let shared = config.threads_enabled && u.ratio(1, 4)?;
+ // We want to favor memories <= 1gb in size, allocate at most 16k pages,
+ // depending on the maximum number of memories.
+ let memory64 = config.memory64_enabled && u.arbitrary()?;
+ let max_inbounds = 16 * 1024 / u64::try_from(config.max_memories).unwrap();
+ let min_pages = if config.disallow_traps { Some(1) } else { None };
+ let max_pages = min_pages.unwrap_or(0).max(if memory64 {
+ config.max_memory64_pages
+ } else {
+ config.max_memory32_pages
+ });
+ let (minimum, maximum) = arbitrary_limits64(
+ u,
+ min_pages,
+ max_pages,
+ config.memory_max_size_required || shared,
+ max_inbounds.min(max_pages),
+ )?;
+ Ok(MemoryType {
+ minimum,
+ maximum,
+ memory64,
+ shared,
+ })
+}
+
+pub(crate) fn arbitrary_tag_type(
+ u: &mut Unstructured,
+ candidate_func_types: &[u32],
+ get_func_type: impl FnOnce(u32) -> Rc<FuncType>,
+) -> Result<TagType> {
+ let max = candidate_func_types.len() - 1;
+ let ty = candidate_func_types[u.int_in_range(0..=max)?];
+ Ok(TagType {
+ func_type_idx: ty,
+ func_type: get_func_type(ty),
+ })
+}
+
+/// This function generates a number between `min` and `max`, favoring values
+/// between `min` and `max_inbounds`.
+///
+/// The thinking behind this function is that it's used for things like offsets
+/// and minimum sizes which, when very large, can trivially make the wasm oom or
+/// abort with a trap. This isn't the most interesting thing to do so it tries
+/// to favor numbers in the `min..max_inbounds` range to avoid immediate ooms.
+fn gradually_grow(u: &mut Unstructured, min: u64, max_inbounds: u64, max: u64) -> Result<u64> {
+ if min == max {
+ return Ok(min);
+ }
+ let min = min as f64;
+ let max = max as f64;
+ let max_inbounds = max_inbounds as f64;
+ let x = u.arbitrary::<u32>()?;
+ let x = f64::from(x);
+ let x = map_custom(
+ x,
+ f64::from(u32::MIN)..f64::from(u32::MAX),
+ min..max_inbounds,
+ min..max,
+ );
+ return Ok(x.round() as u64);
+
+ /// Map a value from within the input range to the output range(s).
+ ///
+ /// This will first map the input range into the `0..1` input range, and
+ /// then depending on the value it will either map it exponentially
+ /// (favoring small values) into the `output_inbounds` range or it will map
+ /// it into the `output` range.
+ fn map_custom(
+ value: f64,
+ input: Range<f64>,
+ output_inbounds: Range<f64>,
+ output: Range<f64>,
+ ) -> f64 {
+ assert!(!value.is_nan(), "{}", value);
+ assert!(value.is_finite(), "{}", value);
+ assert!(input.start < input.end, "{} < {}", input.start, input.end);
+ assert!(
+ output.start < output.end,
+ "{} < {}",
+ output.start,
+ output.end
+ );
+ assert!(value >= input.start, "{} >= {}", value, input.start);
+ assert!(value <= input.end, "{} <= {}", value, input.end);
+ assert!(
+ output.start <= output_inbounds.start,
+ "{} <= {}",
+ output.start,
+ output_inbounds.start
+ );
+ assert!(
+ output_inbounds.end <= output.end,
+ "{} <= {}",
+ output_inbounds.end,
+ output.end
+ );
+
+ let x = map_linear(value, input, 0.0..1.0);
+ let result = if x < PCT_INBOUNDS {
+ if output_inbounds.start == output_inbounds.end {
+ output_inbounds.start
+ } else {
+ let unscaled = x * x * x * x * x * x;
+ map_linear(unscaled, 0.0..1.0, output_inbounds)
+ }
+ } else {
+ map_linear(x, 0.0..1.0, output.clone())
+ };
+
+ assert!(result >= output.start, "{} >= {}", result, output.start);
+ assert!(result <= output.end, "{} <= {}", result, output.end);
+ result
+ }
+
+ /// Map a value from within the input range linearly to the output range.
+ ///
+ /// For example, mapping `0.5` from the input range `0.0..1.0` to the output
+ /// range `1.0..3.0` produces `2.0`.
+ fn map_linear(
+ value: f64,
+ Range {
+ start: in_low,
+ end: in_high,
+ }: Range<f64>,
+ Range {
+ start: out_low,
+ end: out_high,
+ }: Range<f64>,
+ ) -> f64 {
+ assert!(!value.is_nan(), "{}", value);
+ assert!(value.is_finite(), "{}", value);
+ assert!(in_low < in_high, "{} < {}", in_low, in_high);
+ assert!(out_low < out_high, "{} < {}", out_low, out_high);
+ assert!(value >= in_low, "{} >= {}", value, in_low);
+ assert!(value <= in_high, "{} <= {}", value, in_high);
+
+ let dividend = out_high - out_low;
+ let divisor = in_high - in_low;
+ let slope = dividend / divisor;
+ let result = out_low + (slope * (value - in_low));
+
+ assert!(result >= out_low, "{} >= {}", result, out_low);
+ assert!(result <= out_high, "{} <= {}", result, out_high);
+ result
+ }
+}
+
+/// Selects a reasonable offset for an element or data segment. This favors
+/// having the segment being in-bounds, but it may still generate
+/// any offset.
+fn arbitrary_offset(
+ u: &mut Unstructured,
+ limit_min: u64,
+ limit_max: u64,
+ segment_size: usize,
+) -> Result<u64> {
+ let size = u64::try_from(segment_size).unwrap();
+
+ // If the segment is too big for the whole memory, just give it any
+ // offset.
+ if size > limit_min {
+ u.int_in_range(0..=limit_max)
+ } else {
+ gradually_grow(u, 0, limit_min - size, limit_max)
+ }
+}
+
+fn unique_import_strings(max_size: usize, u: &mut Unstructured) -> Result<(String, String)> {
+ let module = limited_string(max_size, u)?;
+ let field = limited_string(max_size, u)?;
+ Ok((module, field))
+}
+
+fn arbitrary_vec_u8(u: &mut Unstructured) -> Result<Vec<u8>> {
+ let size = u.arbitrary_len::<u8>()?;
+ Ok(u.bytes(size)?.to_vec())
+}
+
+impl EntityType {
+ fn size(&self) -> u32 {
+ match self {
+ EntityType::Tag(_)
+ | EntityType::Global(_)
+ | EntityType::Table(_)
+ | EntityType::Memory(_) => 1,
+ EntityType::Func(_, ty) => 1 + (ty.params.len() + ty.results.len()) as u32,
+ }
+ }
+}
+
+// A helper structure used when generating module/instance types to limit the
+// amount of each kind of import created.
+#[derive(Default, Clone, Copy, PartialEq)]
+struct Entities {
+ globals: usize,
+ memories: usize,
+ tables: usize,
+ funcs: usize,
+ tags: usize,
+}
+
+/// A container for the kinds of instructions that wasm-smith is allowed to
+/// emit.
+///
+/// # Example
+///
+/// ```
+/// # use wasm_smith::{InstructionKinds, InstructionKind};
+/// let kinds = InstructionKinds::new(&[InstructionKind::Numeric, InstructionKind::Memory]);
+/// assert!(kinds.contains(InstructionKind::Memory));
+/// ```
+#[derive(Clone, Copy, Debug, Default)]
+#[cfg_attr(feature = "serde_derive", derive(serde_derive::Deserialize))]
+pub struct InstructionKinds(pub(crate) FlagSet<InstructionKind>);
+
+impl InstructionKinds {
+ /// Create a new container.
+ pub fn new(kinds: &[InstructionKind]) -> Self {
+ Self(kinds.iter().fold(FlagSet::default(), |ks, k| ks | *k))
+ }
+
+ /// Include all [InstructionKind]s.
+ pub fn all() -> Self {
+ Self(FlagSet::full())
+ }
+
+ /// Include no [InstructionKind]s.
+ pub fn none() -> Self {
+ Self(FlagSet::default())
+ }
+
+ /// Check if the [InstructionKind] is contained in this set.
+ #[inline]
+ pub fn contains(&self, kind: InstructionKind) -> bool {
+ self.0.contains(kind)
+ }
+}
+
+flags! {
+ /// Enumerate the categories of instructions defined in the [WebAssembly
+ /// specification](https://webassembly.github.io/spec/core/syntax/instructions.html).
+ #[allow(missing_docs)]
+ #[cfg_attr(feature = "_internal_cli", derive(serde_derive::Deserialize))]
+ pub enum InstructionKind: u16 {
+ Numeric,
+ Vector,
+ Reference,
+ Parametric,
+ Variable,
+ Table,
+ Memory,
+ Control,
+ Aggregate,
+ }
+}
+
+impl FromStr for InstructionKinds {
+ type Err = String;
+ fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
+ let mut kinds = vec![];
+ for part in s.split(",") {
+ let kind = InstructionKind::from_str(part)?;
+ kinds.push(kind);
+ }
+ Ok(InstructionKinds::new(&kinds))
+ }
+}
+
+impl FromStr for InstructionKind {
+ type Err = String;
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ match s.to_lowercase().as_str() {
+ "numeric" => Ok(InstructionKind::Numeric),
+ "vector" => Ok(InstructionKind::Vector),
+ "reference" => Ok(InstructionKind::Reference),
+ "parametric" => Ok(InstructionKind::Parametric),
+ "variable" => Ok(InstructionKind::Variable),
+ "table" => Ok(InstructionKind::Table),
+ "memory" => Ok(InstructionKind::Memory),
+ "control" => Ok(InstructionKind::Control),
+ _ => Err(format!("unknown instruction kind: {}", s)),
+ }
+ }
+}