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