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