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.
arbitrary_val(ty: &component::Type, input: &mut Unstructured) -> arbitrary::Result<Val>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
store<T>(input: &mut Unstructured<'_>, val: T) -> arbitrary::Result<Store<T>>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.bulk_memory_enabled = true;
146 config.module_config.config.reference_types_enabled = true;
147 config.module_config.config.max_memories = 2;
148 config.module_config.component_model_async = true;
149 config.module_config.component_model_async_stackful = true;
150 config.module_config.component_model_map = true;
151 if config.wasmtime.compiler_strategy == CompilerStrategy::Winch {
152 config.wasmtime.compiler_strategy = CompilerStrategy::CraneliftNative;
153 }
154
155 fn set_min<T>(a: &mut T, min: T)
156 where
157 T: Ord + Copy,
158 {
159 if *a < min {
160 *a = min;
161 }
162 }
163
164 fn set_opt_min<T>(a: &mut Option<T>, min: T)
165 where
166 T: Ord + Copy,
167 {
168 if let Some(a) = a {
169 set_min(a, min);
170 }
171 }
172
173 if let InstanceAllocationStrategy::Pooling(p) = &mut config.wasmtime.strategy {
174 set_min(&mut p.total_component_instances, 5);
175 set_min(&mut p.total_core_instances, 5);
176 set_min(&mut p.total_memories, 2);
177 set_min(&mut p.total_stacks, 4);
178 set_min(&mut p.max_memories_per_component, 2);
179 set_min(&mut p.max_memories_per_module, 2);
180 set_min(&mut p.component_instance_size, 64 << 10);
181 set_min(&mut p.core_instance_size, 64 << 10);
182 p.memory_protection_keys = Enabled::No;
183 p.max_memory_size = 10 << 20; // 10 MiB
184 }
185 set_opt_min(
186 &mut config.wasmtime.memory_config.memory_reservation,
187 10 << 20,
188 );
189
190 let engine = Engine::new(
191 config
192 .to_wasmtime()
193 .debug_adapter_modules(input.arbitrary()?),
194 )
195 .unwrap();
196 let mut store = Store::new(&engine, val);
197 config.configure_store_epoch_and_fuel(&mut store);
198 Ok(store)
199 }
200
201 /// Generate zero or more sets of arbitrary argument and result values and execute the test using those
202 /// values, asserting that they flow from host-to-guest and guest-to-host unchanged.
static_api_test<'a, P, R>( input: &mut Unstructured<'a>, declarations: &Declarations, ) -> arbitrary::Result<()> where P: ComponentNamedList + Lift + Lower + Clone + PartialEq + Debug + Arbitrary<'a> + Send + Sync + 'static, R: ComponentNamedList + Lift + Lower + Clone + PartialEq + Debug + Arbitrary<'a> + Send + Sync + 'static,203 pub fn static_api_test<'a, P, R>(
204 input: &mut Unstructured<'a>,
205 declarations: &Declarations,
206 ) -> arbitrary::Result<()>
207 where
208 P: ComponentNamedList
209 + Lift
210 + Lower
211 + Clone
212 + PartialEq
213 + Debug
214 + Arbitrary<'a>
215 + Send
216 + Sync
217 + 'static,
218 R: ComponentNamedList
219 + Lift
220 + Lower
221 + Clone
222 + PartialEq
223 + Debug
224 + Arbitrary<'a>
225 + Send
226 + Sync
227 + 'static,
228 {
229 crate::init_fuzzing();
230
231 let mut store = store::<Box<dyn Any + Send>>(input, Box::new(()))?;
232 let engine = store.engine();
233 let wat = declarations.make_component();
234 let wat = wat.as_bytes();
235 crate::oracles::log_wasm(wat);
236 let component = Component::new(&engine, wat).unwrap();
237 let mut linker = Linker::new(&engine);
238
239 fn host_function<P, R>(
240 cx: StoreContextMut<'_, Box<dyn Any + Send>>,
241 params: P,
242 ) -> wasmtime::Result<R>
243 where
244 P: Debug + PartialEq + 'static,
245 R: Debug + Clone + 'static,
246 {
247 log::trace!("received parameters {params:?}");
248 let data: &(P, R) = cx.data().downcast_ref().unwrap();
249 let (expected_params, result) = data;
250 assert_eq!(params, *expected_params);
251 log::trace!("returning result {result:?}");
252 Ok(result.clone())
253 }
254
255 if declarations.options.host_async {
256 linker
257 .root()
258 .func_wrap_concurrent(IMPORT_FUNCTION, |a, params| {
259 Box::pin(async move {
260 a.with(|mut cx| host_function::<P, R>(cx.as_context_mut(), params))
261 })
262 })
263 .unwrap();
264 } else {
265 linker
266 .root()
267 .func_wrap(IMPORT_FUNCTION, |cx, params| {
268 host_function::<P, R>(cx, params)
269 })
270 .unwrap();
271 }
272
273 block_on(async {
274 let instance = linker
275 .instantiate_async(&mut store, &component)
276 .await
277 .unwrap();
278 let func = instance
279 .get_typed_func::<P, R>(&mut store, EXPORT_FUNCTION)
280 .unwrap();
281
282 let mut iters = 0..MAX_ITERS;
283 while iters.next().is_some() && input.arbitrary()? {
284 let params = input.arbitrary::<P>()?;
285 let result = input.arbitrary::<R>()?;
286 *store.data_mut() = Box::new((params.clone(), result.clone()));
287 log::trace!("passing in parameters {params:?}");
288 let actual = if declarations.options.guest_caller_async {
289 store
290 .run_concurrent(async |a| func.call_concurrent(a, params).await.unwrap())
291 .await
292 .unwrap()
293 } else {
294 func.call_async(&mut store, params).await.unwrap()
295 };
296 log::trace!("got result {actual:?}");
297 assert_eq!(actual, result);
298 }
299
300 Ok(())
301 })
302 }
303
304 /// Generate and execute a `crate::generators::component_types::TestCase` using the specified `input` to create
305 /// arbitrary types and values.
dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()>306 pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()> {
307 crate::init_fuzzing();
308
309 let mut types = Vec::new();
310 let mut type_fuel = 500;
311
312 for _ in 0..5 {
313 types.push(Type::generate(input, MAX_TYPE_DEPTH, &mut type_fuel)?);
314 }
315
316 let case = TestCase::generate(&types, input)?;
317
318 let mut store = store(input, (Vec::new(), None))?;
319 let engine = store.engine();
320 let wat = case.declarations().make_component();
321 let wat = wat.as_bytes();
322 log_wasm(wat);
323 let component = Component::new(&engine, wat).unwrap();
324 let mut linker = Linker::new(&engine);
325
326 fn host_function(
327 mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>,
328 params: &[Val],
329 results: &mut [Val],
330 ) -> Result<()> {
331 log::trace!("received params {params:?}");
332 let (expected_args, expected_results) = cx.data_mut();
333 assert_eq!(params.len(), expected_args.len());
334 for (expected, actual) in expected_args.iter().zip(params) {
335 assert_eq!(expected, actual);
336 }
337 results.clone_from_slice(&expected_results.take().unwrap());
338 log::trace!("returning results {results:?}");
339 Ok(())
340 }
341
342 if case.options.host_async {
343 linker
344 .root()
345 .func_new_concurrent(IMPORT_FUNCTION, {
346 move |cx: &Accessor<_, _>, _, params: &[Val], results: &mut [Val]| {
347 Box::pin(async move {
348 cx.with(|mut store| host_function(store.as_context_mut(), params, results))
349 })
350 }
351 })
352 .unwrap();
353 } else {
354 linker
355 .root()
356 .func_new(IMPORT_FUNCTION, {
357 move |cx, _, params, results| host_function(cx, params, results)
358 })
359 .unwrap();
360 }
361
362 block_on(async {
363 let instance = linker
364 .instantiate_async(&mut store, &component)
365 .await
366 .unwrap();
367 let func = instance.get_func(&mut store, EXPORT_FUNCTION).unwrap();
368 let ty = func.ty(&store);
369
370 let mut iters = 0..MAX_ITERS;
371 while iters.next().is_some() && input.arbitrary()? {
372 let params = ty
373 .params()
374 .map(|(_, ty)| arbitrary_val(&ty, input))
375 .collect::<arbitrary::Result<Vec<_>>>()?;
376 let results = ty
377 .results()
378 .map(|ty| arbitrary_val(&ty, input))
379 .collect::<arbitrary::Result<Vec<_>>>()?;
380
381 *store.data_mut() = (params.clone(), Some(results.clone()));
382
383 log::trace!("passing params {params:?}");
384 let mut actual = vec![Val::Bool(false); results.len()];
385 if case.options.guest_caller_async {
386 store
387 .run_concurrent(async |a| {
388 func.call_concurrent(a, ¶ms, &mut actual).await.unwrap();
389 })
390 .await
391 .unwrap();
392 } else {
393 func.call_async(&mut store, ¶ms, &mut actual)
394 .await
395 .unwrap();
396 }
397 log::trace!("received results {actual:?}");
398 assert_eq!(actual, results);
399 }
400 Ok(())
401 })
402 }
403
404 #[cfg(test)]
405 mod tests {
406 use super::*;
407 use crate::test::test_n_times;
408 use wasmtime_test_util::component_fuzz::{TestCase, Type};
409
410 #[test]
dynamic_component_api_smoke_test()411 fn dynamic_component_api_smoke_test() {
412 test_n_times(50, |(), u| super::dynamic_component_api_target(u));
413 }
414
415 #[test]
static_api_smoke_test()416 fn static_api_smoke_test() {
417 test_n_times(10, |(), u| {
418 let mut case = TestCase::generate(&[], u)?;
419 case.params = vec![&Type::S32, &Type::Bool, &Type::String];
420 case.result = Some(&Type::String);
421
422 let declarations = case.declarations();
423 static_api_test::<(i32, bool, String), (String,)>(u, &declarations)
424 });
425 }
426 }
427