Path: blob/main/crates/fuzzing/src/oracles/component_api.rs
3061 views
//! This module generates test cases for the Wasmtime component model function APIs,1//! e.g. `wasmtime::component::func::Func` and `TypedFunc`.2//!3//! Each case includes a list of arbitrary interface types to use as parameters, plus another one to use as a4//! result, and a component which exports a function and imports a function. The exported function forwards its5//! parameters to the imported one and forwards the result back to the caller. This serves to exercise Wasmtime's6//! lifting and lowering code and verify the values remain intact during both processes.78use crate::block_on;9use crate::generators::{self, CompilerStrategy, InstanceAllocationStrategy};10use crate::oracles::log_wasm;11use arbitrary::{Arbitrary, Unstructured};12use std::any::Any;13use std::fmt::Debug;14use std::ops::ControlFlow;15use wasmtime::component::{16self, Accessor, Component, ComponentNamedList, Lift, Linker, Lower, Val,17};18use wasmtime::{AsContextMut, Enabled, Engine, Result, Store, StoreContextMut};19use wasmtime_test_util::component_fuzz::{20Declarations, EXPORT_FUNCTION, IMPORT_FUNCTION, MAX_TYPE_DEPTH, TestCase, Type,21};2223/// Minimum length of an arbitrary list value generated for a test case24const MIN_LIST_LENGTH: u32 = 0;2526/// Maximum length of an arbitrary list value generated for a test case27const MAX_LIST_LENGTH: u32 = 10;2829/// Maximum number of invocations of one fuzz case.30const MAX_ITERS: usize = 1_000;3132/// Generate an arbitrary instance of the specified type.33fn arbitrary_val(ty: &component::Type, input: &mut Unstructured) -> arbitrary::Result<Val> {34use component::Type;3536Ok(match ty {37Type::Bool => Val::Bool(input.arbitrary()?),38Type::S8 => Val::S8(input.arbitrary()?),39Type::U8 => Val::U8(input.arbitrary()?),40Type::S16 => Val::S16(input.arbitrary()?),41Type::U16 => Val::U16(input.arbitrary()?),42Type::S32 => Val::S32(input.arbitrary()?),43Type::U32 => Val::U32(input.arbitrary()?),44Type::S64 => Val::S64(input.arbitrary()?),45Type::U64 => Val::U64(input.arbitrary()?),46Type::Float32 => Val::Float32(input.arbitrary()?),47Type::Float64 => Val::Float64(input.arbitrary()?),48Type::Char => Val::Char(input.arbitrary()?),49Type::String => Val::String(input.arbitrary()?),50Type::List(list) => {51let mut values = Vec::new();52input.arbitrary_loop(Some(MIN_LIST_LENGTH), Some(MAX_LIST_LENGTH), |input| {53values.push(arbitrary_val(&list.ty(), input)?);5455Ok(ControlFlow::Continue(()))56})?;5758Val::List(values)59}60Type::Record(record) => Val::Record(61record62.fields()63.map(|field| Ok((field.name.to_string(), arbitrary_val(&field.ty, input)?)))64.collect::<arbitrary::Result<_>>()?,65),66Type::Tuple(tuple) => Val::Tuple(67tuple68.types()69.map(|ty| arbitrary_val(&ty, input))70.collect::<arbitrary::Result<_>>()?,71),72Type::Variant(variant) => {73let cases = variant.cases().collect::<Vec<_>>();74let case = input.choose(&cases)?;75let payload = match &case.ty {76Some(ty) => Some(Box::new(arbitrary_val(ty, input)?)),77None => None,78};79Val::Variant(case.name.to_string(), payload)80}81Type::Enum(en) => {82let names = en.names().collect::<Vec<_>>();83let name = input.choose(&names)?;84Val::Enum(name.to_string())85}86Type::Option(option) => {87let discriminant = input.int_in_range(0..=1)?;88Val::Option(match discriminant {890 => None,901 => Some(Box::new(arbitrary_val(&option.ty(), input)?)),91_ => unreachable!(),92})93}94Type::Result(result) => {95let discriminant = input.int_in_range(0..=1)?;96Val::Result(match discriminant {970 => Ok(match result.ok() {98Some(ty) => Some(Box::new(arbitrary_val(&ty, input)?)),99None => None,100}),1011 => Err(match result.err() {102Some(ty) => Some(Box::new(arbitrary_val(&ty, input)?)),103None => None,104}),105_ => unreachable!(),106})107}108Type::Flags(flags) => Val::Flags(109flags110.names()111.filter_map(|name| {112input113.arbitrary()114.map(|p| if p { Some(name.to_string()) } else { None })115.transpose()116})117.collect::<arbitrary::Result<_>>()?,118),119120// Resources, futures, streams, and error contexts aren't fuzzed at this time.121Type::Own(_) | Type::Borrow(_) | Type::Future(_) | Type::Stream(_) | Type::ErrorContext => {122unreachable!()123}124})125}126127fn store<T>(input: &mut Unstructured<'_>, val: T) -> arbitrary::Result<Store<T>> {128crate::init_fuzzing();129130let mut config = input.arbitrary::<generators::Config>()?;131config.enable_async(input)?;132config.module_config.config.multi_value_enabled = true;133config.module_config.config.reference_types_enabled = true;134config.module_config.config.max_memories = 2;135config.module_config.component_model_async = true;136config.module_config.component_model_async_stackful = true;137if config.wasmtime.compiler_strategy == CompilerStrategy::Winch {138config.wasmtime.compiler_strategy = CompilerStrategy::CraneliftNative;139}140141fn set_min<T>(a: &mut T, min: T)142where143T: Ord + Copy,144{145if *a < min {146*a = min;147}148}149150fn set_opt_min<T>(a: &mut Option<T>, min: T)151where152T: Ord + Copy,153{154if let Some(a) = a {155set_min(a, min);156}157}158159if let InstanceAllocationStrategy::Pooling(p) = &mut config.wasmtime.strategy {160set_min(&mut p.total_component_instances, 5);161set_min(&mut p.total_core_instances, 5);162set_min(&mut p.total_memories, 2);163set_min(&mut p.total_stacks, 4);164set_min(&mut p.max_memories_per_component, 2);165set_min(&mut p.max_memories_per_module, 2);166set_min(&mut p.component_instance_size, 64 << 10);167set_min(&mut p.core_instance_size, 64 << 10);168p.memory_protection_keys = Enabled::No;169p.max_memory_size = 10 << 20; // 10 MiB170}171set_opt_min(172&mut config.wasmtime.memory_config.memory_reservation,17310 << 20,174);175176let engine = Engine::new(177config178.to_wasmtime()179.debug_adapter_modules(input.arbitrary()?),180)181.unwrap();182let mut store = Store::new(&engine, val);183config.configure_store_epoch_and_fuel(&mut store);184Ok(store)185}186187/// Generate zero or more sets of arbitrary argument and result values and execute the test using those188/// values, asserting that they flow from host-to-guest and guest-to-host unchanged.189pub fn static_api_test<'a, P, R>(190input: &mut Unstructured<'a>,191declarations: &Declarations,192) -> arbitrary::Result<()>193where194P: ComponentNamedList195+ Lift196+ Lower197+ Clone198+ PartialEq199+ Debug200+ Arbitrary<'a>201+ Send202+ Sync203+ 'static,204R: ComponentNamedList205+ Lift206+ Lower207+ Clone208+ PartialEq209+ Debug210+ Arbitrary<'a>211+ Send212+ Sync213+ 'static,214{215crate::init_fuzzing();216217let mut store = store::<Box<dyn Any + Send>>(input, Box::new(()))?;218let engine = store.engine();219let wat = declarations.make_component();220let wat = wat.as_bytes();221crate::oracles::log_wasm(wat);222let component = Component::new(&engine, wat).unwrap();223let mut linker = Linker::new(&engine);224225fn host_function<P, R>(226cx: StoreContextMut<'_, Box<dyn Any + Send>>,227params: P,228) -> wasmtime::Result<R>229where230P: Debug + PartialEq + 'static,231R: Debug + Clone + 'static,232{233log::trace!("received parameters {params:?}");234let data: &(P, R) = cx.data().downcast_ref().unwrap();235let (expected_params, result) = data;236assert_eq!(params, *expected_params);237log::trace!("returning result {result:?}");238Ok(result.clone())239}240241if declarations.options.host_async {242linker243.root()244.func_wrap_concurrent(IMPORT_FUNCTION, |a, params| {245Box::pin(async move {246a.with(|mut cx| host_function::<P, R>(cx.as_context_mut(), params))247})248})249.unwrap();250} else {251linker252.root()253.func_wrap(IMPORT_FUNCTION, |cx, params| {254host_function::<P, R>(cx, params)255})256.unwrap();257}258259block_on(async {260let instance = linker261.instantiate_async(&mut store, &component)262.await263.unwrap();264let func = instance265.get_typed_func::<P, R>(&mut store, EXPORT_FUNCTION)266.unwrap();267268let mut iters = 0..MAX_ITERS;269while iters.next().is_some() && input.arbitrary()? {270let params = input.arbitrary::<P>()?;271let result = input.arbitrary::<R>()?;272*store.data_mut() = Box::new((params.clone(), result.clone()));273log::trace!("passing in parameters {params:?}");274let actual = if declarations.options.guest_caller_async {275store276.run_concurrent(async |a| func.call_concurrent(a, params).await.unwrap().0)277.await278.unwrap()279} else {280func.call_async(&mut store, params).await.unwrap()281};282log::trace!("got result {actual:?}");283assert_eq!(actual, result);284}285286Ok(())287})288}289290/// Generate and execute a `crate::generators::component_types::TestCase` using the specified `input` to create291/// arbitrary types and values.292pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()> {293crate::init_fuzzing();294295let mut types = Vec::new();296let mut type_fuel = 500;297298for _ in 0..5 {299types.push(Type::generate(input, MAX_TYPE_DEPTH, &mut type_fuel)?);300}301302let case = TestCase::generate(&types, input)?;303304let mut store = store(input, (Vec::new(), None))?;305let engine = store.engine();306let wat = case.declarations().make_component();307let wat = wat.as_bytes();308log_wasm(wat);309let component = Component::new(&engine, wat).unwrap();310let mut linker = Linker::new(&engine);311312fn host_function(313mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>,314params: &[Val],315results: &mut [Val],316) -> Result<()> {317log::trace!("received params {params:?}");318let (expected_args, expected_results) = cx.data_mut();319assert_eq!(params.len(), expected_args.len());320for (expected, actual) in expected_args.iter().zip(params) {321assert_eq!(expected, actual);322}323results.clone_from_slice(&expected_results.take().unwrap());324log::trace!("returning results {results:?}");325Ok(())326}327328if case.options.host_async {329linker330.root()331.func_new_concurrent(IMPORT_FUNCTION, {332move |cx: &Accessor<_, _>, _, params: &[Val], results: &mut [Val]| {333Box::pin(async move {334cx.with(|mut store| host_function(store.as_context_mut(), params, results))335})336}337})338.unwrap();339} else {340linker341.root()342.func_new(IMPORT_FUNCTION, {343move |cx, _, params, results| host_function(cx, params, results)344})345.unwrap();346}347348block_on(async {349let instance = linker350.instantiate_async(&mut store, &component)351.await352.unwrap();353let func = instance.get_func(&mut store, EXPORT_FUNCTION).unwrap();354let ty = func.ty(&store);355356let mut iters = 0..MAX_ITERS;357while iters.next().is_some() && input.arbitrary()? {358let params = ty359.params()360.map(|(_, ty)| arbitrary_val(&ty, input))361.collect::<arbitrary::Result<Vec<_>>>()?;362let results = ty363.results()364.map(|ty| arbitrary_val(&ty, input))365.collect::<arbitrary::Result<Vec<_>>>()?;366367*store.data_mut() = (params.clone(), Some(results.clone()));368369log::trace!("passing params {params:?}");370let mut actual = vec![Val::Bool(false); results.len()];371if case.options.guest_caller_async {372store373.run_concurrent(async |a| {374func.call_concurrent(a, ¶ms, &mut actual).await.unwrap();375})376.await377.unwrap();378} else {379func.call_async(&mut store, ¶ms, &mut actual)380.await381.unwrap();382}383log::trace!("received results {actual:?}");384assert_eq!(actual, results);385}386Ok(())387})388}389390#[cfg(test)]391mod tests {392use super::*;393use crate::test::test_n_times;394use wasmtime_test_util::component_fuzz::{TestCase, Type};395396#[test]397fn dynamic_component_api_smoke_test() {398test_n_times(50, |(), u| super::dynamic_component_api_target(u));399}400401#[test]402fn static_api_smoke_test() {403test_n_times(10, |(), u| {404let mut case = TestCase::generate(&[], u)?;405case.params = vec![&Type::S32, &Type::Bool, &Type::String];406case.result = Some(&Type::String);407408let declarations = case.declarations();409static_api_test::<(i32, bool, String), (String,)>(u, &declarations)410});411}412}413414415