1 //! Generate a Wasm module and the configuration for generating it. 2 3 use arbitrary::{Arbitrary, Unstructured}; 4 use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; 5 6 /// Default module-level configuration for fuzzing Wasmtime. 7 /// 8 /// Internally this uses `wasm-smith`'s own `Config` but we further refine 9 /// the defaults here as well. 10 #[derive(Debug, Clone)] 11 #[expect(missing_docs, reason = "self-describing fields")] 12 pub struct ModuleConfig { 13 pub config: wasm_smith::Config, 14 15 // These knobs aren't exposed in `wasm-smith` at this time but are exposed 16 // in our `*.wast` testing so keep knobs here so they can be read during 17 // config-to-`wasmtime::Config` translation. 18 pub function_references_enabled: bool, 19 pub component_model_async: bool, 20 pub component_model_async_builtins: bool, 21 pub component_model_async_stackful: bool, 22 pub component_model_threading: bool, 23 pub component_model_error_context: bool, 24 pub component_model_gc: bool, 25 pub component_model_map: bool, 26 pub component_model_fixed_length_lists: bool, 27 pub legacy_exceptions: bool, 28 pub shared_memory: bool, 29 } 30 31 impl<'a> Arbitrary<'a> for ModuleConfig { 32 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<ModuleConfig> { 33 let mut config = wasm_smith::Config::arbitrary(u)?; 34 35 // This list is intended to be the definitive source of truth for 36 // what's at least possible to fuzz within Wasmtime. This is a 37 // combination of features in `wasm-smith` where some proposals are 38 // on-by-default (as determined by fuzz input) and others are 39 // off-by-default (as they aren't stage4+). Wasmtime will default-fuzz 40 // proposals that a pre-stage-4 to test our own implementation. Wasmtime 41 // might also unconditionally disable proposals that it doesn't 42 // implement yet which are stage4+. This is intended to be an exhaustive 43 // list of all the wasm proposals that `wasm-smith` supports and the 44 // fuzzing status within Wasmtime too. 45 let _ = config.multi_value_enabled; 46 let _ = config.saturating_float_to_int_enabled; 47 let _ = config.sign_extension_ops_enabled; 48 let _ = config.bulk_memory_enabled; 49 let _ = config.reference_types_enabled; 50 let _ = config.simd_enabled; 51 let _ = config.relaxed_simd_enabled; 52 let _ = config.tail_call_enabled; 53 let _ = config.extended_const_enabled; 54 let _ = config.gc_enabled; 55 let _ = config.exceptions_enabled; 56 config.custom_page_sizes_enabled = u.arbitrary()?; 57 config.wide_arithmetic_enabled = u.arbitrary()?; 58 config.memory64_enabled = u.ratio(1, 20)?; 59 // Fuzzing threads is an open question. Even without actual parallel 60 // threads `SharedMemory` still poses a problem where it isn't hooked 61 // into resource limits the same way `Memory` is. Overall not clear what 62 // to do so it's disabled for now. 63 config.threads_enabled = false; 64 // Allow multi-memory but make it unlikely 65 if u.ratio(1, 20)? { 66 config.max_memories = config.max_memories.max(2); 67 } else { 68 config.max_memories = 1; 69 } 70 // ... NB: if you add something above this line please be sure to update 71 // `docs/stability-wasm-proposals.md` 72 73 // We get better differential execution when we disallow traps, so we'll 74 // do that most of the time. 75 config.disallow_traps = u.ratio(9, 10)?; 76 77 Ok(ModuleConfig { 78 component_model_async: false, 79 component_model_async_builtins: false, 80 component_model_async_stackful: false, 81 component_model_threading: false, 82 component_model_error_context: false, 83 component_model_gc: false, 84 component_model_map: false, 85 component_model_fixed_length_lists: false, 86 legacy_exceptions: false, 87 shared_memory: false, 88 function_references_enabled: config.gc_enabled, 89 config, 90 }) 91 } 92 } 93 94 impl ModuleConfig { 95 /// Uses this configuration and the supplied source of data to generate a 96 /// Wasm module. 97 /// 98 /// If a `default_fuel` is provided, the resulting module will be configured 99 /// to ensure termination; as doing so will add an additional global to the 100 /// module, the pooling allocator, if configured, must also have its globals 101 /// limit updated. 102 pub fn generate( 103 &self, 104 input: &mut Unstructured<'_>, 105 default_fuel: Option<u32>, 106 ) -> arbitrary::Result<wasm_smith::Module> { 107 crate::init_fuzzing(); 108 109 // If requested, save `*.{dna,json}` files for recreating this module 110 // in wasm-tools alone. 111 let input_before = if log::log_enabled!(log::Level::Debug) { 112 let len = input.len(); 113 Some(input.peek_bytes(len).unwrap().to_vec()) 114 } else { 115 None 116 }; 117 118 let mut module = wasm_smith::Module::new(self.config.clone(), input)?; 119 120 if let Some(before) = input_before { 121 static GEN_CNT: AtomicUsize = AtomicUsize::new(0); 122 let used = before.len() - input.len(); 123 let i = GEN_CNT.fetch_add(1, Relaxed); 124 let dna = format!("testcase{i}.dna"); 125 let config = format!("testcase{i}.json"); 126 log::debug!("writing `{dna}` and `{config}`"); 127 std::fs::write(&dna, &before[..used]).unwrap(); 128 std::fs::write(&config, serde_json::to_string_pretty(&self.config).unwrap()).unwrap(); 129 } 130 131 if let Some(default_fuel) = default_fuel { 132 module.ensure_termination(default_fuel).unwrap(); 133 } 134 135 Ok(module) 136 } 137 } 138