1use crate::block_on;
10use crate::generators::{self, CompilerStrategy, InstanceAllocationStrategy};
11use crate::oracles::log_wasm;
12use arbitrary::{Arbitrary, Unstructured};
13use std::any::Any;
14use std::fmt::Debug;
15use std::ops::ControlFlow;
16use wasmtime::component::{
17 self, Accessor, Component, ComponentNamedList, Lift, Linker, Lower, Val,
18};
19use wasmtime::{AsContextMut, Enabled, Engine, Result, Store, StoreContextMut};
20use wasmtime_test_util::component_fuzz::{
21 Declarations, EXPORT_FUNCTION, IMPORT_FUNCTION, MAX_TYPE_DEPTH, TestCase, Type,
22};
23
24const MIN_LIST_LENGTH: u32 = 0;
26
27const MAX_LIST_LENGTH: u32 = 10;
29
30const MAX_ITERS: usize = 1_000;
32
33fn arbitrary_val(
35 ty: &component::Type,
36 fuel: &mut u32,
37 input: &mut Unstructured,
38) -> arbitrary::Result<Val> {
39 use component::Type;
40
41 *fuel = fuel.saturating_sub(1);
42 Ok(match ty {
43 Type::Bool => Val::Bool(input.arbitrary()?),
44 Type::S8 => Val::S8(input.arbitrary()?),
45 Type::U8 => Val::U8(input.arbitrary()?),
46 Type::S16 => Val::S16(input.arbitrary()?),
47 Type::U16 => Val::U16(input.arbitrary()?),
48 Type::S32 => Val::S32(input.arbitrary()?),
49 Type::U32 => Val::U32(input.arbitrary()?),
50 Type::S64 => Val::S64(input.arbitrary()?),
51 Type::U64 => Val::U64(input.arbitrary()?),
52 Type::Float32 => Val::Float32(input.arbitrary()?),
53 Type::Float64 => Val::Float64(input.arbitrary()?),
54 Type::Char => Val::Char(input.arbitrary()?),
55 Type::String => {
56 let string = if *fuel == 0 {
57 String::new()
58 } else {
59 input.arbitrary()?
60 };
61 *fuel = fuel.saturating_sub(string.len() as u32);
62 Val::String(string)
63 }
64 Type::List(list) => {
65 let mut values = Vec::new();
66 input.arbitrary_loop(Some(MIN_LIST_LENGTH), Some(MAX_LIST_LENGTH), |input| {
67 if *fuel == 0 {
68 return Ok(ControlFlow::Break(()));
69 }
70 values.push(arbitrary_val(&list.ty(), fuel, input)?);
71
72 Ok(ControlFlow::Continue(()))
73 })?;
74
75 Val::List(values)
76 }
77 Type::Record(record) => Val::Record(
78 record
79 .fields()
80 .map(|field| {
81 Ok((
82 field.name.to_string(),
83 arbitrary_val(&field.ty, fuel, input)?,
84 ))
85 })
86 .collect::<arbitrary::Result<_>>()?,
87 ),
88 Type::Tuple(tuple) => Val::Tuple(
89 tuple
90 .types()
91 .map(|ty| arbitrary_val(&ty, fuel, input))
92 .collect::<arbitrary::Result<_>>()?,
93 ),
94 Type::Variant(variant) => {
95 let cases = variant.cases().collect::<Vec<_>>();
96 let case = input.choose(&cases)?;
97 let payload = match &case.ty {
98 Some(ty) => Some(Box::new(arbitrary_val(ty, fuel, input)?)),
99 None => None,
100 };
101 Val::Variant(case.name.to_string(), payload)
102 }
103 Type::Enum(en) => {
104 let names = en.names().collect::<Vec<_>>();
105 let name = input.choose(&names)?;
106 Val::Enum(name.to_string())
107 }
108 Type::Option(option) => {
109 let discriminant = input.int_in_range(0..=1)?;
110 Val::Option(match discriminant {
111 0 => None,
112 1 => Some(Box::new(arbitrary_val(&option.ty(), fuel, input)?)),
113 _ => unreachable!(),
114 })
115 }
116 Type::Result(result) => {
117 let discriminant = input.int_in_range(0..=1)?;
118 Val::Result(match discriminant {
119 0 => Ok(match result.ok() {
120 Some(ty) => Some(Box::new(arbitrary_val(&ty, fuel, input)?)),
121 None => None,
122 }),
123 1 => Err(match result.err() {
124 Some(ty) => Some(Box::new(arbitrary_val(&ty, fuel, input)?)),
125 None => None,
126 }),
127 _ => unreachable!(),
128 })
129 }
130 Type::Flags(flags) => Val::Flags(
131 flags
132 .names()
133 .filter_map(|name| {
134 input
135 .arbitrary()
136 .map(|p| if p { Some(name.to_string()) } else { None })
137 .transpose()
138 })
139 .collect::<arbitrary::Result<_>>()?,
140 ),
141 Type::FixedLengthList(list) => Val::FixedLengthList(
142 (0..list.len())
143 .map(|_| arbitrary_val(&list.ty(), fuel, input))
144 .collect::<arbitrary::Result<_>>()?,
145 ),
146
147 Type::Map(map) => {
148 let mut pairs = Vec::new();
149 input.arbitrary_loop(Some(MIN_LIST_LENGTH), Some(MAX_LIST_LENGTH), |input| {
150 if *fuel == 0 {
151 return Ok(ControlFlow::Break(()));
152 }
153 let key = arbitrary_val(&map.key(), fuel, input)?;
154 let value = arbitrary_val(&map.value(), fuel, input)?;
155 pairs.push((key, value));
156 Ok(ControlFlow::Continue(()))
157 })?;
158 Val::Map(pairs)
159 }
160
161 Type::Own(_) | Type::Borrow(_) | Type::Future(_) | Type::Stream(_) | Type::ErrorContext => {
163 unreachable!()
164 }
165 })
166}
167
168fn store<T>(input: &mut Unstructured<'_>, val: T) -> arbitrary::Result<Store<T>> {
169 crate::init_fuzzing();
170
171 let mut config = input.arbitrary::<generators::Config>()?;
172 config.enable_async(input)?;
173 config.module_config.config.multi_value_enabled = true;
174 config.module_config.config.bulk_memory_enabled = true;
175 config.module_config.config.reference_types_enabled = true;
176 config.module_config.config.max_memories = 2;
177 config.module_config.component_model_async = true;
178 config.module_config.component_model_async_stackful = true;
179 config.module_config.component_model_map = true;
180 if config.wasmtime.compiler_strategy == CompilerStrategy::Winch {
181 config.wasmtime.compiler_strategy = CompilerStrategy::CraneliftNative;
182 }
183
184 fn set_min<T>(a: &mut T, min: T)
185 where
186 T: Ord + Copy,
187 {
188 if *a < min {
189 *a = min;
190 }
191 }
192
193 fn set_opt_min<T>(a: &mut Option<T>, min: T)
194 where
195 T: Ord + Copy,
196 {
197 if let Some(a) = a {
198 set_min(a, min);
199 }
200 }
201
202 const MEMORY_NEEDED: usize = 100 << 20; if let InstanceAllocationStrategy::Pooling(p) = &mut config.wasmtime.strategy {
205 set_min(&mut p.total_component_instances, 5);
206 set_min(&mut p.total_core_instances, 5);
207 set_min(&mut p.total_memories, 2);
208 set_min(&mut p.total_stacks, 4);
209 set_min(&mut p.max_memories_per_component, 2);
210 set_min(&mut p.max_memories_per_module, 2);
211 set_min(&mut p.component_instance_size, 64 << 10);
212 set_min(&mut p.core_instance_size, 64 << 10);
213 p.memory_protection_keys = Enabled::No;
214 p.max_memory_size = MEMORY_NEEDED;
215
216 if cfg!(target_pointer_width = "32") {
219 config
220 .wasmtime
221 .memory_config
222 .memory_reservation
223 .get_or_insert(MEMORY_NEEDED as u64);
224 }
225 }
226 set_opt_min(
227 &mut config.wasmtime.memory_config.memory_reservation,
228 MEMORY_NEEDED as u64,
229 );
230
231 config.wasmtime.make_internally_consistent();
235
236 let engine = Engine::new(
237 config
238 .to_wasmtime()
239 .debug_adapter_modules(input.arbitrary()?),
240 )
241 .unwrap();
242 let mut store = Store::new(&engine, val);
243 config.configure_store_epoch_and_fuel(&mut store);
244 Ok(store)
245}
246
247pub fn static_api_test<'a, P, R>(
250 input: &mut Unstructured<'a>,
251 declarations: &Declarations,
252) -> arbitrary::Result<()>
253where
254 P: ComponentNamedList
255 + Lift
256 + Lower
257 + Clone
258 + PartialEq
259 + Debug
260 + Arbitrary<'a>
261 + Send
262 + Sync
263 + 'static,
264 R: ComponentNamedList
265 + Lift
266 + Lower
267 + Clone
268 + PartialEq
269 + Debug
270 + Arbitrary<'a>
271 + Send
272 + Sync
273 + 'static,
274{
275 crate::init_fuzzing();
276
277 let mut store = store::<Box<dyn Any + Send>>(input, Box::new(()))?;
278 let engine = store.engine();
279 let wat = declarations.make_component();
280 let wat = wat.as_bytes();
281 crate::oracles::log_wasm(wat);
282 let component = Component::new(&engine, wat).unwrap();
283 let mut linker = Linker::new(&engine);
284
285 fn host_function<P, R>(
286 cx: StoreContextMut<'_, Box<dyn Any + Send>>,
287 params: P,
288 ) -> wasmtime::Result<R>
289 where
290 P: Debug + PartialEq + 'static,
291 R: Debug + Clone + 'static,
292 {
293 log::trace!("received parameters {params:?}");
294 let data: &(P, R) = cx.data().downcast_ref().unwrap();
295 let (expected_params, result) = data;
296 assert_eq!(params, *expected_params);
297 log::trace!("returning result {result:?}");
298 Ok(result.clone())
299 }
300
301 if declarations.options.host_async {
302 linker
303 .root()
304 .func_wrap_concurrent(IMPORT_FUNCTION, |a, params| {
305 Box::pin(async move {
306 a.with(|mut cx| host_function::<P, R>(cx.as_context_mut(), params))
307 })
308 })
309 .unwrap();
310 } else {
311 linker
312 .root()
313 .func_wrap(IMPORT_FUNCTION, |cx, params| {
314 host_function::<P, R>(cx, params)
315 })
316 .unwrap();
317 }
318
319 block_on(async {
320 let instance = linker
321 .instantiate_async(&mut store, &component)
322 .await
323 .unwrap();
324 let func = instance
325 .get_typed_func::<P, R>(&mut store, EXPORT_FUNCTION)
326 .unwrap();
327
328 let mut iters = 0..MAX_ITERS;
329 while iters.next().is_some() && input.arbitrary()? {
330 let params = input.arbitrary::<P>()?;
331 let result = input.arbitrary::<R>()?;
332 *store.data_mut() = Box::new((params.clone(), result.clone()));
333 log::trace!("passing in parameters {params:?}");
334 let actual = if declarations.options.guest_caller_async {
335 store
336 .run_concurrent(async |a| func.call_concurrent(a, params).await.unwrap())
337 .await
338 .unwrap()
339 } else {
340 func.call_async(&mut store, params).await.unwrap()
341 };
342 log::trace!("got result {actual:?}");
343 assert_eq!(actual, result);
344 }
345
346 Ok(())
347 })
348}
349
350pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()> {
353 crate::init_fuzzing();
354
355 let mut types = Vec::new();
356 let mut type_fuel = 500;
357
358 for _ in 0..5 {
359 types.push(Type::generate(input, MAX_TYPE_DEPTH, &mut type_fuel)?);
360 }
361
362 let case = TestCase::generate(&types, input)?;
363
364 let mut store = store(input, (Vec::new(), None))?;
365 let engine = store.engine();
366 let wat = case.declarations().make_component();
367 let wat = wat.as_bytes();
368 log_wasm(wat);
369 let component = Component::new(&engine, wat).unwrap();
370 let mut linker = Linker::new(&engine);
371
372 fn host_function(
373 mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>,
374 params: &[Val],
375 results: &mut [Val],
376 ) -> Result<()> {
377 log::trace!("received params {params:?}");
378 let (expected_args, expected_results) = cx.data_mut();
379 assert_eq!(params.len(), expected_args.len());
380 for (expected, actual) in expected_args.iter().zip(params) {
381 assert_eq!(expected, actual);
382 }
383 results.clone_from_slice(&expected_results.take().unwrap());
384 log::trace!("returning results {results:?}");
385 Ok(())
386 }
387
388 if case.options.host_async {
389 linker
390 .root()
391 .func_new_concurrent(IMPORT_FUNCTION, {
392 move |cx: &Accessor<_, _>, _, params: &[Val], results: &mut [Val]| {
393 Box::pin(async move {
394 cx.with(|mut store| host_function(store.as_context_mut(), params, results))
395 })
396 }
397 })
398 .unwrap();
399 } else {
400 linker
401 .root()
402 .func_new(IMPORT_FUNCTION, {
403 move |cx, _, params, results| host_function(cx, params, results)
404 })
405 .unwrap();
406 }
407
408 block_on(async {
409 let instance = linker
410 .instantiate_async(&mut store, &component)
411 .await
412 .unwrap();
413 let func = instance.get_func(&mut store, EXPORT_FUNCTION).unwrap();
414 let ty = func.ty(&store);
415
416 let mut iters = 0..MAX_ITERS;
417 while iters.next().is_some() && input.arbitrary()? {
418 let mut fuel = 10_000;
419 let params = ty
420 .params()
421 .map(|(_, ty)| arbitrary_val(&ty, &mut fuel, input))
422 .collect::<arbitrary::Result<Vec<_>>>()?;
423 let results = ty
424 .results()
425 .map(|ty| arbitrary_val(&ty, &mut fuel, input))
426 .collect::<arbitrary::Result<Vec<_>>>()?;
427
428 *store.data_mut() = (params.clone(), Some(results.clone()));
429
430 log::trace!("passing params {params:?}");
431 let mut actual = vec![Val::Bool(false); results.len()];
432 if case.options.guest_caller_async {
433 store
434 .run_concurrent(async |a| {
435 func.call_concurrent(a, ¶ms, &mut actual).await.unwrap();
436 })
437 .await
438 .unwrap();
439 } else {
440 func.call_async(&mut store, ¶ms, &mut actual)
441 .await
442 .unwrap();
443 }
444 log::trace!("received results {actual:?}");
445 assert_eq!(actual, results);
446 }
447 Ok(())
448 })
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454 use crate::test::test_n_times;
455 use wasmtime_test_util::component_fuzz::{TestCase, Type};
456
457 #[test]
458 fn dynamic_component_api_smoke_test() {
459 test_n_times(50, |(), u| super::dynamic_component_api_target(u));
460 }
461
462 #[test]
463 fn static_api_smoke_test() {
464 test_n_times(10, |(), u| {
465 let mut case = TestCase::generate(&[], u)?;
466 case.params = vec![&Type::S32, &Type::Bool, &Type::String];
467 case.result = Some(&Type::String);
468
469 let declarations = case.declarations();
470 static_api_test::<(i32, bool, String), (String,)>(u, &declarations)
471 });
472 }
473}