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