wasmtime_fuzzing/generators/
api.rs

1//! Generating sequences of Wasmtime API calls.
2//!
3//! We only generate *valid* sequences of API calls. To do this, we keep track
4//! of what objects we've already created in earlier API calls via the `Scope`
5//! struct.
6//!
7//! To generate even-more-pathological sequences of API calls, we use [swarm
8//! testing]:
9//!
10//! > In swarm testing, the usual practice of potentially including all features
11//! > in every test case is abandoned. Rather, a large “swarm” of randomly
12//! > generated configurations, each of which omits some features, is used, with
13//! > configurations receiving equal resources.
14//!
15//! [swarm testing]: https://www.cs.utah.edu/~regehr/papers/swarm12.pdf
16
17use crate::generators::Config;
18use arbitrary::{Arbitrary, Unstructured};
19use std::collections::BTreeSet;
20
21#[derive(Arbitrary, Debug)]
22struct Swarm {
23    module_new: bool,
24    module_drop: bool,
25    instance_new: bool,
26    instance_drop: bool,
27    call_exported_func: bool,
28}
29
30/// A call to one of Wasmtime's public APIs.
31#[derive(Arbitrary, Debug)]
32#[expect(missing_docs, reason = "self-describing fields")]
33pub enum ApiCall {
34    StoreNew(Config),
35    ModuleNew { id: usize, wasm: Vec<u8> },
36    ModuleDrop { id: usize },
37    InstanceNew { id: usize, module: usize },
38    InstanceDrop { id: usize },
39    CallExportedFunc { instance: usize, nth: usize },
40}
41use ApiCall::*;
42
43struct Scope {
44    id_counter: usize,
45    modules: BTreeSet<usize>,
46    instances: BTreeSet<usize>,
47    config: Config,
48}
49
50impl Scope {
51    fn next_id(&mut self) -> usize {
52        let id = self.id_counter;
53        self.id_counter = id + 1;
54        id
55    }
56}
57
58/// A sequence of API calls.
59#[derive(Debug)]
60pub struct ApiCalls {
61    /// The API calls.
62    pub calls: Vec<ApiCall>,
63}
64
65impl<'a> Arbitrary<'a> for ApiCalls {
66    fn arbitrary(input: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
67        crate::init_fuzzing();
68
69        let swarm = Swarm::arbitrary(input)?;
70        let mut calls = vec![];
71
72        let config = Config::arbitrary(input)?;
73        calls.push(StoreNew(config.clone()));
74
75        let mut scope = Scope {
76            id_counter: 0,
77            modules: BTreeSet::default(),
78            instances: BTreeSet::default(),
79            config,
80        };
81
82        // Total limit on number of API calls we'll generate. This exists to
83        // avoid libFuzzer timeouts.
84        let max_calls = 100;
85
86        for _ in 0..input.arbitrary_len::<ApiCall>()? {
87            if calls.len() > max_calls {
88                break;
89            }
90
91            let mut choices: Vec<fn(_, &mut Scope) -> arbitrary::Result<ApiCall>> = vec![];
92
93            if swarm.module_new {
94                choices.push(|input, scope| {
95                    let id = scope.next_id();
96                    let wasm = scope.config.generate(input, Some(1000))?;
97                    scope.modules.insert(id);
98                    Ok(ModuleNew {
99                        id,
100                        wasm: wasm.to_bytes(),
101                    })
102                });
103            }
104            if swarm.module_drop && !scope.modules.is_empty() {
105                choices.push(|input, scope| {
106                    let modules: Vec<_> = scope.modules.iter().collect();
107                    let id = **input.choose(&modules)?;
108                    scope.modules.remove(&id);
109                    Ok(ModuleDrop { id })
110                });
111            }
112            if swarm.instance_new && !scope.modules.is_empty() {
113                choices.push(|input, scope| {
114                    let modules: Vec<_> = scope.modules.iter().collect();
115                    let module = **input.choose(&modules)?;
116                    let id = scope.next_id();
117                    scope.instances.insert(id);
118                    Ok(InstanceNew { id, module })
119                });
120            }
121            if swarm.instance_drop && !scope.instances.is_empty() {
122                choices.push(|input, scope| {
123                    let instances: Vec<_> = scope.instances.iter().collect();
124                    let id = **input.choose(&instances)?;
125                    scope.instances.remove(&id);
126                    Ok(InstanceDrop { id })
127                });
128            }
129            if swarm.call_exported_func && !scope.instances.is_empty() {
130                choices.push(|input, scope| {
131                    let instances: Vec<_> = scope.instances.iter().collect();
132                    let instance = **input.choose(&instances)?;
133                    let nth = usize::arbitrary(input)?;
134                    Ok(CallExportedFunc { instance, nth })
135                });
136            }
137
138            if choices.is_empty() {
139                break;
140            }
141            let c = input.choose(&choices)?;
142            calls.push(c(input, &mut scope)?);
143        }
144
145        Ok(ApiCalls { calls })
146    }
147}