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::BTreeMap;
19 use std::mem;
20 use wasmparser::*;
21 
22 #[derive(Arbitrary, Debug)]
23 struct Swarm {
24     config_debug_info: bool,
25     module_new: bool,
26     module_drop: bool,
27     instance_new: bool,
28     instance_drop: bool,
29     call_exported_func: bool,
30 }
31 
32 /// A call to one of Wasmtime's public APIs.
33 #[derive(Arbitrary, Clone, Debug)]
34 #[allow(missing_docs)]
35 pub enum ApiCall {
36     ConfigNew,
37     ConfigDebugInfo(bool),
38     EngineNew,
39     StoreNew,
40     ModuleNew { id: usize, wasm: super::WasmOptTtf },
41     ModuleDrop { id: usize },
42     InstanceNew { id: usize, module: usize },
43     InstanceDrop { id: usize },
44     CallExportedFunc { instance: usize, nth: usize },
45 }
46 use ApiCall::*;
47 
48 #[derive(Default)]
49 struct Scope {
50     id_counter: usize,
51     predicted_rss: usize,
52     /// Map from a module id to the predicted amount of rss it will take to
53     /// instantiate.
54     modules: BTreeMap<usize, usize>,
55     /// Map from an instance id to the amount of rss it's expected to be using.
56     instances: BTreeMap<usize, usize>,
57 }
58 
59 impl Scope {
60     fn next_id(&mut self) -> usize {
61         let id = self.id_counter;
62         self.id_counter = id + 1;
63         id
64     }
65 }
66 
67 /// A sequence of API calls.
68 #[derive(Debug)]
69 pub struct ApiCalls {
70     /// The API calls.
71     pub calls: Vec<ApiCall>,
72 }
73 
74 impl Arbitrary for ApiCalls {
75     fn arbitrary(input: &mut Unstructured) -> arbitrary::Result<Self> {
76         let swarm = Swarm::arbitrary(input)?;
77         let mut calls = vec![];
78 
79         arbitrary_config(input, &swarm, &mut calls)?;
80         calls.push(EngineNew);
81         calls.push(StoreNew);
82 
83         let mut scope = Scope::default();
84         let max_rss = 1 << 30; // 1GB
85 
86         for _ in 0..input.arbitrary_len::<ApiCall>()? {
87             let mut choices: Vec<fn(_, &mut Scope) -> arbitrary::Result<ApiCall>> = vec![];
88 
89             if swarm.module_new {
90                 choices.push(|input, scope| {
91                     let id = scope.next_id();
92                     let wasm = super::WasmOptTtf::arbitrary(input)?;
93                     let predicted_rss = predict_rss(&wasm.wasm).unwrap_or(0);
94                     scope.modules.insert(id, predicted_rss);
95                     Ok(ModuleNew { id, wasm })
96                 });
97             }
98             if swarm.module_drop && !scope.modules.is_empty() {
99                 choices.push(|input, scope| {
100                     let modules: Vec<_> = scope.modules.keys().collect();
101                     let id = **input.choose(&modules)?;
102                     scope.modules.remove(&id);
103                     Ok(ModuleDrop { id })
104                 });
105             }
106             if swarm.instance_new && !scope.modules.is_empty() && scope.predicted_rss < max_rss {
107                 choices.push(|input, scope| {
108                     let modules: Vec<_> = scope.modules.iter().collect();
109                     let (&module, &predicted_rss) = *input.choose(&modules)?;
110                     let id = scope.next_id();
111                     scope.instances.insert(id, predicted_rss);
112                     scope.predicted_rss += predicted_rss;
113                     Ok(InstanceNew { id, module })
114                 });
115             }
116             if swarm.instance_drop && !scope.instances.is_empty() {
117                 choices.push(|input, scope| {
118                     let instances: Vec<_> = scope.instances.iter().collect();
119                     let (&id, &rss) = *input.choose(&instances)?;
120                     scope.instances.remove(&id);
121                     scope.predicted_rss -= rss;
122                     Ok(InstanceDrop { id })
123                 });
124             }
125             if swarm.call_exported_func && !scope.instances.is_empty() {
126                 choices.push(|input, scope| {
127                     let instances: Vec<_> = scope.instances.keys().collect();
128                     let instance = **input.choose(&instances)?;
129                     let nth = usize::arbitrary(input)?;
130                     Ok(CallExportedFunc { instance, nth })
131                 });
132             }
133 
134             if choices.is_empty() {
135                 break;
136             }
137             let c = input.choose(&choices)?;
138             calls.push(c(input, &mut scope)?);
139         }
140 
141         Ok(ApiCalls { calls })
142     }
143 
144     fn size_hint(depth: usize) -> (usize, Option<usize>) {
145         arbitrary::size_hint::recursion_guard(depth, |depth| {
146             arbitrary::size_hint::or(
147                 // This is the stuff we unconditionally need, which affects the
148                 // minimum size.
149                 arbitrary::size_hint::and(
150                     <Swarm as Arbitrary>::size_hint(depth),
151                     // `arbitrary_config` uses two bools when
152                     // `swarm.config_debug_info` is true.
153                     <(bool, bool) as Arbitrary>::size_hint(depth),
154                 ),
155                 // We can generate arbitrary `WasmOptTtf` instances, which have
156                 // no upper bound on the number of bytes they consume. This sets
157                 // the upper bound to `None`.
158                 <super::WasmOptTtf as Arbitrary>::size_hint(depth),
159             )
160         })
161     }
162 }
163 
164 fn arbitrary_config(
165     input: &mut Unstructured,
166     swarm: &Swarm,
167     calls: &mut Vec<ApiCall>,
168 ) -> arbitrary::Result<()> {
169     calls.push(ConfigNew);
170 
171     if swarm.config_debug_info && bool::arbitrary(input)? {
172         calls.push(ConfigDebugInfo(bool::arbitrary(input)?));
173     }
174 
175     // TODO: flags, features, and compilation strategy.
176 
177     Ok(())
178 }
179 
180 /// Attempt to heuristically predict how much rss instantiating the `wasm`
181 /// provided will take in wasmtime.
182 ///
183 /// The intention of this function is to prevent out-of-memory situations from
184 /// trivially instantiating a bunch of modules. We're basically taking any
185 /// random sequence of fuzz inputs and generating API calls, but if we
186 /// instantiate a million things we'd reasonably expect that to exceed the fuzz
187 /// limit of 2GB because, well, instantiation does take a bit of memory.
188 ///
189 /// This prediction will prevent new instances from being created once we've
190 /// created a bunch of instances. Once instances start being dropped, though,
191 /// it'll free up new slots to start making new instances.
192 fn predict_rss(wasm: &[u8]) -> Result<usize> {
193     let mut prediction = 0;
194     let mut reader = ModuleReader::new(wasm)?;
195     while !reader.eof() {
196         let section = reader.read()?;
197         match section.code {
198             // For each declared memory we'll have to map that all in, so add in
199             // the minimum amount of memory to our predicted rss.
200             SectionCode::Memory => {
201                 for entry in section.get_memory_section_reader()? {
202                     let initial = entry?.limits.initial as usize;
203                     prediction += initial * 64 * 1024;
204                 }
205             }
206 
207             // We'll need to allocate tables and space for table elements, and
208             // currently this is 3 pointers per table entry.
209             SectionCode::Table => {
210                 for entry in section.get_table_section_reader()? {
211                     let initial = entry?.limits.initial as usize;
212                     prediction += initial * 3 * mem::size_of::<usize>();
213                 }
214             }
215 
216             // ... and for now nothing else is counted. If we run into issues
217             // with the fuzzers though we can always try to take into account
218             // more things
219             _ => {}
220         }
221     }
222     Ok(prediction)
223 }
224