Path: blob/main/crates/fuzzing/src/generators/api.rs
1693 views
//! Generating sequences of Wasmtime API calls.1//!2//! We only generate *valid* sequences of API calls. To do this, we keep track3//! of what objects we've already created in earlier API calls via the `Scope`4//! struct.5//!6//! To generate even-more-pathological sequences of API calls, we use [swarm7//! testing]:8//!9//! > In swarm testing, the usual practice of potentially including all features10//! > in every test case is abandoned. Rather, a large “swarm” of randomly11//! > generated configurations, each of which omits some features, is used, with12//! > configurations receiving equal resources.13//!14//! [swarm testing]: https://www.cs.utah.edu/~regehr/papers/swarm12.pdf1516use crate::generators::Config;17use arbitrary::{Arbitrary, Unstructured};18use std::collections::BTreeSet;1920#[derive(Arbitrary, Debug)]21struct Swarm {22module_new: bool,23module_drop: bool,24instance_new: bool,25instance_drop: bool,26call_exported_func: bool,27}2829/// A call to one of Wasmtime's public APIs.30#[derive(Arbitrary, Debug)]31#[expect(missing_docs, reason = "self-describing fields")]32pub enum ApiCall {33StoreNew(Config),34ModuleNew { id: usize, wasm: Vec<u8> },35ModuleDrop { id: usize },36InstanceNew { id: usize, module: usize },37InstanceDrop { id: usize },38CallExportedFunc { instance: usize, nth: usize },39}40use ApiCall::*;4142struct Scope {43id_counter: usize,44modules: BTreeSet<usize>,45instances: BTreeSet<usize>,46config: Config,47}4849impl Scope {50fn next_id(&mut self) -> usize {51let id = self.id_counter;52self.id_counter = id + 1;53id54}55}5657/// A sequence of API calls.58#[derive(Debug)]59pub struct ApiCalls {60/// The API calls.61pub calls: Vec<ApiCall>,62}6364impl<'a> Arbitrary<'a> for ApiCalls {65fn arbitrary(input: &mut Unstructured<'a>) -> arbitrary::Result<Self> {66crate::init_fuzzing();6768let swarm = Swarm::arbitrary(input)?;69let mut calls = vec![];7071let config = Config::arbitrary(input)?;72calls.push(StoreNew(config.clone()));7374let mut scope = Scope {75id_counter: 0,76modules: BTreeSet::default(),77instances: BTreeSet::default(),78config,79};8081// Total limit on number of API calls we'll generate. This exists to82// avoid libFuzzer timeouts.83let max_calls = 100;8485for _ in 0..input.arbitrary_len::<ApiCall>()? {86if calls.len() > max_calls {87break;88}8990let mut choices: Vec<fn(_, &mut Scope) -> arbitrary::Result<ApiCall>> = vec![];9192if swarm.module_new {93choices.push(|input, scope| {94let id = scope.next_id();95let wasm = scope.config.generate(input, Some(1000))?;96scope.modules.insert(id);97Ok(ModuleNew {98id,99wasm: wasm.to_bytes(),100})101});102}103if swarm.module_drop && !scope.modules.is_empty() {104choices.push(|input, scope| {105let modules: Vec<_> = scope.modules.iter().collect();106let id = **input.choose(&modules)?;107scope.modules.remove(&id);108Ok(ModuleDrop { id })109});110}111if swarm.instance_new && !scope.modules.is_empty() {112choices.push(|input, scope| {113let modules: Vec<_> = scope.modules.iter().collect();114let module = **input.choose(&modules)?;115let id = scope.next_id();116scope.instances.insert(id);117Ok(InstanceNew { id, module })118});119}120if swarm.instance_drop && !scope.instances.is_empty() {121choices.push(|input, scope| {122let instances: Vec<_> = scope.instances.iter().collect();123let id = **input.choose(&instances)?;124scope.instances.remove(&id);125Ok(InstanceDrop { id })126});127}128if swarm.call_exported_func && !scope.instances.is_empty() {129choices.push(|input, scope| {130let instances: Vec<_> = scope.instances.iter().collect();131let instance = **input.choose(&instances)?;132let nth = usize::arbitrary(input)?;133Ok(CallExportedFunc { instance, nth })134});135}136137if choices.is_empty() {138break;139}140let c = input.choose(&choices)?;141calls.push(c(input, &mut scope)?);142}143144Ok(ApiCalls { calls })145}146}147148149