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 arbitrary::{Arbitrary, Unstructured};
18 use std::collections::BTreeSet;
19 
20 #[derive(Arbitrary, Debug)]
21 struct Swarm {
22     config_debug_info: bool,
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, Clone, Debug)]
32 #[allow(missing_docs)]
33 pub enum ApiCall {
34     ConfigNew,
35     ConfigDebugInfo(bool),
36     EngineNew,
37     StoreNew,
38     ModuleNew { id: usize, wasm: super::WasmOptTtf },
39     ModuleDrop { id: usize },
40     InstanceNew { id: usize, module: usize },
41     InstanceDrop { id: usize },
42     CallExportedFunc { instance: usize, nth: usize },
43 }
44 use ApiCall::*;
45 
46 #[derive(Default)]
47 struct Scope {
48     id_counter: usize,
49     modules: BTreeSet<usize>,
50     instances: BTreeSet<usize>,
51 }
52 
53 impl Scope {
54     fn next_id(&mut self) -> usize {
55         let id = self.id_counter;
56         self.id_counter = id + 1;
57         id
58     }
59 }
60 
61 /// A sequence of API calls.
62 #[derive(Debug)]
63 pub struct ApiCalls {
64     /// The API calls.
65     pub calls: Vec<ApiCall>,
66 }
67 
68 impl Arbitrary for ApiCalls {
69     fn arbitrary(input: &mut Unstructured) -> arbitrary::Result<Self> {
70         let swarm = Swarm::arbitrary(input)?;
71         let mut calls = vec![];
72 
73         arbitrary_config(input, &swarm, &mut calls)?;
74         calls.push(EngineNew);
75         calls.push(StoreNew);
76 
77         let mut scope = Scope::default();
78 
79         for _ in 0..input.arbitrary_len::<ApiCall>()? {
80             let mut choices: Vec<fn(_, &mut Scope) -> arbitrary::Result<ApiCall>> = vec![];
81 
82             if swarm.module_new {
83                 choices.push(|input, scope| {
84                     let id = scope.next_id();
85                     scope.modules.insert(id);
86                     let wasm = super::WasmOptTtf::arbitrary(input)?;
87                     Ok(ModuleNew { id, wasm })
88                 });
89             }
90             if swarm.module_drop && !scope.modules.is_empty() {
91                 choices.push(|input, scope| {
92                     let modules: Vec<_> = scope.modules.iter().cloned().collect();
93                     let id = *input.choose(&modules)?;
94                     scope.modules.remove(&id);
95                     Ok(ModuleDrop { id })
96                 });
97             }
98             if swarm.instance_new && !scope.modules.is_empty() {
99                 choices.push(|input, scope| {
100                     let modules: Vec<_> = scope.modules.iter().cloned().collect();
101                     let module = *input.choose(&modules)?;
102                     let id = scope.next_id();
103                     scope.instances.insert(id);
104                     Ok(InstanceNew { id, module })
105                 });
106             }
107             if swarm.instance_drop && !scope.instances.is_empty() {
108                 choices.push(|input, scope| {
109                     let instances: Vec<_> = scope.instances.iter().cloned().collect();
110                     let id = *input.choose(&instances)?;
111                     scope.instances.remove(&id);
112                     Ok(InstanceDrop { id })
113                 });
114             }
115             if swarm.call_exported_func && !scope.instances.is_empty() {
116                 choices.push(|input, scope| {
117                     let instances: Vec<_> = scope.instances.iter().cloned().collect();
118                     let instance = *input.choose(&instances)?;
119                     let nth = usize::arbitrary(input)?;
120                     Ok(CallExportedFunc { instance, nth })
121                 });
122             }
123 
124             if choices.is_empty() {
125                 break;
126             }
127             let c = input.choose(&choices)?;
128             calls.push(c(input, &mut scope)?);
129         }
130 
131         Ok(ApiCalls { calls })
132     }
133 }
134 
135 fn arbitrary_config(
136     input: &mut Unstructured,
137     swarm: &Swarm,
138     calls: &mut Vec<ApiCall>,
139 ) -> arbitrary::Result<()> {
140     calls.push(ConfigNew);
141 
142     if swarm.config_debug_info && bool::arbitrary(input)? {
143         calls.push(ConfigDebugInfo(bool::arbitrary(input)?));
144     }
145 
146     // TODO: flags, features, and compilation strategy.
147 
148     Ok(())
149 }
150