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