wasmtime_fuzzing/generators/
component_types.rs

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
9use arbitrary::{Arbitrary, Unstructured};
10use std::any::Any;
11use std::fmt::Debug;
12use std::ops::ControlFlow;
13use wasmtime::component::{self, Component, ComponentNamedList, Lift, Linker, Lower, Val};
14use wasmtime::{Config, Engine, Store, StoreContextMut};
15use wasmtime_test_util::component_fuzz::{Declarations, EXPORT_FUNCTION, IMPORT_FUNCTION};
16
17/// Minimum length of an arbitrary list value generated for a test case
18const MIN_LIST_LENGTH: u32 = 0;
19
20/// Maximum length of an arbitrary list value generated for a test case
21const MAX_LIST_LENGTH: u32 = 10;
22
23/// Generate an arbitrary instance of the specified type.
24pub fn arbitrary_val(ty: &component::Type, input: &mut Unstructured) -> arbitrary::Result<Val> {
25    use component::Type;
26
27    Ok(match ty {
28        Type::Bool => Val::Bool(input.arbitrary()?),
29        Type::S8 => Val::S8(input.arbitrary()?),
30        Type::U8 => Val::U8(input.arbitrary()?),
31        Type::S16 => Val::S16(input.arbitrary()?),
32        Type::U16 => Val::U16(input.arbitrary()?),
33        Type::S32 => Val::S32(input.arbitrary()?),
34        Type::U32 => Val::U32(input.arbitrary()?),
35        Type::S64 => Val::S64(input.arbitrary()?),
36        Type::U64 => Val::U64(input.arbitrary()?),
37        Type::Float32 => Val::Float32(input.arbitrary()?),
38        Type::Float64 => Val::Float64(input.arbitrary()?),
39        Type::Char => Val::Char(input.arbitrary()?),
40        Type::String => Val::String(input.arbitrary()?),
41        Type::List(list) => {
42            let mut values = Vec::new();
43            input.arbitrary_loop(Some(MIN_LIST_LENGTH), Some(MAX_LIST_LENGTH), |input| {
44                values.push(arbitrary_val(&list.ty(), input)?);
45
46                Ok(ControlFlow::Continue(()))
47            })?;
48
49            Val::List(values)
50        }
51        Type::Record(record) => Val::Record(
52            record
53                .fields()
54                .map(|field| Ok((field.name.to_string(), arbitrary_val(&field.ty, input)?)))
55                .collect::<arbitrary::Result<_>>()?,
56        ),
57        Type::Tuple(tuple) => Val::Tuple(
58            tuple
59                .types()
60                .map(|ty| arbitrary_val(&ty, input))
61                .collect::<arbitrary::Result<_>>()?,
62        ),
63        Type::Variant(variant) => {
64            let cases = variant.cases().collect::<Vec<_>>();
65            let case = input.choose(&cases)?;
66            let payload = match &case.ty {
67                Some(ty) => Some(Box::new(arbitrary_val(ty, input)?)),
68                None => None,
69            };
70            Val::Variant(case.name.to_string(), payload)
71        }
72        Type::Enum(en) => {
73            let names = en.names().collect::<Vec<_>>();
74            let name = input.choose(&names)?;
75            Val::Enum(name.to_string())
76        }
77        Type::Option(option) => {
78            let discriminant = input.int_in_range(0..=1)?;
79            Val::Option(match discriminant {
80                0 => None,
81                1 => Some(Box::new(arbitrary_val(&option.ty(), input)?)),
82                _ => unreachable!(),
83            })
84        }
85        Type::Result(result) => {
86            let discriminant = input.int_in_range(0..=1)?;
87            Val::Result(match discriminant {
88                0 => Ok(match result.ok() {
89                    Some(ty) => Some(Box::new(arbitrary_val(&ty, input)?)),
90                    None => None,
91                }),
92                1 => Err(match result.err() {
93                    Some(ty) => Some(Box::new(arbitrary_val(&ty, input)?)),
94                    None => None,
95                }),
96                _ => unreachable!(),
97            })
98        }
99        Type::Flags(flags) => Val::Flags(
100            flags
101                .names()
102                .filter_map(|name| {
103                    input
104                        .arbitrary()
105                        .map(|p| if p { Some(name.to_string()) } else { None })
106                        .transpose()
107                })
108                .collect::<arbitrary::Result<_>>()?,
109        ),
110
111        // Resources, futures, streams, and error contexts aren't fuzzed at this time.
112        Type::Own(_) | Type::Borrow(_) | Type::Future(_) | Type::Stream(_) | Type::ErrorContext => {
113            unreachable!()
114        }
115    })
116}
117
118/// Generate zero or more sets of arbitrary argument and result values and execute the test using those
119/// values, asserting that they flow from host-to-guest and guest-to-host unchanged.
120pub fn static_api_test<'a, P, R>(
121    input: &mut Unstructured<'a>,
122    declarations: &Declarations,
123) -> arbitrary::Result<()>
124where
125    P: ComponentNamedList
126        + Lift
127        + Lower
128        + Clone
129        + PartialEq
130        + Debug
131        + Arbitrary<'a>
132        + Send
133        + Sync
134        + 'static,
135    R: ComponentNamedList
136        + Lift
137        + Lower
138        + Clone
139        + PartialEq
140        + Debug
141        + Arbitrary<'a>
142        + Send
143        + Sync
144        + 'static,
145{
146    crate::init_fuzzing();
147
148    let mut config = Config::new();
149    config.wasm_component_model(true);
150    config.debug_adapter_modules(input.arbitrary()?);
151    let engine = Engine::new(&config).unwrap();
152    let wat = declarations.make_component();
153    let wat = wat.as_bytes();
154    crate::oracles::log_wasm(wat);
155    let component = Component::new(&engine, wat).unwrap();
156    let mut linker = Linker::new(&engine);
157    linker
158        .root()
159        .func_wrap(
160            IMPORT_FUNCTION,
161            |cx: StoreContextMut<'_, Box<dyn Any + Send>>, params: P| {
162                log::trace!("received parameters {params:?}");
163                let data: &(P, R) = cx.data().downcast_ref().unwrap();
164                let (expected_params, result) = data;
165                assert_eq!(params, *expected_params);
166                log::trace!("returning result {result:?}");
167                Ok(result.clone())
168            },
169        )
170        .unwrap();
171    let mut store: Store<Box<dyn Any + Send>> = Store::new(&engine, Box::new(()));
172    let instance = linker.instantiate(&mut store, &component).unwrap();
173    let func = instance
174        .get_typed_func::<P, R>(&mut store, EXPORT_FUNCTION)
175        .unwrap();
176
177    while input.arbitrary()? {
178        let params = input.arbitrary::<P>()?;
179        let result = input.arbitrary::<R>()?;
180        *store.data_mut() = Box::new((params.clone(), result.clone()));
181        log::trace!("passing in parameters {params:?}");
182        let actual = func.call(&mut store, params).unwrap();
183        log::trace!("got result {actual:?}");
184        assert_eq!(actual, result);
185        func.post_return(&mut store).unwrap();
186    }
187
188    Ok(())
189}