Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fuzzing/src/oracles/component_api.rs
3061 views
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
// Resources, futures, streams, and error contexts aren't fuzzed at this time.
122
Type::Own(_) | Type::Borrow(_) | Type::Future(_) | Type::Stream(_) | Type::ErrorContext => {
123
unreachable!()
124
}
125
})
126
}
127
128
fn store<T>(input: &mut Unstructured<'_>, val: T) -> arbitrary::Result<Store<T>> {
129
crate::init_fuzzing();
130
131
let mut config = input.arbitrary::<generators::Config>()?;
132
config.enable_async(input)?;
133
config.module_config.config.multi_value_enabled = true;
134
config.module_config.config.reference_types_enabled = true;
135
config.module_config.config.max_memories = 2;
136
config.module_config.component_model_async = true;
137
config.module_config.component_model_async_stackful = true;
138
if config.wasmtime.compiler_strategy == CompilerStrategy::Winch {
139
config.wasmtime.compiler_strategy = CompilerStrategy::CraneliftNative;
140
}
141
142
fn set_min<T>(a: &mut T, min: T)
143
where
144
T: Ord + Copy,
145
{
146
if *a < min {
147
*a = min;
148
}
149
}
150
151
fn set_opt_min<T>(a: &mut Option<T>, min: T)
152
where
153
T: Ord + Copy,
154
{
155
if let Some(a) = a {
156
set_min(a, min);
157
}
158
}
159
160
if let InstanceAllocationStrategy::Pooling(p) = &mut config.wasmtime.strategy {
161
set_min(&mut p.total_component_instances, 5);
162
set_min(&mut p.total_core_instances, 5);
163
set_min(&mut p.total_memories, 2);
164
set_min(&mut p.total_stacks, 4);
165
set_min(&mut p.max_memories_per_component, 2);
166
set_min(&mut p.max_memories_per_module, 2);
167
set_min(&mut p.component_instance_size, 64 << 10);
168
set_min(&mut p.core_instance_size, 64 << 10);
169
p.memory_protection_keys = Enabled::No;
170
p.max_memory_size = 10 << 20; // 10 MiB
171
}
172
set_opt_min(
173
&mut config.wasmtime.memory_config.memory_reservation,
174
10 << 20,
175
);
176
177
let engine = Engine::new(
178
config
179
.to_wasmtime()
180
.debug_adapter_modules(input.arbitrary()?),
181
)
182
.unwrap();
183
let mut store = Store::new(&engine, val);
184
config.configure_store_epoch_and_fuel(&mut store);
185
Ok(store)
186
}
187
188
/// Generate zero or more sets of arbitrary argument and result values and execute the test using those
189
/// values, asserting that they flow from host-to-guest and guest-to-host unchanged.
190
pub fn static_api_test<'a, P, R>(
191
input: &mut Unstructured<'a>,
192
declarations: &Declarations,
193
) -> arbitrary::Result<()>
194
where
195
P: ComponentNamedList
196
+ Lift
197
+ Lower
198
+ Clone
199
+ PartialEq
200
+ Debug
201
+ Arbitrary<'a>
202
+ Send
203
+ Sync
204
+ 'static,
205
R: ComponentNamedList
206
+ Lift
207
+ Lower
208
+ Clone
209
+ PartialEq
210
+ Debug
211
+ Arbitrary<'a>
212
+ Send
213
+ Sync
214
+ 'static,
215
{
216
crate::init_fuzzing();
217
218
let mut store = store::<Box<dyn Any + Send>>(input, Box::new(()))?;
219
let engine = store.engine();
220
let wat = declarations.make_component();
221
let wat = wat.as_bytes();
222
crate::oracles::log_wasm(wat);
223
let component = Component::new(&engine, wat).unwrap();
224
let mut linker = Linker::new(&engine);
225
226
fn host_function<P, R>(
227
cx: StoreContextMut<'_, Box<dyn Any + Send>>,
228
params: P,
229
) -> wasmtime::Result<R>
230
where
231
P: Debug + PartialEq + 'static,
232
R: Debug + Clone + 'static,
233
{
234
log::trace!("received parameters {params:?}");
235
let data: &(P, R) = cx.data().downcast_ref().unwrap();
236
let (expected_params, result) = data;
237
assert_eq!(params, *expected_params);
238
log::trace!("returning result {result:?}");
239
Ok(result.clone())
240
}
241
242
if declarations.options.host_async {
243
linker
244
.root()
245
.func_wrap_concurrent(IMPORT_FUNCTION, |a, params| {
246
Box::pin(async move {
247
a.with(|mut cx| host_function::<P, R>(cx.as_context_mut(), params))
248
})
249
})
250
.unwrap();
251
} else {
252
linker
253
.root()
254
.func_wrap(IMPORT_FUNCTION, |cx, params| {
255
host_function::<P, R>(cx, params)
256
})
257
.unwrap();
258
}
259
260
block_on(async {
261
let instance = linker
262
.instantiate_async(&mut store, &component)
263
.await
264
.unwrap();
265
let func = instance
266
.get_typed_func::<P, R>(&mut store, EXPORT_FUNCTION)
267
.unwrap();
268
269
let mut iters = 0..MAX_ITERS;
270
while iters.next().is_some() && input.arbitrary()? {
271
let params = input.arbitrary::<P>()?;
272
let result = input.arbitrary::<R>()?;
273
*store.data_mut() = Box::new((params.clone(), result.clone()));
274
log::trace!("passing in parameters {params:?}");
275
let actual = if declarations.options.guest_caller_async {
276
store
277
.run_concurrent(async |a| func.call_concurrent(a, params).await.unwrap().0)
278
.await
279
.unwrap()
280
} else {
281
func.call_async(&mut store, params).await.unwrap()
282
};
283
log::trace!("got result {actual:?}");
284
assert_eq!(actual, result);
285
}
286
287
Ok(())
288
})
289
}
290
291
/// Generate and execute a `crate::generators::component_types::TestCase` using the specified `input` to create
292
/// arbitrary types and values.
293
pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()> {
294
crate::init_fuzzing();
295
296
let mut types = Vec::new();
297
let mut type_fuel = 500;
298
299
for _ in 0..5 {
300
types.push(Type::generate(input, MAX_TYPE_DEPTH, &mut type_fuel)?);
301
}
302
303
let case = TestCase::generate(&types, input)?;
304
305
let mut store = store(input, (Vec::new(), None))?;
306
let engine = store.engine();
307
let wat = case.declarations().make_component();
308
let wat = wat.as_bytes();
309
log_wasm(wat);
310
let component = Component::new(&engine, wat).unwrap();
311
let mut linker = Linker::new(&engine);
312
313
fn host_function(
314
mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>,
315
params: &[Val],
316
results: &mut [Val],
317
) -> Result<()> {
318
log::trace!("received params {params:?}");
319
let (expected_args, expected_results) = cx.data_mut();
320
assert_eq!(params.len(), expected_args.len());
321
for (expected, actual) in expected_args.iter().zip(params) {
322
assert_eq!(expected, actual);
323
}
324
results.clone_from_slice(&expected_results.take().unwrap());
325
log::trace!("returning results {results:?}");
326
Ok(())
327
}
328
329
if case.options.host_async {
330
linker
331
.root()
332
.func_new_concurrent(IMPORT_FUNCTION, {
333
move |cx: &Accessor<_, _>, _, params: &[Val], results: &mut [Val]| {
334
Box::pin(async move {
335
cx.with(|mut store| host_function(store.as_context_mut(), params, results))
336
})
337
}
338
})
339
.unwrap();
340
} else {
341
linker
342
.root()
343
.func_new(IMPORT_FUNCTION, {
344
move |cx, _, params, results| host_function(cx, params, results)
345
})
346
.unwrap();
347
}
348
349
block_on(async {
350
let instance = linker
351
.instantiate_async(&mut store, &component)
352
.await
353
.unwrap();
354
let func = instance.get_func(&mut store, EXPORT_FUNCTION).unwrap();
355
let ty = func.ty(&store);
356
357
let mut iters = 0..MAX_ITERS;
358
while iters.next().is_some() && input.arbitrary()? {
359
let params = ty
360
.params()
361
.map(|(_, ty)| arbitrary_val(&ty, input))
362
.collect::<arbitrary::Result<Vec<_>>>()?;
363
let results = ty
364
.results()
365
.map(|ty| arbitrary_val(&ty, input))
366
.collect::<arbitrary::Result<Vec<_>>>()?;
367
368
*store.data_mut() = (params.clone(), Some(results.clone()));
369
370
log::trace!("passing params {params:?}");
371
let mut actual = vec![Val::Bool(false); results.len()];
372
if case.options.guest_caller_async {
373
store
374
.run_concurrent(async |a| {
375
func.call_concurrent(a, &params, &mut actual).await.unwrap();
376
})
377
.await
378
.unwrap();
379
} else {
380
func.call_async(&mut store, &params, &mut actual)
381
.await
382
.unwrap();
383
}
384
log::trace!("received results {actual:?}");
385
assert_eq!(actual, results);
386
}
387
Ok(())
388
})
389
}
390
391
#[cfg(test)]
392
mod tests {
393
use super::*;
394
use crate::test::test_n_times;
395
use wasmtime_test_util::component_fuzz::{TestCase, Type};
396
397
#[test]
398
fn dynamic_component_api_smoke_test() {
399
test_n_times(50, |(), u| super::dynamic_component_api_target(u));
400
}
401
402
#[test]
403
fn static_api_smoke_test() {
404
test_n_times(10, |(), u| {
405
let mut case = TestCase::generate(&[], u)?;
406
case.params = vec![&Type::S32, &Type::Bool, &Type::String];
407
case.result = Some(&Type::String);
408
409
let declarations = case.declarations();
410
static_api_test::<(i32, bool, String), (String,)>(u, &declarations)
411
});
412
}
413
}
414
415