// FIXME(#1000): component support in `wasm-smith` is a work in progress. #![allow(unused_variables, dead_code)]
usecrate::{arbitrary_loop, Config}; use arbitrary::{Arbitrary, Result, Unstructured}; use std::collections::BTreeMap; use std::{
collections::{HashMap, HashSet},
rc::Rc,
}; use wasm_encoder::{
ComponentTypeRef, ComponentValType, HeapType, PrimitiveValType, RefType, TypeBounds, ValType,
};
mod encode;
/// A pseudo-random WebAssembly [component]. /// /// Construct instances of this type with [the `Arbitrary` /// trait](https://docs.rs/arbitrary/*/arbitrary/trait.Arbitrary.html). /// /// [component]: https://github.com/WebAssembly/component-model/blob/ast-and-binary/design/MVP/Explainer.md /// /// ## Configured Generated Components /// /// The `Arbitrary` implementation uses the [`Config::default()`][crate::Config] /// configuration. If you want to customize the shape of generated components, /// create your own [`Config`][crate::Config] instance and pass it to /// [`Component::new`][crate::Component::new]. #[derive(Debug)] pubstruct Component {
sections: Vec<Section>,
}
/// A builder to create a component (and possibly a whole tree of nested /// components). /// /// Maintains a stack of components we are currently building, as well as /// metadata about them. The split between `Component` and `ComponentBuilder` is /// that the builder contains metadata that is purely used when generating /// components and is unnecessary after we are done generating the structure of /// the components and only need to encode an already-generated component to /// bytes. #[derive(Debug)] struct ComponentBuilder {
config: Config,
// The set of core `valtype`s that we are configured to generate.
core_valtypes: Vec<ValType>,
// Stack of types scopes that are currently available. // // There is an entry in this stack for each component, but there can also be // additional entries for module/component/instance types, each of which // have their own scope. // // This stack is always non-empty and the last entry is always the current // scope. // // When a particular scope can alias outer types, it can alias from any // scope that is older than it (i.e. `types_scope[i]` can alias from // `types_scope[j]` when `j <= i`).
types: Vec<TypesScope>,
// The set of components we are currently building and their associated // metadata.
components: Vec<ComponentContext>,
// Whether we are in the final bits of generating this component and we just // need to ensure that the minimum number of entities configured have all // been generated. This changes the behavior of various // `arbitrary_<section>` methods to always fill in their minimums.
fill_minimums: bool,
// Our maximums for these entities are applied across the whole component // tree, not per-component.
total_components: usize,
total_modules: usize,
total_instances: usize,
total_values: usize,
}
/// Metadata (e.g. contents of various index spaces) we keep track of on a /// per-component basis. #[derive(Debug)] struct ComponentContext { // The actual component itself.
component: Component,
// The number of imports we have generated thus far.
num_imports: usize,
// The set of names of imports we've generated thus far.
import_names: HashSet<String>,
// The set of URLs of imports we've generated thus far.
import_urls: HashSet<String>,
// This component's function index space.
funcs: Vec<ComponentOrCoreFuncType>,
// Which entries in `funcs` are component functions?
component_funcs: Vec<u32>,
// Which entries in `component_funcs` are component functions that only use scalar // types?
scalar_component_funcs: Vec<u32>,
// Which entries in `funcs` are core Wasm functions? // // Note that a component can't import core functions, so these entries will // never point to a `Section::Import`.
core_funcs: Vec<u32>,
// This component's component index space. // // An indirect list of all directly-nested (not transitive) components // inside this component. // // Each entry is of the form `(i, j)` where `component.sections[i]` is // guaranteed to be either // // * a `Section::Component` and we are referencing the component defined in // that section (in this case `j` must also be `0`, since a component // section can only contain a single nested component), or // // * a `Section::Import` and we are referencing the `j`th import in that // section, which is guaranteed to be a component import.
components: Vec<(usize, usize)>,
// This component's module index space. // // An indirect list of all directly-nested (not transitive) modules // inside this component. // // Each entry is of the form `(i, j)` where `component.sections[i]` is // guaranteed to be either // // * a `Section::Core` and we are referencing the module defined in that // section (in this case `j` must also be `0`, since a core section can // only contain a single nested module), or // // * a `Section::Import` and we are referencing the `j`th import in that // section, which is guaranteed to be a module import.
modules: Vec<(usize, usize)>,
// This component's instance index space.
instances: Vec<ComponentOrCoreInstanceType>,
// This component's value index space.
values: Vec<ComponentValType>,
}
impl Step { fn unwrap_still_building(self) { matchself {
Step::Finished(_) => panic!( "`Step::unwrap_still_building` called on a `Step` that is not `StillBuilding`"
),
Step::StillBuilding => {}
}
}
}
// Only add any choice other than "finish what we've generated thus // far" when there is more arbitrary fuzzer data for us to consume. if !u.is_empty() {
choices.push(Self::arbitrary_custom_section);
// NB: we add each section as a choice even if we've already // generated our maximum number of entities in that section so that // we can exercise adding empty sections to the end of the module.
choices.push(Self::arbitrary_core_type_section);
choices.push(Self::arbitrary_type_section);
choices.push(Self::arbitrary_import_section);
choices.push(Self::arbitrary_canonical_section);
let f = u.choose(&choices)?; match f(self, u)? {
Step::StillBuilding => {}
Step::Finished(component) => { ifself.components.is_empty() { // If we just finished the root component, then return it. return Ok(component);
} else { // Otherwise, add it as a nested component in the parent. self.push_section(Section::Component(component));
}
}
}
}
}
self.types
.pop()
.expect("should have a types scope for the component we are finishing");
Ok(Step::Finished(self.components.pop().unwrap().component))
}
// Types cannot be imported currently if !for_import
&& !scope.types.is_empty()
&& (for_type_def || scope.types.len() < self.config.max_types)
{
choices.push(|me, u| {
Ok(ComponentTypeRef::Type(TypeBounds::Eq(u.int_in_range( 0..=u32::try_from(me.current_type_scope().types.len() - 1).unwrap(),
)?)))
});
}
// TODO: wasm-smith needs to ensure that every arbitrary value gets used exactly once. // until that time, don't import values // if for_type_def || !for_import || self.total_values < self.config.max_values() { // choices.push(|me, u| Ok(ComponentTypeRef::Value(me.arbitrary_component_val_type(u)?))); // }
// Special case the canonical ABI functions since certain types can only // be passed across the component boundary if they exist and // randomly generating them is extremely unlikely.
let max_choice = if types.len() < self.config.max_types { // Check if the parent scope has core function types to alias if !types.is_empty()
|| (!self.types.is_empty()
&& !self.types.last().unwrap().core_func_types.is_empty())
{ // Imports, exports, types, and aliases 3
} else { // Imports, exports, and types 2
}
} else { // Imports and exports 1
};
match u.int_in_range::<u8>(0..=max_choice)? { // Import. 0 => { let module = crate::limited_string(100, u)?; let existing_module_imports = imports.entry(module.clone()).or_default(); let field = crate::unique_string(100, existing_module_imports, u)?; let entity_type = matchself.arbitrary_core_entity_type(
u,
&types,
&mut entity_choices,
&mut counts,
)? {
None => return Ok(false),
Some(x) => x,
};
defs.push(ModuleTypeDef::Import(crate::core::Import {
module,
field,
entity_type,
}));
}
// Export. 1 => { let name = crate::unique_string(100, &mut exports, u)?; let entity_ty = matchself.arbitrary_core_entity_type(
u,
&types,
&mut entity_choices,
&mut counts,
)? {
None => return Ok(false),
Some(x) => x,
};
defs.push(ModuleTypeDef::Export(name, entity_ty));
}
// Type definition. 2 => { let ty = arbitrary_func_type(
u,
&self.config,
&self.core_valtypes, ifself.config.multi_value_enabled {
None
} else {
Some(1)
}, 0,
)?;
types.push(ty.clone());
defs.push(ModuleTypeDef::TypeDef( crate::core::CompositeType::new_func(ty, false),
)); // TODO: handle shared
}
// Alias 3 => { let (count, index, kind) = self.arbitrary_outer_core_type_alias(u, &types)?; let ty = match &kind {
CoreOuterAliasKind::Type(ty) => ty.clone(),
};
types.push(ty);
defs.push(ModuleTypeDef::OuterAlias {
count,
i: index,
kind,
});
}
// Export. ifself.current_type_scope().can_ref_type() {
choices.push(|me, exports, export_urls, u, _type_fuel| { let ty = me.arbitrary_type_ref(u, false, true)?.unwrap(); iflet ComponentTypeRef::Type(TypeBounds::Eq(idx)) = ty { let ty = me.current_type_scope().get(idx).clone();
me.current_type_scope_mut().push(ty);
}
Ok(InstanceTypeDecl::Export {
name: crate::unique_kebab_string(100, exports, u)?,
url: if u.arbitrary()? {
Some(crate::unique_url(100, export_urls, u)?)
} else {
None
},
ty,
})
});
}
// Outer type alias. ifself
.types
.iter()
.any(|scope| !scope.types.is_empty() || !scope.core_types.is_empty())
{
choices.push(|me, _exports, _export_urls, u, _type_fuel| { let alias = me.arbitrary_outer_type_alias(u)?; match &alias {
Alias::Outer {
kind: OuterAliasKind::Type(ty),
..
} => me.current_type_scope_mut().push(ty.clone()),
Alias::Outer {
kind: OuterAliasKind::CoreType(ty),
..
} => me.current_type_scope_mut().push_core(ty.clone()),
_ => unreachable!(),
};
Ok(InstanceTypeDecl::Alias(alias))
});
}
// Core type definition.
choices.push(|me, _exports, _export_urls, u, type_fuel| { let ty = me.arbitrary_core_type(u, type_fuel)?;
me.current_type_scope_mut().push_core(ty.clone());
Ok(InstanceTypeDecl::CoreType(ty))
});
// Type definition. ifself.types.len() < self.config.max_nesting_depth {
choices.push(|me, _exports, _export_urls, u, type_fuel| { let ty = me.arbitrary_type(u, type_fuel)?;
me.current_type_scope_mut().push(ty.clone());
Ok(InstanceTypeDecl::Type(ty))
});
}
let f = u.choose(&choices)?;
f(self, exports, export_urls, u, type_fuel)
}
let max = enclosing_type_len + local_types.len() - 1; let i = u.int_in_range(0..=max)?; let (count, index, ty) = if i < enclosing_type_len { let enclosing = self.types.last().unwrap(); let index = enclosing.core_func_types[i];
( 1,
index, match enclosing.get_core(index).as_ref() {
CoreType::Func(ty) => ty.clone(),
CoreType::Module(_) => unreachable!(),
},
)
} elseif i - enclosing_type_len < local_types.len() { let i = i - enclosing_type_len;
(0, u32::try_from(i).unwrap(), local_types[i].clone())
} else {
unreachable!()
};
let (count, scope) = u.choose(&non_empty_types_scopes)?; let count = u32::try_from(*count).unwrap();
assert!(!scope.types.is_empty() || !scope.core_types.is_empty());
let max_type_in_scope = scope.types.len() + scope.core_types.len() - 1; let i = u.int_in_range(0..=max_type_in_scope)?;
let (i, kind) = if i < scope.types.len() { let i = u32::try_from(i).unwrap();
(i, OuterAliasKind::Type(Rc::clone(scope.get(i))))
} elseif i - scope.types.len() < scope.core_types.len() { let i = u32::try_from(i - scope.types.len()).unwrap();
(i, OuterAliasKind::CoreType(Rc::clone(scope.get_core(i))))
} else {
unreachable!()
};
// Note: parameters are currently limited to a maximum of 16 // because any additional parameters will require indirect access // via a pointer argument; when this occurs, validation of any // lowered function will fail because it will be missing a // memory option (not yet implemented). // // When options are correctly specified on canonical functions, // we should increase this maximum to test indirect parameter // passing.
arbitrary_loop(u, 0, 16, |u| {
*type_fuel = type_fuel.saturating_sub(1); if *type_fuel == 0 { return Ok(false);
}
let name = crate::unique_kebab_string(100, &mut names, u)?; let ty = self.arbitrary_component_val_type(u)?;
params.push((name, ty));
Ok(true)
})?;
names.clear();
// Likewise, the limit for results is 1 before the memory option is // required. When the memory option is implemented, this restriction // should be relaxed.
arbitrary_loop(u, 0, 1, |u| {
*type_fuel = type_fuel.saturating_sub(1); if *type_fuel == 0 { return Ok(false);
}
// If the result list is empty (i.e. first push), then arbitrarily give // the result a name. Otherwise, all of the subsequent items must be named. let name = if results.is_empty() { // Most of the time we should have a single, unnamed result.
u.ratio::<u8>(10, 100)?
.then(|| crate::unique_kebab_string(100, &mut names, u))
.transpose()?
} else {
Some(crate::unique_kebab_string(100, &mut names, u)?)
};
let ty = self.arbitrary_component_val_type(u)?;
results.push((name, ty));
// There can be only one unnamed result. if results.len() == 1 && results[0].0.is_none() { return Ok(false);
}
Ok(true)
})?;
Ok(Rc::new(FuncType { params, results }))
}
fn arbitrary_component_val_type(&self, u: &mut Unstructured) -> Result<ComponentValType> { let max_choices = ifself.current_type_scope().defined_types.is_empty() { 0
} else { 1
}; match u.int_in_range(0..=max_choices)? { 0 => Ok(ComponentValType::Primitive( self.arbitrary_primitive_val_type(u)?,
)), 1 => { let index = *u.choose(&self.current_type_scope().defined_types)?; let ty = Rc::clone(self.current_type_scope().get(index));
Ok(ComponentValType::Type(index))
}
_ => unreachable!(),
}
}
match ty {
ComponentTypeRef::Module(_) => { self.total_modules += 1; self.component_mut().modules.push((section_index, nth));
}
ComponentTypeRef::Func(ty_index) => { let func_ty = matchself.current_type_scope().get(ty_index).as_ref() { Type::Func(ty) => ty.clone(),
_ => unreachable!(),
};
if func_ty.is_scalar() { let func_index = u32::try_from(self.component().component_funcs.len()).unwrap(); self.component_mut().scalar_component_funcs.push(func_index);
}
let func_index = u32::try_from(self.component().funcs.len()).unwrap(); self.component_mut()
.funcs
.push(ComponentOrCoreFuncType::Component(func_ty));
let min = ifself.fill_minimums { self.config
.min_imports
.saturating_sub(self.component().num_imports)
} else { // Allow generating empty sections. We can always fill in the required // minimum later. 0
}; let max = self.config.max_imports - self.component().num_imports;
crate::arbitrary_loop(u, min, max, |u| { matchself.arbitrary_type_ref(u, true, false)? {
Some(ty) => { let name = crate::unique_kebab_string(100, &mutself.component_mut().import_names, u)?; let url = if u.arbitrary()? {
Some(crate::unique_url( 100,
&mutself.component_mut().import_urls,
u,
)?)
} else {
None
}; self.push_import(name, url, ty);
Ok(true)
}
None => Ok(false),
}
})?;
let min = ifself.fill_minimums { self.config
.min_funcs
.saturating_sub(self.component().funcs.len())
} else { // Allow generating empty sections. We can always fill in the // required minimum later. 0
}; let max = self.config.max_funcs - self.component().funcs.len();
// NB: We only lift/lower scalar component functions. // // If we generated lifting and lowering of compound value types, // the probability of generating a corresponding Wasm module that // generates valid instances of the compound value types would // be vanishingly tiny (e.g. for `list<string>` we would have to // generate a core Wasm module that correctly produces a pointer and // length for a memory region that itself is a series of pointers // and lengths of valid strings, as well as `canonical_abi_realloc` // and `canonical_abi_free` functions that do the right thing). // // This is a pretty serious limitation of `wasm-smith`'s component // types support, but it is one we are intentionally // accepting. `wasm-smith` will focus on generating arbitrary // component sections, structures, and import/export topologies; not // component functions and core Wasm implementations of component // functions. In the future, we intend to build a new, distinct test // case generator specifically for exercising component functions // and the canonical ABI. This new generator won't emit arbitrary // component sections, structures, or import/export topologies, and // will instead leave that to `wasm-smith`.
if !self.component().scalar_component_funcs.is_empty() {
choices.push(|u, c| { let func_index = *u.choose(&c.component().scalar_component_funcs)?;
Ok(Some(Func::CanonLower { // Scalar component functions don't use any canonical options.
options: vec![],
func_index,
}))
});
}
if !self.component().core_funcs.is_empty() {
choices.push(|u, c| { let core_func_index = u.int_in_range( 0..=u32::try_from(c.component().core_funcs.len() - 1).unwrap(),
)?; let core_func_ty = c.core_function_type(core_func_index); let comp_func_ty = inverse_scalar_canonical_abi_for(u, core_func_ty)?;
let func_ty = iflet Some(indices) = c
.current_type_scope()
.func_type_to_indices
.get(&comp_func_ty)
{ // If we've already defined this component function type // one or more times, then choose one of those // definitions arbitrarily.
debug_assert!(!indices.is_empty());
*u.choose(indices)?
} elseif c.current_type_scope().types.len() < c.config.max_types { // If we haven't already defined this component function // type, and we haven't defined the configured maximum // amount of types yet, then just define this type. let ty = Rc::new(Type::Func(Rc::new(comp_func_ty)));
c.push_type(ty)
} else { // Otherwise, give up on lifting this function. return Ok(None);
};
Ok(Some(Func::CanonLift {
func_ty, // Scalar functions don't use any canonical options.
options: vec![],
core_func_index,
}))
});
}
if choices.is_empty() { return Ok(false);
}
let f = u.choose(&choices)?; iflet Some(func) = f(u, self)? { self.push_func(func);
}
impl<'a> Arbitrary<'a> for CustomSection { fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { let name = crate::limited_string(1_000, u)?; let data = u.arbitrary()?;
Ok(CustomSection { name, data })
}
}
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)?)
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.29 Sekunden
(vorverarbeitet am 2026-06-18)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.