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