Skip to main content

wasm_smith/
lib.rs

1//! A WebAssembly test case generator.
2//!
3//! ## Usage
4//!
5//! First, use [`cargo fuzz`](https://github.com/rust-fuzz/cargo-fuzz) to define
6//! a new fuzz target:
7//!
8//! ```shell
9//! $ cargo fuzz add my_wasm_smith_fuzz_target
10//! ```
11//!
12//! Next, add `wasm-smith` to your dependencies:
13//!
14//! ```shell
15//! $ cargo add wasm-smith
16//! ```
17//!
18//! Then, define your fuzz target so that it takes arbitrary
19//! `wasm_smith::Module`s as an argument, convert the module into serialized
20//! Wasm bytes via the `to_bytes` method, and then feed it into your system:
21//!
22//! ```no_run
23//! // fuzz/fuzz_targets/my_wasm_smith_fuzz_target.rs
24//!
25//! #![no_main]
26//!
27//! # #[cfg(not(target_family = "wasm"))] mod x {
28//! use libfuzzer_sys::fuzz_target;
29//! use wasm_smith::Module;
30//!
31//! fuzz_target!(|module: Module| {
32//!     let wasm_bytes = module.to_bytes();
33//!
34//!     // Your code here...
35//! });
36//! # }
37//! ```
38//!
39//! Finally, start fuzzing:
40//!
41//! ```shell
42//! $ cargo fuzz run my_wasm_smith_fuzz_target
43//! ```
44//!
45//! > **Note:** For a real world example, also check out [the `validate` fuzz
46//! > target](https://github.com/bytecodealliance/wasm-tools/blob/main/fuzz/src/validate.rs)
47//! > defined in this repository. Using the `wasmparser` crate, it checks that
48//! > every module generated by `wasm-smith` validates successfully.
49//!
50//! ## Design
51//!
52//! The design and implementation strategy of wasm-smith is outlined in
53//! [this article](https://fitzgeraldnick.com/2020/08/24/writing-a-test-case-generator.html).
54
55#![cfg_attr(docsrs, feature(doc_cfg))]
56#![deny(missing_docs, missing_debug_implementations)]
57// Needed for the `instructions!` macro in `src/code_builder.rs`.
58#![recursion_limit = "1024"]
59
60#[cfg(feature = "component-model")]
61mod component;
62mod config;
63mod core;
64
65pub use crate::core::{InstructionKind, InstructionKinds, Module};
66use arbitrary::{Result, Unstructured};
67#[cfg(feature = "component-model")]
68pub use component::Component;
69pub use config::{Config, MemoryOffsetChoices};
70use std::{collections::HashSet, fmt::Write, str};
71
72#[doc(hidden)]
73pub use config::InternalOptionalConfig;
74
75/// Do something an arbitrary number of times.
76///
77/// The callback can return `false` to exit the loop early.
78pub(crate) fn arbitrary_loop<'a>(
79    u: &mut Unstructured<'a>,
80    min: usize,
81    max: usize,
82    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
83) -> Result<()> {
84    assert!(max >= min);
85    for _ in 0..min {
86        if !f(u)? {
87            return Err(arbitrary::Error::IncorrectFormat);
88        }
89    }
90    for _ in 0..(max - min) {
91        let keep_going = u.arbitrary().unwrap_or(false);
92        if !keep_going {
93            break;
94        }
95
96        if !f(u)? {
97            break;
98        }
99    }
100
101    Ok(())
102}
103
104// Mirror what happens in `Arbitrary for String`, but do so with a clamped size.
105pub(crate) fn limited_str<'a>(max_size: usize, u: &mut Unstructured<'a>) -> Result<&'a str> {
106    let size = u.arbitrary_len::<u8>()?;
107    let size = std::cmp::min(size, max_size);
108    match str::from_utf8(u.peek_bytes(size).unwrap()) {
109        Ok(s) => {
110            u.bytes(size).unwrap();
111            Ok(s)
112        }
113        Err(e) => {
114            let i = e.valid_up_to();
115            let valid = u.bytes(i).unwrap();
116            let s = str::from_utf8(valid).unwrap();
117            Ok(s)
118        }
119    }
120}
121
122pub(crate) fn limited_string(max_size: usize, u: &mut Unstructured) -> Result<String> {
123    Ok(limited_str(max_size, u)?.into())
124}
125
126pub(crate) fn unique_string(
127    max_size: usize,
128    names: &mut HashSet<String>,
129    u: &mut Unstructured,
130) -> Result<String> {
131    let mut name = limited_string(max_size, u)?;
132    while names.contains(&name) {
133        write!(&mut name, "{}", names.len()).unwrap();
134    }
135    names.insert(name.clone());
136    Ok(name)
137}
138
139#[cfg(feature = "component-model")]
140pub(crate) fn unique_kebab_string(
141    max_size: usize,
142    names: &mut HashSet<String>,
143    u: &mut Unstructured,
144) -> Result<String> {
145    let size = std::cmp::min(u.arbitrary_len::<u8>()?, max_size);
146    let mut name = String::with_capacity(size);
147    let mut empty_segment = true;
148    for i in 0..size {
149        name.push(match u.int_in_range::<u8>(0..=36)? {
150            x if (0..26).contains(&x) => {
151                empty_segment = false;
152                (b'a' + x) as char
153            }
154            x if (26..36).contains(&x) => {
155                empty_segment = false;
156                if i == 0 {
157                    (b'a' + (x - 26)) as char
158                } else {
159                    (b'0' + (x - 26)) as char
160                }
161            }
162            x if x == 36 => {
163                if empty_segment {
164                    empty_segment = false;
165                    'a'
166                } else {
167                    empty_segment = true;
168                    '-'
169                }
170            }
171            _ => unreachable!(),
172        });
173    }
174
175    if name.is_empty() || name.ends_with('-') {
176        name.push('a');
177    }
178
179    while names.contains(&name) {
180        write!(&mut name, "{}", names.len()).unwrap();
181    }
182
183    names.insert(name.clone());
184
185    Ok(name)
186}
187
188#[cfg(feature = "component-model")]
189pub(crate) fn unique_url(
190    max_size: usize,
191    names: &mut HashSet<String>,
192    u: &mut Unstructured,
193) -> Result<String> {
194    let path = unique_kebab_string(max_size, names, u)?;
195    Ok(format!("https://example.com/{path}"))
196}