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, &params, &mut actual).await.unwrap();
373                     })
374                     .await
375                     .unwrap();
376             } else {
377                 func.call_async(&mut store, &params, &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