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::HashSet;
19 
20 struct Swarm {
21     config_debug_info: bool,
22     module_new: bool,
23     module_drop: bool,
24     instance_new: bool,
25     instance_drop: bool,
26     call_exported_func: bool,
27 }
28 
29 impl Arbitrary for Swarm {
30     fn arbitrary<U>(input: &mut U) -> Result<Self, U::Error>
31     where
32         U: Unstructured + ?Sized,
33     {
34         Ok(Swarm {
35             config_debug_info: bool::arbitrary(input)?,
36             module_new: bool::arbitrary(input)?,
37             module_drop: bool::arbitrary(input)?,
38             instance_new: bool::arbitrary(input)?,
39             instance_drop: bool::arbitrary(input)?,
40             call_exported_func: bool::arbitrary(input)?,
41         })
42     }
43 }
44 
45 /// A call to one of Wasmtime's public APIs.
46 #[derive(Clone, Debug)]
47 #[allow(missing_docs)]
48 pub enum ApiCall {
49     ConfigNew,
50     ConfigDebugInfo(bool),
51     EngineNew,
52     StoreNew,
53     ModuleNew { id: usize, wasm: super::WasmOptTtf },
54     ModuleDrop { id: usize },
55     InstanceNew { id: usize, module: usize },
56     InstanceDrop { id: usize },
57     CallExportedFunc { instance: usize, nth: usize },
58 }
59 use ApiCall::*;
60 
61 #[derive(Default)]
62 struct Scope {
63     id_counter: usize,
64     modules: HashSet<usize>,
65     instances: HashSet<usize>,
66 }
67 
68 impl Scope {
69     fn next_id(&mut self) -> usize {
70         let id = self.id_counter;
71         self.id_counter = id + 1;
72         id
73     }
74 }
75 
76 /// A sequence of API calls.
77 #[derive(Debug)]
78 pub struct ApiCalls {
79     /// The API calls.
80     pub calls: Vec<ApiCall>,
81 }
82 
83 impl Arbitrary for ApiCalls {
84     fn arbitrary<U>(input: &mut U) -> Result<Self, U::Error>
85     where
86         U: Unstructured + ?Sized,
87     {
88         let swarm = Swarm::arbitrary(input)?;
89         let mut calls = vec![];
90 
91         arbitrary_config(input, &swarm, &mut calls)?;
92         calls.push(EngineNew);
93         calls.push(StoreNew);
94 
95         let mut scope = Scope::default();
96 
97         for _ in 0..input.container_size()? {
98             let mut choices: Vec<fn(_, &mut Scope) -> Result<ApiCall, U::Error>> = vec![];
99 
100             if swarm.module_new {
101                 choices.push(|input, scope| {
102                     let id = scope.next_id();
103                     scope.modules.insert(id);
104                     let wasm = super::WasmOptTtf::arbitrary(input)?;
105                     Ok(ModuleNew { id, wasm })
106                 });
107             }
108             if swarm.module_drop && !scope.modules.is_empty() {
109                 choices.push(|input, scope| {
110                     let modules: Vec<_> = scope.modules.iter().cloned().collect();
111                     let id = arbitrary_choice(input, &modules)?.cloned().unwrap();
112                     scope.modules.remove(&id);
113                     Ok(ModuleDrop { id })
114                 });
115             }
116             if swarm.instance_new && !scope.modules.is_empty() {
117                 choices.push(|input, scope| {
118                     let modules: Vec<_> = scope.modules.iter().cloned().collect();
119                     let module = arbitrary_choice(input, &modules)?.cloned().unwrap();
120                     let id = scope.next_id();
121                     scope.instances.insert(id);
122                     Ok(InstanceNew { id, module })
123                 });
124             }
125             if swarm.instance_drop && !scope.instances.is_empty() {
126                 choices.push(|input, scope| {
127                     let instances: Vec<_> = scope.instances.iter().cloned().collect();
128                     let id = arbitrary_choice(input, &instances)?.cloned().unwrap();
129                     scope.instances.remove(&id);
130                     Ok(InstanceDrop { id })
131                 });
132             }
133             if swarm.call_exported_func && !scope.instances.is_empty() {
134                 choices.push(|input, scope| {
135                     let instances: Vec<_> = scope.instances.iter().cloned().collect();
136                     let instance = arbitrary_choice(input, &instances)?.cloned().unwrap();
137                     let nth = usize::arbitrary(input)?;
138                     Ok(CallExportedFunc { instance, nth })
139                 });
140             }
141 
142             if let Some(c) = arbitrary_choice(input, &choices)? {
143                 calls.push(c(input, &mut scope)?);
144             } else {
145                 break;
146             }
147         }
148 
149         Ok(ApiCalls { calls })
150     }
151 }
152 
153 fn arbitrary_choice<'a, T, U>(input: &mut U, choices: &'a [T]) -> Result<Option<&'a T>, U::Error>
154 where
155     U: Unstructured + ?Sized,
156 {
157     if choices.is_empty() {
158         Ok(None)
159     } else {
160         let i = usize::arbitrary(input)? % choices.len();
161         Ok(Some(&choices[i]))
162     }
163 }
164 
165 fn arbitrary_config<U>(
166     input: &mut U,
167     swarm: &Swarm,
168     calls: &mut Vec<ApiCall>,
169 ) -> Result<(), U::Error>
170 where
171     U: Unstructured + ?Sized,
172 {
173     calls.push(ConfigNew);
174 
175     if swarm.config_debug_info && bool::arbitrary(input)? {
176         calls.push(ConfigDebugInfo(bool::arbitrary(input)?));
177     }
178 
179     // TODO: flags, features, and compilation strategy.
180 
181     Ok(())
182 }
183