/src/wasm-tools/fuzz/src/lib.rs
Line | Count | Source |
1 | | use arbitrary::{Result, Unstructured}; |
2 | | use std::fmt::Debug; |
3 | | use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; |
4 | | use wasm_smith::{Component, Config, Module}; |
5 | | |
6 | | pub mod incremental_parse; |
7 | | pub mod mutate; |
8 | | pub mod no_traps; |
9 | | pub mod print; |
10 | | pub mod reencode; |
11 | | pub mod roundtrip; |
12 | | pub mod roundtrip_wit; |
13 | | pub mod text_parser; |
14 | | pub mod validate; |
15 | | pub mod validate_valid_module; |
16 | | pub mod wit64; |
17 | | |
18 | 10.7k | pub fn generate_valid_module( |
19 | 10.7k | u: &mut Unstructured, |
20 | 10.7k | configure: impl FnOnce(&mut Config, &mut Unstructured<'_>) -> Result<()>, |
21 | 10.7k | ) -> Result<(Vec<u8>, Config)> { |
22 | 10.7k | let mut config: Config = u.arbitrary()?; |
23 | | |
24 | | // These are disabled in the swarm config by default, but we want to test |
25 | | // them. Use the input data to determine whether these features are enabled. |
26 | 10.7k | config.canonicalize_nans = u.arbitrary()?; |
27 | 10.7k | config.custom_page_sizes_enabled = u.arbitrary()?; |
28 | 10.7k | config.wide_arithmetic_enabled = u.arbitrary()?; |
29 | 10.7k | config.shared_everything_threads_enabled = u.arbitrary()?; |
30 | | |
31 | 10.7k | configure(&mut config, u)?; |
32 | | |
33 | | // Use wasm-smith to generate an arbitrary module and convert it to wasm |
34 | | // bytes. |
35 | 10.7k | let mut module = Module::new(config.clone(), u)?; |
36 | 10.7k | let bytes = module.to_bytes(); |
37 | | |
38 | | // 10% of the time, ish, test that the `ensure_termination` method will |
39 | | // still produce a valid module. |
40 | 10.7k | if u.ratio(1, 10)? { |
41 | 8.57k | log::debug!("ensuring termination with 100 fuel"); |
42 | 8.57k | let _ = module.ensure_termination(100); |
43 | 2.13k | } |
44 | | |
45 | 10.7k | log_wasm(&bytes, &config); |
46 | | |
47 | 10.7k | Ok((bytes, config)) |
48 | 10.7k | } wasm_tools_fuzz::generate_valid_module::<wasm_tools_fuzz::validate_valid_module::run::{closure#1}>Line | Count | Source | 18 | 3.50k | pub fn generate_valid_module( | 19 | 3.50k | u: &mut Unstructured, | 20 | 3.50k | configure: impl FnOnce(&mut Config, &mut Unstructured<'_>) -> Result<()>, | 21 | 3.50k | ) -> Result<(Vec<u8>, Config)> { | 22 | 3.50k | let mut config: Config = u.arbitrary()?; | 23 | | | 24 | | // These are disabled in the swarm config by default, but we want to test | 25 | | // them. Use the input data to determine whether these features are enabled. | 26 | 3.50k | config.canonicalize_nans = u.arbitrary()?; | 27 | 3.50k | config.custom_page_sizes_enabled = u.arbitrary()?; | 28 | 3.50k | config.wide_arithmetic_enabled = u.arbitrary()?; | 29 | 3.50k | config.shared_everything_threads_enabled = u.arbitrary()?; | 30 | | | 31 | 3.50k | configure(&mut config, u)?; | 32 | | | 33 | | // Use wasm-smith to generate an arbitrary module and convert it to wasm | 34 | | // bytes. | 35 | 3.50k | let mut module = Module::new(config.clone(), u)?; | 36 | 3.47k | let bytes = module.to_bytes(); | 37 | | | 38 | | // 10% of the time, ish, test that the `ensure_termination` method will | 39 | | // still produce a valid module. | 40 | 3.47k | if u.ratio(1, 10)? { | 41 | 2.83k | log::debug!("ensuring termination with 100 fuel"); | 42 | 2.83k | let _ = module.ensure_termination(100); | 43 | 645 | } | 44 | | | 45 | 3.47k | log_wasm(&bytes, &config); | 46 | | | 47 | 3.47k | Ok((bytes, config)) | 48 | 3.50k | } |
wasm_tools_fuzz::generate_valid_module::<wasm_tools_fuzz::mutate::run::{closure#0}>Line | Count | Source | 18 | 3.91k | pub fn generate_valid_module( | 19 | 3.91k | u: &mut Unstructured, | 20 | 3.91k | configure: impl FnOnce(&mut Config, &mut Unstructured<'_>) -> Result<()>, | 21 | 3.91k | ) -> Result<(Vec<u8>, Config)> { | 22 | 3.91k | let mut config: Config = u.arbitrary()?; | 23 | | | 24 | | // These are disabled in the swarm config by default, but we want to test | 25 | | // them. Use the input data to determine whether these features are enabled. | 26 | 3.91k | config.canonicalize_nans = u.arbitrary()?; | 27 | 3.91k | config.custom_page_sizes_enabled = u.arbitrary()?; | 28 | 3.91k | config.wide_arithmetic_enabled = u.arbitrary()?; | 29 | 3.91k | config.shared_everything_threads_enabled = u.arbitrary()?; | 30 | | | 31 | 3.91k | configure(&mut config, u)?; | 32 | | | 33 | | // Use wasm-smith to generate an arbitrary module and convert it to wasm | 34 | | // bytes. | 35 | 3.91k | let mut module = Module::new(config.clone(), u)?; | 36 | 3.86k | let bytes = module.to_bytes(); | 37 | | | 38 | | // 10% of the time, ish, test that the `ensure_termination` method will | 39 | | // still produce a valid module. | 40 | 3.86k | if u.ratio(1, 10)? { | 41 | 3.03k | log::debug!("ensuring termination with 100 fuel"); | 42 | 3.03k | let _ = module.ensure_termination(100); | 43 | 834 | } | 44 | | | 45 | 3.86k | log_wasm(&bytes, &config); | 46 | | | 47 | 3.86k | Ok((bytes, config)) | 48 | 3.91k | } |
wasm_tools_fuzz::generate_valid_module::<wasm_tools_fuzz::no_traps::run::{closure#0}>Line | Count | Source | 18 | 1.54k | pub fn generate_valid_module( | 19 | 1.54k | u: &mut Unstructured, | 20 | 1.54k | configure: impl FnOnce(&mut Config, &mut Unstructured<'_>) -> Result<()>, | 21 | 1.54k | ) -> Result<(Vec<u8>, Config)> { | 22 | 1.54k | let mut config: Config = u.arbitrary()?; | 23 | | | 24 | | // These are disabled in the swarm config by default, but we want to test | 25 | | // them. Use the input data to determine whether these features are enabled. | 26 | 1.54k | config.canonicalize_nans = u.arbitrary()?; | 27 | 1.54k | config.custom_page_sizes_enabled = u.arbitrary()?; | 28 | 1.54k | config.wide_arithmetic_enabled = u.arbitrary()?; | 29 | 1.54k | config.shared_everything_threads_enabled = u.arbitrary()?; | 30 | | | 31 | 1.54k | configure(&mut config, u)?; | 32 | | | 33 | | // Use wasm-smith to generate an arbitrary module and convert it to wasm | 34 | | // bytes. | 35 | 1.54k | let mut module = Module::new(config.clone(), u)?; | 36 | 1.53k | let bytes = module.to_bytes(); | 37 | | | 38 | | // 10% of the time, ish, test that the `ensure_termination` method will | 39 | | // still produce a valid module. | 40 | 1.53k | if u.ratio(1, 10)? { | 41 | 1.25k | log::debug!("ensuring termination with 100 fuel"); | 42 | 1.25k | let _ = module.ensure_termination(100); | 43 | 278 | } | 44 | | | 45 | 1.53k | log_wasm(&bytes, &config); | 46 | | | 47 | 1.53k | Ok((bytes, config)) | 48 | 1.54k | } |
wasm_tools_fuzz::generate_valid_module::<wasm_tools_fuzz::reencode::run::{closure#0}>Line | Count | Source | 18 | 1.32k | pub fn generate_valid_module( | 19 | 1.32k | u: &mut Unstructured, | 20 | 1.32k | configure: impl FnOnce(&mut Config, &mut Unstructured<'_>) -> Result<()>, | 21 | 1.32k | ) -> Result<(Vec<u8>, Config)> { | 22 | 1.32k | let mut config: Config = u.arbitrary()?; | 23 | | | 24 | | // These are disabled in the swarm config by default, but we want to test | 25 | | // them. Use the input data to determine whether these features are enabled. | 26 | 1.32k | config.canonicalize_nans = u.arbitrary()?; | 27 | 1.32k | config.custom_page_sizes_enabled = u.arbitrary()?; | 28 | 1.32k | config.wide_arithmetic_enabled = u.arbitrary()?; | 29 | 1.32k | config.shared_everything_threads_enabled = u.arbitrary()?; | 30 | | | 31 | 1.32k | configure(&mut config, u)?; | 32 | | | 33 | | // Use wasm-smith to generate an arbitrary module and convert it to wasm | 34 | | // bytes. | 35 | 1.32k | let mut module = Module::new(config.clone(), u)?; | 36 | 1.32k | let bytes = module.to_bytes(); | 37 | | | 38 | | // 10% of the time, ish, test that the `ensure_termination` method will | 39 | | // still produce a valid module. | 40 | 1.32k | if u.ratio(1, 10)? { | 41 | 1.06k | log::debug!("ensuring termination with 100 fuel"); | 42 | 1.06k | let _ = module.ensure_termination(100); | 43 | 264 | } | 44 | | | 45 | 1.32k | log_wasm(&bytes, &config); | 46 | | | 47 | 1.32k | Ok((bytes, config)) | 48 | 1.32k | } |
wasm_tools_fuzz::generate_valid_module::<wasm_tools_fuzz::validate::validate_maybe_invalid_module::{closure#0}>Line | Count | Source | 18 | 498 | pub fn generate_valid_module( | 19 | 498 | u: &mut Unstructured, | 20 | 498 | configure: impl FnOnce(&mut Config, &mut Unstructured<'_>) -> Result<()>, | 21 | 498 | ) -> Result<(Vec<u8>, Config)> { | 22 | 498 | let mut config: Config = u.arbitrary()?; | 23 | | | 24 | | // These are disabled in the swarm config by default, but we want to test | 25 | | // them. Use the input data to determine whether these features are enabled. | 26 | 498 | config.canonicalize_nans = u.arbitrary()?; | 27 | 498 | config.custom_page_sizes_enabled = u.arbitrary()?; | 28 | 498 | config.wide_arithmetic_enabled = u.arbitrary()?; | 29 | 498 | config.shared_everything_threads_enabled = u.arbitrary()?; | 30 | | | 31 | 498 | configure(&mut config, u)?; | 32 | | | 33 | | // Use wasm-smith to generate an arbitrary module and convert it to wasm | 34 | | // bytes. | 35 | 498 | let mut module = Module::new(config.clone(), u)?; | 36 | 498 | let bytes = module.to_bytes(); | 37 | | | 38 | | // 10% of the time, ish, test that the `ensure_termination` method will | 39 | | // still produce a valid module. | 40 | 498 | if u.ratio(1, 10)? { | 41 | 388 | log::debug!("ensuring termination with 100 fuel"); | 42 | 388 | let _ = module.ensure_termination(100); | 43 | 110 | } | 44 | | | 45 | 498 | log_wasm(&bytes, &config); | 46 | | | 47 | 498 | Ok((bytes, config)) | 48 | 498 | } |
|
49 | | |
50 | 0 | pub fn generate_valid_component( |
51 | 0 | u: &mut Unstructured, |
52 | 0 | configure: impl FnOnce(&mut Config, &mut Unstructured<'_>) -> Result<()>, |
53 | 0 | ) -> Result<(Vec<u8>, Config)> { |
54 | 0 | let mut config: Config = u.arbitrary()?; |
55 | | |
56 | | // These are disabled in the swarm config by default, but we want to test |
57 | | // them. Use the input data to determine whether these features are enabled. |
58 | 0 | config.simd_enabled = u.arbitrary()?; |
59 | 0 | config.relaxed_simd_enabled = config.simd_enabled && u.arbitrary()?; |
60 | 0 | config.exceptions_enabled = u.arbitrary()?; |
61 | 0 | config.canonicalize_nans = u.arbitrary()?; |
62 | | |
63 | 0 | configure(&mut config, u)?; |
64 | | |
65 | | // Use wasm-smith to generate an arbitrary component and convert it to wasm |
66 | | // bytes. |
67 | 0 | let component = Component::new(config.clone(), u)?; |
68 | 0 | let bytes = component.to_bytes(); |
69 | | |
70 | 0 | log_wasm(&bytes, &config); |
71 | | |
72 | 0 | Ok((bytes, config)) |
73 | 0 | } |
74 | | |
75 | 3.97k | pub fn validator_for_config(config: &Config) -> wasmparser::Validator { |
76 | 3.97k | wasmparser::Validator::new_with_features(config.features()) |
77 | 3.97k | } |
78 | | |
79 | | // Optionally log the module and its configuration if we've gotten this |
80 | | // far. Note that we don't do this unconditionally to avoid slowing down |
81 | | // fuzzing, but this is expected to be enabled when debugging a failing |
82 | | // fuzzer. |
83 | 17.6k | pub fn log_wasm(wasm: &[u8], config: impl Debug) { |
84 | 17.6k | drop(env_logger::try_init()); |
85 | | |
86 | 17.6k | if !log::log_enabled!(log::Level::Debug) { |
87 | 17.6k | return; |
88 | 0 | } |
89 | | |
90 | | static CNT: AtomicUsize = AtomicUsize::new(0); |
91 | | |
92 | 0 | let i = CNT.fetch_add(1, SeqCst); |
93 | | |
94 | 0 | let wasm_file = format!("test{i}.wasm"); |
95 | 0 | let config_file = format!("test{i}.config"); |
96 | 0 | let wat_file = format!("test{i}.wat"); |
97 | | |
98 | 0 | log::debug!("writing test case to `{wasm_file}` ..."); |
99 | 0 | std::fs::write(&wasm_file, wasm).unwrap(); |
100 | 0 | std::fs::write(&config_file, format!("{config:#?}")).unwrap(); |
101 | 0 | if let Ok(wat) = wasmprinter::print_bytes(wasm) { |
102 | 0 | log::debug!("writing text format to `{wat_file}` ..."); |
103 | 0 | std::fs::write(&wat_file, wat).unwrap(); |
104 | 0 | } else { |
105 | 0 | drop(std::fs::remove_file(&wat_file)); |
106 | 0 | } |
107 | 17.6k | } wasm_tools_fuzz::log_wasm::<&wasmprinter::Config> Line | Count | Source | 83 | 5 | pub fn log_wasm(wasm: &[u8], config: impl Debug) { | 84 | 5 | drop(env_logger::try_init()); | 85 | | | 86 | 5 | if !log::log_enabled!(log::Level::Debug) { | 87 | 5 | return; | 88 | 0 | } | 89 | | | 90 | | static CNT: AtomicUsize = AtomicUsize::new(0); | 91 | | | 92 | 0 | let i = CNT.fetch_add(1, SeqCst); | 93 | | | 94 | 0 | let wasm_file = format!("test{i}.wasm"); | 95 | 0 | let config_file = format!("test{i}.config"); | 96 | 0 | let wat_file = format!("test{i}.wat"); | 97 | | | 98 | 0 | log::debug!("writing test case to `{wasm_file}` ..."); | 99 | 0 | std::fs::write(&wasm_file, wasm).unwrap(); | 100 | 0 | std::fs::write(&config_file, format!("{config:#?}")).unwrap(); | 101 | 0 | if let Ok(wat) = wasmprinter::print_bytes(wasm) { | 102 | 0 | log::debug!("writing text format to `{wat_file}` ..."); | 103 | 0 | std::fs::write(&wat_file, wat).unwrap(); | 104 | 0 | } else { | 105 | 0 | drop(std::fs::remove_file(&wat_file)); | 106 | 0 | } | 107 | 5 | } |
wasm_tools_fuzz::log_wasm::<&wasm_smith::config::Config> Line | Count | Source | 83 | 17.6k | pub fn log_wasm(wasm: &[u8], config: impl Debug) { | 84 | 17.6k | drop(env_logger::try_init()); | 85 | | | 86 | 17.6k | if !log::log_enabled!(log::Level::Debug) { | 87 | 17.6k | return; | 88 | 0 | } | 89 | | | 90 | | static CNT: AtomicUsize = AtomicUsize::new(0); | 91 | | | 92 | 0 | let i = CNT.fetch_add(1, SeqCst); | 93 | | | 94 | 0 | let wasm_file = format!("test{i}.wasm"); | 95 | 0 | let config_file = format!("test{i}.config"); | 96 | 0 | let wat_file = format!("test{i}.wat"); | 97 | | | 98 | 0 | log::debug!("writing test case to `{wasm_file}` ..."); | 99 | 0 | std::fs::write(&wasm_file, wasm).unwrap(); | 100 | 0 | std::fs::write(&config_file, format!("{config:#?}")).unwrap(); | 101 | 0 | if let Ok(wat) = wasmprinter::print_bytes(wasm) { | 102 | 0 | log::debug!("writing text format to `{wat_file}` ..."); | 103 | 0 | std::fs::write(&wat_file, wat).unwrap(); | 104 | 0 | } else { | 105 | 0 | drop(std::fs::remove_file(&wat_file)); | 106 | 0 | } | 107 | 17.6k | } |
wasm_tools_fuzz::log_wasm::<&str> Line | Count | Source | 83 | 4 | pub fn log_wasm(wasm: &[u8], config: impl Debug) { | 84 | 4 | drop(env_logger::try_init()); | 85 | | | 86 | 4 | if !log::log_enabled!(log::Level::Debug) { | 87 | 4 | return; | 88 | 0 | } | 89 | | | 90 | | static CNT: AtomicUsize = AtomicUsize::new(0); | 91 | | | 92 | 0 | let i = CNT.fetch_add(1, SeqCst); | 93 | | | 94 | 0 | let wasm_file = format!("test{i}.wasm"); | 95 | 0 | let config_file = format!("test{i}.config"); | 96 | 0 | let wat_file = format!("test{i}.wat"); | 97 | | | 98 | 0 | log::debug!("writing test case to `{wasm_file}` ..."); | 99 | 0 | std::fs::write(&wasm_file, wasm).unwrap(); | 100 | 0 | std::fs::write(&config_file, format!("{config:#?}")).unwrap(); | 101 | 0 | if let Ok(wat) = wasmprinter::print_bytes(wasm) { | 102 | 0 | log::debug!("writing text format to `{wat_file}` ..."); | 103 | 0 | std::fs::write(&wat_file, wat).unwrap(); | 104 | 0 | } else { | 105 | 0 | drop(std::fs::remove_file(&wat_file)); | 106 | 0 | } | 107 | 4 | } |
|
108 | | #[cfg(test)] |
109 | | mod test { |
110 | | use arbitrary::{Result, Unstructured}; |
111 | | use rand::{Rng, SeedableRng, rngs::SmallRng}; |
112 | | |
113 | | fn gen_until_pass(mut f: impl FnMut(&mut Unstructured<'_>) -> Result<bool>) -> bool { |
114 | | let mut rng = SmallRng::seed_from_u64(0); |
115 | | let mut buf = vec![0; 2048]; |
116 | | let n = 3000; |
117 | | for _ in 0..n { |
118 | | rng.fill_bytes(&mut buf); |
119 | | let mut u = Unstructured::new(&buf); |
120 | | |
121 | | if f(&mut u).unwrap() { |
122 | | return true; |
123 | | } |
124 | | } |
125 | | false |
126 | | } |
127 | | |
128 | | /// Runs `f` with random data until it returns `Ok(())` `iters` times. |
129 | | pub fn test_n_times( |
130 | | iters: u32, |
131 | | mut f: impl FnMut(&mut Unstructured<'_>) -> arbitrary::Result<()>, |
132 | | ) { |
133 | | let mut to_test = 0..iters; |
134 | | let ok = gen_until_pass(|b| { |
135 | | if f(b).is_ok() { |
136 | | Ok(to_test.next().is_none()) |
137 | | } else { |
138 | | Ok(false) |
139 | | } |
140 | | }); |
141 | | assert!(ok); |
142 | | } |
143 | | } |