Skip to content

Commit 5730c76

Browse files
authored
Improve Config-related coverage of component_api (#12049)
This commit improves the preexisting `component_api` fuzz target to use arbitrary `Config` data. This, for example, helps exercise Pulley in addition to native. In general this also helps stress and ensure that no config knobs are accidentally breaking ABI assumptions.
1 parent b56706f commit 5730c76

5 files changed

Lines changed: 179 additions & 137 deletions

File tree

crates/fuzzing/src/generators.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
pub mod api;
1212
mod async_config;
1313
mod codegen_settings;
14-
pub mod component_types;
1514
mod config;
1615
pub mod gc_ops;
1716
mod instance_allocation_strategy;

crates/fuzzing/src/oracles.rs

Lines changed: 1 addition & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//! When an oracle finds a bug, it should report it to the fuzzing engine by
1111
//! panicking.
1212
13+
pub mod component_api;
1314
#[cfg(feature = "fuzz-spec-interpreter")]
1415
pub mod diff_spec;
1516
pub mod diff_wasmi;
@@ -33,7 +34,6 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
3334
use std::sync::{Arc, Condvar, Mutex};
3435
use std::task::{Context, Poll};
3536
use std::time::{Duration, Instant};
36-
use wasmtime::component::Accessor;
3737
use wasmtime::*;
3838
use wasmtime_wast::WastContext;
3939

@@ -1077,117 +1077,6 @@ impl Drop for HelperThread {
10771077
}
10781078
}
10791079

1080-
/// Generate and execute a `crate::generators::component_types::TestCase` using the specified `input` to create
1081-
/// arbitrary types and values.
1082-
pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()> {
1083-
use crate::generators::component_types;
1084-
use wasmtime::component::{Component, Linker, Val};
1085-
use wasmtime_test_util::component_fuzz::{
1086-
EXPORT_FUNCTION, IMPORT_FUNCTION, MAX_TYPE_DEPTH, TestCase, Type,
1087-
};
1088-
1089-
crate::init_fuzzing();
1090-
1091-
let mut types = Vec::new();
1092-
let mut type_fuel = 500;
1093-
1094-
for _ in 0..5 {
1095-
types.push(Type::generate(input, MAX_TYPE_DEPTH, &mut type_fuel)?);
1096-
}
1097-
1098-
let case = TestCase::generate(&types, input)?;
1099-
1100-
let mut config = wasmtime_test_util::component::config();
1101-
config.async_support(true);
1102-
config.wasm_component_model_async(true);
1103-
config.wasm_component_model_async_stackful(true);
1104-
config.debug_adapter_modules(input.arbitrary()?);
1105-
let engine = Engine::new(&config).unwrap();
1106-
let mut store = Store::new(&engine, (Vec::new(), None));
1107-
let wat = case.declarations().make_component();
1108-
let wat = wat.as_bytes();
1109-
log_wasm(wat);
1110-
let component = Component::new(&engine, wat).unwrap();
1111-
let mut linker = Linker::new(&engine);
1112-
1113-
fn host_function(
1114-
mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>,
1115-
params: &[Val],
1116-
results: &mut [Val],
1117-
) -> Result<()> {
1118-
log::trace!("received params {params:?}");
1119-
let (expected_args, expected_results) = cx.data_mut();
1120-
assert_eq!(params.len(), expected_args.len());
1121-
for (expected, actual) in expected_args.iter().zip(params) {
1122-
assert_eq!(expected, actual);
1123-
}
1124-
results.clone_from_slice(&expected_results.take().unwrap());
1125-
log::trace!("returning results {results:?}");
1126-
Ok(())
1127-
}
1128-
1129-
if case.options.host_async {
1130-
linker
1131-
.root()
1132-
.func_new_concurrent(IMPORT_FUNCTION, {
1133-
move |cx: &Accessor<_, _>, _, params: &[Val], results: &mut [Val]| {
1134-
Box::pin(async move {
1135-
cx.with(|mut store| host_function(store.as_context_mut(), params, results))
1136-
})
1137-
}
1138-
})
1139-
.unwrap();
1140-
} else {
1141-
linker
1142-
.root()
1143-
.func_new(IMPORT_FUNCTION, {
1144-
move |cx, _, params, results| host_function(cx, params, results)
1145-
})
1146-
.unwrap();
1147-
}
1148-
1149-
block_on(async {
1150-
let instance = linker
1151-
.instantiate_async(&mut store, &component)
1152-
.await
1153-
.unwrap();
1154-
let func = instance.get_func(&mut store, EXPORT_FUNCTION).unwrap();
1155-
let ty = func.ty(&store);
1156-
1157-
while input.arbitrary()? {
1158-
let params = ty
1159-
.params()
1160-
.map(|(_, ty)| component_types::arbitrary_val(&ty, input))
1161-
.collect::<arbitrary::Result<Vec<_>>>()?;
1162-
let results = ty
1163-
.results()
1164-
.map(|ty| component_types::arbitrary_val(&ty, input))
1165-
.collect::<arbitrary::Result<Vec<_>>>()?;
1166-
1167-
*store.data_mut() = (params.clone(), Some(results.clone()));
1168-
1169-
log::trace!("passing params {params:?}");
1170-
let mut actual = vec![Val::Bool(false); results.len()];
1171-
if case.options.guest_caller_async {
1172-
store
1173-
.run_concurrent(async |a| {
1174-
func.call_concurrent(a, &params, &mut actual).await.unwrap();
1175-
})
1176-
.await
1177-
.unwrap();
1178-
} else {
1179-
func.call_async(&mut store, &params, &mut actual)
1180-
.await
1181-
.unwrap();
1182-
func.post_return_async(&mut store).await.unwrap();
1183-
}
1184-
log::trace!("received results {actual:?}");
1185-
assert_eq!(actual, results);
1186-
}
1187-
Ok(())
1188-
})
1189-
}
1190-
11911080
/// Instantiates a wasm module and runs its exports with dummy values, all in
11921081
/// an async fashion.
11931082
///
@@ -1478,9 +1367,4 @@ mod tests {
14781367
fn wast_smoke_test() {
14791368
test_n_times(50, |(), u| super::wast_test(u));
14801369
}
1481-
1482-
#[test]
1483-
fn dynamic_component_api_smoke_test() {
1484-
test_n_times(50, |(), u| super::dynamic_component_api_target(u));
1485-
}
14861370
}

