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