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, &params, &mut actual).await.unwrap();
388                     })
389                     .await
390                     .unwrap();
391             } else {
392                 func.call_async(&mut store, &params, &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