crates/fuzzing/src/generators/component_types.rs renamed to crates/fuzzing/src/oracles/component_api.rs

Lines changed: 174 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@
77
//! lifting and lowering code and verify the values remain intact during both processes.
88
99
use crate::block_on;
10+
use crate::generators::{self, CompilerStrategy, InstanceAllocationStrategy};
11+
use crate::oracles::log_wasm;
1012
use arbitrary::{Arbitrary, Unstructured};
1113
use std::any::Any;
1214
use std::fmt::Debug;
1315
use std::ops::ControlFlow;
14-
use wasmtime::component::{self, Component, ComponentNamedList, Lift, Linker, Lower, Val};
15-
use wasmtime::{AsContextMut, Config, Engine, Store, StoreContextMut};
16-
use wasmtime_test_util::component_fuzz::{Declarations, EXPORT_FUNCTION, IMPORT_FUNCTION};
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+
};
1723

1824
/// Minimum length of an arbitrary list value generated for a test case
1925
const MIN_LIST_LENGTH: u32 = 0;
@@ -22,7 +28,7 @@ const MIN_LIST_LENGTH: u32 = 0;
2228
const MAX_LIST_LENGTH: u32 = 10;
2329

2430
/// Generate an arbitrary instance of the specified type.
25-
pub fn arbitrary_val(ty: &component::Type, input: &mut Unstructured) -> arbitrary::Result<Val> {
31+
fn arbitrary_val(ty: &component::Type, input: &mut Unstructured) -> arbitrary::Result<Val> {
2632
use component::Type;
2733

2834
Ok(match ty {
@@ -116,6 +122,63 @@ pub fn arbitrary_val(ty: &component::Type, input: &mut Unstructured) -> arbitrar
116122
})
117123
}
118124

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.max_memories_per_component, 2);
162+
set_min(&mut p.max_memories_per_module, 2);
163+
p.memory_protection_keys = Enabled::No;
164+
p.max_memory_size = 10 << 20; // 10 MiB
165+
}
166+
set_opt_min(
167+
&mut config.wasmtime.memory_config.memory_reservation,
168+
10 << 20,
169+
);
170+
171+
let engine = Engine::new(
172+
config
173+
.to_wasmtime()
174+
.debug_adapter_modules(input.arbitrary()?),
175+
)
176+
.unwrap();
177+
let mut store = Store::new(&engine, val);
178+
config.configure_store_epoch_and_fuel(&mut store);
179+
Ok(store)
180+
}
181+
119182
/// Generate zero or more sets of arbitrary argument and result values and execute the test using those
120183
/// values, asserting that they flow from host-to-guest and guest-to-host unchanged.
121184
pub fn static_api_test<'a, P, R>(
@@ -146,13 +209,8 @@ where
146209
{
147210
crate::init_fuzzing();
148211

149-
let mut config = Config::new();
150-
config.wasm_component_model(true);
151-
config.wasm_component_model_async(true);
152-
config.wasm_component_model_async_stackful(true);
153-
config.async_support(true);
154-
config.debug_adapter_modules(input.arbitrary()?);
155-
let engine = Engine::new(&config).unwrap();
212+
let mut store = store::<Box<dyn Any + Send>>(input, Box::new(()))?;
213+
let engine = store.engine();
156214
let wat = declarations.make_component();
157215
let wat = wat.as_bytes();
158216
crate::oracles::log_wasm(wat);
@@ -192,7 +250,6 @@ where
192250
})
193251
.unwrap();
194252
}
195-
let mut store: Store<Box<dyn Any + Send>> = Store::new(&engine, Box::new(()));
196253

