1 //! This module generates test cases for the Wasmtime component model function APIs, 2 //! e.g. `wasmtime::component::func::Func` and `TypedFunc`. 3 //! 4 //! Each case includes a list of arbitrary interface types to use as parameters, plus another one to use as a 5 //! result, and a component which exports a function and imports a function. The exported function forwards its 6 //! parameters to the imported one and forwards the result back to the caller. This serves to exercise Wasmtime's 7 //! lifting and lowering code and verify the values remain intact during both processes. 8 9 use crate::block_on; 10 use crate::generators::{self, CompilerStrategy, InstanceAllocationStrategy}; 11 use crate::oracles::log_wasm; 12 use arbitrary::{Arbitrary, Unstructured}; 13 use std::any::Any; 14 use std::fmt::Debug; 15 use std::ops::ControlFlow; 16 use wasmtime::component::{ 17 self, Accessor, Component, ComponentNamedList, Lift, Linker, Lower, Val, 18 }; 19 use wasmtime::{AsContextMut, Enabled, Engine, Result, Store, StoreContextMut}; 20 use wasmtime_test_util::component_fuzz::{ 21 Declarations, EXPORT_FUNCTION, IMPORT_FUNCTION, MAX_TYPE_DEPTH, TestCase, Type, 22 }; 23 24 /// Minimum length of an arbitrary list value generated for a test case 25 const MIN_LIST_LENGTH: u32 = 0; 26 27 /// Maximum length of an arbitrary list value generated for a test case 28 const MAX_LIST_LENGTH: u32 = 10; 29 30 /// Maximum number of invocations of one fuzz case. 31 const MAX_ITERS: usize = 1_000; 32 33 /// Generate an arbitrary instance of the specified type. 34 fn arbitrary_val(ty: &component::Type, input: &mut Unstructured) -> arbitrary::Result<Val> { 35 use component::Type; 36 37 Ok(match ty { 38 Type::Bool => Val::Bool(input.arbitrary()?), 39 Type::S8 => Val::S8(input.arbitrary()?), 40 Type::U8 => Val::U8(input.arbitrary()?), 41 Type::S16 => Val::S16(input.arbitrary()?), 42 Type::U16 => Val::U16(input.arbitrary()?), 43 Type::S32 => Val::S32(input.arbitrary()?), 44 Type::U32 => Val::U32(input.arbitrary()?), 45 Type::S64 => Val::S64(input.arbitrary()?), 46 Type::U64 => Val::U64(input.arbitrary()?), 47 Type::Float32 => Val::Float32(input.arbitrary()?), 48 Type::Float64 => Val::Float64(input.arbitrary()?), 49 Type::Char => Val::Char(input.arbitrary()?), 50 Type::String => Val::String(input.arbitrary()?), 51 Type::List(list) => { 52 let mut values = Vec::new(); 53 input.arbitrary_loop(Some(MIN_LIST_LENGTH), Some(MAX_LIST_LENGTH), |input| { 54 values.push(arbitrary_val(&list.ty(), input)?); 55 56 Ok(ControlFlow::Continue(())) 57 })?; 58 59 Val::List(values) 60 } 61 Type::Record(record) => Val::Record( 62 record 63 .fields() 64 .map(|field| Ok((field.name.to_string(), arbitrary_val(&field.ty, input)?))) 65 .collect::<arbitrary::Result<_>>()?, 66 ), 67 Type::Tuple(tuple) => Val::Tuple( 68 tuple 69 .types() 70 .map(|ty| arbitrary_val(&ty, input)) 71 .collect::<arbitrary::Result<_>>()?, 72 ), 73 Type::Variant(variant) => { 74 let cases = variant.cases().collect::<Vec<_>>(); 75 let case = input.choose(&cases)?; 76 let payload = match &case.ty { 77 Some(ty) => Some(Box::new(arbitrary_val(ty, input)?)), 78 None => None, 79 }; 80 Val::Variant(case.name.to_string(), payload) 81 } 82 Type::Enum(en) => { 83 let names = en.names().collect::<Vec<_>>(); 84 let name = input.choose(&names)?; 85 Val::Enum(name.to_string()) 86 } 87 Type::Option(option) => { 88 let discriminant = input.int_in_range(0..=1)?; 89 Val::Option(match discriminant { 90 0 => None, 91 1 => Some(Box::new(arbitrary_val(&option.ty(), input)?)), 92 _ => unreachable!(), 93 }) 94 } 95 Type::Result(result) => { 96 let discriminant = input.int_in_range(0..=1)?; 97 Val::Result(match discriminant { 98 0 => Ok(match result.ok() { 99 Some(ty) => Some(Box::new(arbitrary_val(&ty, input)?)), 100 None => None, 101 }), 102 1 => Err(match result.err() { 103 Some(ty) => Some(Box::new(arbitrary_val(&ty, input)?)), 104 None => None, 105 }), 106 _ => unreachable!(), 107 }) 108 } 109 Type::Flags(flags) => Val::Flags( 110 flags 111 .names() 112 .filter_map(|name| { 113 input 114 .arbitrary() 115 .map(|p| if p { Some(name.to_string()) } else { None }) 116 .transpose() 117 }) 118 .collect::<arbitrary::Result<_>>()?, 119 ), 120 121 Type::Map(map) => { 122 let mut pairs = Vec::new(); 123 input.arbitrary_loop(Some(MIN_LIST_LENGTH), Some(MAX_LIST_LENGTH), |input| { 124 let key = arbitrary_val(&map.key(), input)?; 125 let value = arbitrary_val(&map.value(), input)?; 126 pairs.push((key, value)); 127 Ok(ControlFlow::Continue(())) 128 })?; 129 Val::Map(pairs) 130 } 131 132 // Resources, futures, streams, and error contexts aren't fuzzed at this time. 133 Type::Own(_) | Type::Borrow(_) | Type::Future(_) | Type::Stream(_) | Type::ErrorContext => { 134 unreachable!() 135 } 136 }) 137 } 138 139 fn store<T>(input: &mut Unstructured<'_>, val: T) -> arbitrary::Result<Store<T>> { 140 crate::init_fuzzing(); 141 142 let mut config = input.arbitrary::<generators::Config>()?; 143 config.enable_async(input)?; 144 config.module_config.config.multi_value_enabled = true; 145 config.module_config.config.reference_types_enabled = true; 146 config.module_config.config.max_memories = 2; 147 config.module_config.component_model_async = true; 148 config.module_config.component_model_async_stackful = true; 149 config.module_config.component_model_map = true; 150 if config.wasmtime.compiler_strategy == CompilerStrategy::Winch { 151 config.wasmtime.compiler_strategy = CompilerStrategy::CraneliftNative; 152 } 153 154 fn set_min<T>(a: &mut T, min: T) 155 where 156 T: Ord + Copy, 157 { 158 if *a < min { 159 *a = min; 160 } 161 } 162 163 fn set_opt_min<T>(a: &mut Option<T>, min: T) 164 where 165 T: Ord + Copy, 166 { 167 if let Some(a) = a { 168 set_min(a, min); 169 } 170 } 171 172 if let InstanceAllocationStrategy::Pooling(p) = &mut config.wasmtime.strategy { 173 set_min(&mut p.total_component_instances, 5); 174 set_min(&mut p.total_core_instances, 5); 175 set_min(&mut p.total_memories, 2); 176 set_min(&mut p.total_stacks, 4); 177 set_min(&mut p.max_memories_per_component, 2); 178 set_min(&mut p.max_memories_per_module, 2); 179 set_min(&mut p.component_instance_size, 64 << 10); 180 set_min(&mut p.core_instance_size, 64 << 10); 181 p.memory_protection_keys = Enabled::No; 182 p.max_memory_size = 10 << 20; // 10 MiB 183 } 184 set_opt_min( 185 &mut config.wasmtime.memory_config.memory_reservation, 186 10 << 20, 187 ); 188 189 let engine = Engine::new( 190 config 191 .to_wasmtime() 192 .debug_adapter_modules(input.arbitrary()?), 193 ) 194 .unwrap(); 195 let mut store = Store::new(&engine, val); 196 config.configure_store_epoch_and_fuel(&mut store); 197 Ok(store) 198 } 199 200 /// Generate zero or more sets of arbitrary argument and result values and execute the test using those 201 /// values, asserting that they flow from host-to-guest and guest-to-host unchanged. 202 pub fn static_api_test<'a, P, R>( 203 input: &mut Unstructured<'a>, 204 declarations: &Declarations, 205 ) -> arbitrary::Result<()> 206 where 207 P: ComponentNamedList 208 + Lift 209 + Lower 210 + Clone 211 + PartialEq 212 + Debug 213 + Arbitrary<'a> 214 + Send 215 + Sync 216 + 'static, 217 R: ComponentNamedList 218 + Lift 219 + Lower 220 + Clone 221 + PartialEq 222 + Debug 223 + Arbitrary<'a> 224 + Send 225 + Sync 226 + 'static, 227 { 228 crate::init_fuzzing(); 229 230 let mut store = store::<Box<dyn Any + Send>>(input, Box::new(()))?; 231 let engine = store.engine(); 232 let wat = declarations.make_component(); 233 let wat = wat.as_bytes(); 234 crate::oracles::log_wasm(wat); 235 let component = Component::new(&engine, wat).unwrap(); 236 let mut linker = Linker::new(&engine); 237 238 fn host_function<P, R>( 239 cx: StoreContextMut<'_, Box<dyn Any + Send>>, 240 params: P, 241 ) -> wasmtime::Result<R> 242 where 243 P: Debug + PartialEq + 'static, 244 R: Debug + Clone + 'static, 245 { 246 log::trace!("received parameters {params:?}"); 247 let data: &(P, R) = cx.data().downcast_ref().unwrap(); 248 let (expected_params, result) = data; 249 assert_eq!(params, *expected_params); 250 log::trace!("returning result {result:?}"); 251 Ok(result.clone()) 252 } 253 254 if declarations.options.host_async { 255 linker 256 .root() 257 .func_wrap_concurrent(IMPORT_FUNCTION, |a, params| { 258 Box::pin(async move { 259 a.with(|mut cx| host_function::<P, R>(cx.as_context_mut(), params)) 260 }) 261 }) 262 .unwrap(); 263 } else { 264 linker 265 .root() 266 .func_wrap(IMPORT_FUNCTION, |cx, params| { 267 host_function::<P, R>(cx, params) 268 }) 269 .unwrap(); 270 } 271 272 block_on(async { 273 let instance = linker 274 .instantiate_async(&mut store, &component) 275 .await 276 .unwrap(); 277 let func = instance 278 .get_typed_func::<P, R>(&mut store, EXPORT_FUNCTION) 279 .unwrap(); 280 281 let mut iters = 0..MAX_ITERS; 282 while iters.next().is_some() && input.arbitrary()? { 283 let params = input.arbitrary::<P>()?; 284 let result = input.arbitrary::<R>()?; 285 *store.data_mut() = Box::new((params.clone(), result.clone())); 286 log::trace!("passing in parameters {params:?}"); 287 let actual = if declarations.options.guest_caller_async { 288 store 289 .run_concurrent(async |a| func.call_concurrent(a, params).await.unwrap()) 290 .await 291 .unwrap() 292 } else { 293 func.call_async(&mut store, params).await.unwrap() 294 }; 295 log::trace!("got result {actual:?}"); 296 assert_eq!(actual, result); 297 } 298 299 Ok(()) 300 }) 301 } 302 303 /// Generate and execute a `crate::generators::component_types::TestCase` using the specified `input` to create 304 /// arbitrary types and values. 305 pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()> { 306 crate::init_fuzzing(); 307 308 let mut types = Vec::new(); 309 let mut type_fuel = 500; 310 311 for _ in 0..5 { 312 types.push(Type::generate(input, MAX_TYPE_DEPTH, &mut type_fuel)?); 313 } 314 315 let case = TestCase::generate(&types, input)?; 316 317 let mut store = store(input, (Vec::new(), None))?; 318 let engine = store.engine(); 319 let wat = case.declarations().make_component(); 320 let wat = wat.as_bytes(); 321 log_wasm(wat); 322 let component = Component::new(&engine, wat).unwrap(); 323 let mut linker = Linker::new(&engine); 324 325 fn host_function( 326 mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>, 327 params: &[Val], 328 results: &mut [Val], 329 ) -> Result<()> { 330 log::trace!("received params {params:?}"); 331 let (expected_args, expected_results) = cx.data_mut(); 332 assert_eq!(params.len(), expected_args.len()); 333 for (expected, actual) in expected_args.iter().zip(params) { 334 assert_eq!(expected, actual); 335 } 336 results.clone_from_slice(&expected_results.take().unwrap()); 337 log::trace!("returning results {results:?}"); 338 Ok(()) 339 } 340 341 if case.options.host_async { 342 linker 343 .root() 344 .func_new_concurrent(IMPORT_FUNCTION, { 345 move |cx: &Accessor<_, _>, _, params: &[Val], results: &mut [Val]| { 346 Box::pin(async move { 347 cx.with(|mut store| host_function(store.as_context_mut(), params, results)) 348 }) 349 } 350 }) 351 .unwrap(); 352 } else { 353 linker 354 .root() 355 .func_new(IMPORT_FUNCTION, { 356 move |cx, _, params, results| host_function(cx, params, results) 357 }) 358 .unwrap(); 359 } 360 361 block_on(async { 362 let instance = linker 363 .instantiate_async(&mut store, &component) 364 .await 365 .unwrap(); 366 let func = instance.get_func(&mut store, EXPORT_FUNCTION).unwrap(); 367 let ty = func.ty(&store); 368 369 let mut iters = 0..MAX_ITERS; 370 while iters.next().is_some() && input.arbitrary()? { 371 let params = ty 372 .params() 373 .map(|(_, ty)| arbitrary_val(&ty, input)) 374 .collect::<arbitrary::Result<Vec<_>>>()?; 375 let results = ty 376 .results() 377 .map(|ty| arbitrary_val(&ty, input)) 378 .collect::<arbitrary::Result<Vec<_>>>()?; 379 380 *store.data_mut() = (params.clone(), Some(results.clone())); 381 382 log::trace!("passing params {params:?}"); 383 let mut actual = vec![Val::Bool(false); results.len()]; 384 if case.options.guest_caller_async { 385 store 386 .run_concurrent(async |a| { 387 func.call_concurrent(a, ¶ms, &mut actual).await.unwrap(); 388 }) 389 .await 390 .unwrap(); 391 } else { 392 func.call_async(&mut store, ¶ms, &mut actual) 393 .await 394 .unwrap(); 395 } 396 log::trace!("received results {actual:?}"); 397 assert_eq!(actual, results); 398 } 399 Ok(()) 400 }) 401 } 402 403 #[cfg(test)] 404 mod tests { 405 use super::*; 406 use crate::test::test_n_times; 407 use wasmtime_test_util::component_fuzz::{TestCase, Type}; 408 409 #[test] 410 fn dynamic_component_api_smoke_test() { 411 test_n_times(50, |(), u| super::dynamic_component_api_target(u)); 412 } 413 414 #[test] 415 fn static_api_smoke_test() { 416 test_n_times(10, |(), u| { 417 let mut case = TestCase::generate(&[], u)?; 418 case.params = vec![&Type::S32, &Type::Bool, &Type::String]; 419 case.result = Some(&Type::String); 420 421 let declarations = case.declarations(); 422 static_api_test::<(i32, bool, String), (String,)>(u, &declarations) 423 }); 424 } 425 } 426