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 
17 use crate::generators::Config;
18 use arbitrary::{Arbitrary, Unstructured};
19 use std::collections::BTreeSet;
20 
21 #[derive(Arbitrary, Debug)]
22 struct 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")]
33 pub 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 }
41 use ApiCall::*;
42 
43 struct Scope {
44     id_counter: usize,
45     modules: BTreeSet<usize>,
46     instances: BTreeSet<usize>,
47     config: Config,
48 }
49 
50 impl Scope {
next_id(&mut self) -> usize51     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)]
60 pub struct ApiCalls {
61     /// The API calls.
62     pub calls: Vec<ApiCall>,
63 }
64 
65 impl<'a> Arbitrary<'a> for ApiCalls {
arbitrary(input: &mut Unstructured<'a>) -> arbitrary::Result<Self>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 }
148