197254
block_on(async {
198255
let instance = linker
@@ -226,12 +283,117 @@ where
226283
})
227284
}
228285

286+
/// Generate and execute a `crate::generators::component_types::TestCase` using the specified `input` to create
287+
/// arbitrary types and values.
288+
pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()> {
289+
crate::init_fuzzing();
290+
291+
let mut types = Vec::new();
292+
let mut type_fuel = 500;
293+
294+
for _ in 0..5 {
295+
types.push(Type::generate(input, MAX_TYPE_DEPTH, &mut type_fuel)?);
296+
}
297+
298+
let case = TestCase::generate(&types, input)?;
299+
300+
let mut store = store(input, (Vec::new(), None))?;
301+
let engine = store.engine();
302+
let wat = case.declarations().make_component();
303+
let wat = wat.as_bytes();
304+
log_wasm(wat);
305+
let component = Component::new(&engine, wat).unwrap();
306+
let mut linker = Linker::new(&engine);
307+
308+
fn host_function(
309+
mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>,
310+
params: &[Val],
311+
results: &mut [Val],
312+
) -> Result<()> {
313+
log::trace!("received params {params:?}");
314+
let (expected_args, expected_results) = cx.data_mut();
315+
assert_eq!(params.len(), expected_args.len());
316+
for (expected, actual) in expected_args.iter().zip(params) {
317+
assert_eq!(expected, actual);
318+
}
319+
results.clone_from_slice(&expected_results.take().unwrap());
320+
log::trace!("returning results {results:?}");
321+
Ok(())
322+
}
323+
324+
if case.options.host_async {
325+
linker
326+
.root()
327+
.func_new_concurrent(IMPORT_FUNCTION, {
328+
move |cx: &Accessor<_, _>, _, params: &[Val], results: &mut [Val]| {
329+
Box::pin(async move {
330+
cx.with(|mut store| host_function(store.as_context_mut(), params, results))
331+
})
332+
}
333+
})
334+
.unwrap();
335+
} else {
336+
linker
337+
.root()
338+
.func_new(IMPORT_FUNCTION, {
339+
move |cx, _, params, results| host_function(cx, params, results)
340+
})
341+
.unwrap();
342+
}
343+
344+
block_on(async {
345+
let instance = linker
346+
.instantiate_async(&mut store, &component)
347+
.await
348+
.unwrap();
349+
let func = instance.get_func(&mut store, EXPORT_FUNCTION).unwrap();
350+
let ty = func.ty(&store);
351+
352+
while input.arbitrary()? {
353+
let params = ty
354+
.params()
355+
.map(|(_, ty)| arbitrary_val(&ty, input))
356+
.collect::<arbitrary::Result<Vec<_>>>()?;
357+
let results = ty
358+
.results()
359+
.map(|ty| arbitrary_val(&ty, input))
360+
.collect::<arbitrary::Result<Vec<_>>>()?;
361+
362+
*store.data_mut() = (params.clone(), Some(results.clone()));
363+
364+
log::trace!("passing params {params:?}");
365+
let mut actual = vec![Val::Bool(false); results.len()];
366+
if case.options.guest_caller_async {
367+
store
368+
.run_concurrent(async |a| {
369+
func.call_concurrent(a, &params, &mut actual).await.unwrap();
370+
})
371+
.await
372+
.unwrap();
373+
} else {
374+
func.call_async(&mut store, &params, &mut actual)
375+
.await
376+
.unwrap();
377+
func.post_return_async(&mut store).await.unwrap();
378+
}
379+
log::trace!("received results {actual:?}");
380+
assert_eq!(actual, results);
381+
}
382+
Ok(())
383+
})
384+
}
385+
229386
#[cfg(test)]
230387
mod tests {
231388
use super::*;
232389
use crate::test::test_n_times;
233390
use wasmtime_test_util::component_fuzz::{TestCase, Type};
234391

392+
#[test]
393+
fn dynamic_component_api_smoke_test() {
394+
test_n_times(50, |(), u| super::dynamic_component_api_target(u));
395+
}
396+
235397
#[test]
236398
fn static_api_smoke_test() {
237399
test_n_times(10, |(), u| {

0 commit comments

Comments
 (0)