[fuzz] Add a meta-differential fuzz target (#4515)
* [fuzz] Add `Module` enum, refactor `ModuleConfig` This change adds a way to create either a single-instruction module or a regular (big) `wasm-smith` module. It has some slight refactorings in preparation for the use of this new code. * [fuzz] Add `DiffValue` for differential evaluation In order to evaluate functions with randomly-generated values, we needed a common way to generate these values. Using the Wasmtime `Val` type is not great because we would like to be able to implement various traits on the new value type, e.g., to convert `Into` and `From` boxed values of other engines we differentially fuzz against. This new type, `DiffValue`, gives us a common ground for all the conversions and comparisons between the other engine types. * [fuzz] Add interface for differential engines In order to randomly choose an engine to fuzz against, we expect all of the engines to meet a common interface. The traits in this commit allow us to instantiate a module from its binary form, evaluate exported functions, and (possibly) hash the exported items of the instance. This change has some missing pieces, though: - the `wasm-spec-interpreter` needs some work to be able to create instances, evaluate a function by name, and expose exported items - the `v8` engine is not implemented yet due to the complexity of its Rust lifetimes * [fuzz] Use `ModuleFeatures` instead of existing configuration When attempting to use both wasm-smith and single-instruction modules, there is a mismatch in how we communicate what an engine must be able to support. In the first case, we could use the `ModuleConfig`, a wrapper for wasm-smith's `SwarmConfig`, but single-instruction modules do not have a `SwarmConfig`--the many options simply don't apply. Here, we instead add `ModuleFeatures` and adapt a `ModuleConfig` to that. `ModuleFeatures` then becomes the way to communicate what features an engine must support to evaluate functions in a module. * [fuzz] Add a new fuzz target using the meta-differential oracle This change adds the `differential_meta` target to the list of fuzz targets. I expect that sometime soon this could replace the other `differential*` targets, as it almost checks all the things those check. The major missing piece is that currently it only chooses single-instruction modules instead of also generating arbitrary modules using `wasm-smith`. Also, this change adds the concept of an ignorable error: some differential engines will choke with certain inputs (e.g., `wasmi` might have an old opcode mapping) which we do not want to flag as fuzz bugs. Here we wrap those errors in `DiffIgnoreError` and then use a new helper trait, `DiffIgnorable`, to downcast and inspect the `anyhow` error to only panic on non-ignorable errors; the ignorable errors are converted to one of the `arbitrary::Error` variants, which we already ignore. * [fuzz] Compare `DiffValue` NaNs more leniently Because arithmetic NaNs can contain arbitrary payload bits, checking that two differential executions should produce the same result should relax the comparison of the `F32` and `F64` types (and eventually `V128` as well... TODO). This change adds several considerations, however, so that in the future we make the comparison a bit stricter, e.g., re: canonical NaNs. This change, however, just matches the current logic used by other fuzz targets. * review: allow hashing mutate the instance state @alexcrichton requested that the interface be adapted to accommodate Wasmtime's API, in which even reading from an instance could trigger mutation of the store. * review: refactor where configurations are made compatible See @alexcrichton's [suggestion](https://github.com/bytecodealliance/wasmtime/pull/4515#discussion_r928974376). * review: convert `DiffValueType` using `TryFrom` See @alexcrichton's [comment](https://github.com/bytecodealliance/wasmtime/pull/4515#discussion_r928962394). * review: adapt target implementation to Wasmtime-specific RHS This change is joint work with @alexcrichton to adapt the structure of the fuzz target to his comments [here](https://github.com/bytecodealliance/wasmtime/pull/4515#pullrequestreview-1073247791). This change: - removes `ModuleFeatures` and the `Module` enum (for big and small modules) - upgrades `SingleInstModule` to filter out cases that are not valid for a given `ModuleConfig` - adds `DiffEngine::name()` - constructs each `DiffEngine` using a `ModuleConfig`, eliminating `DiffIgnoreError` completely - prints an execution rate to the `differential_meta` target Still TODO: - `get_exported_function_signatures` could be re-written in terms of the Wasmtime API instead `wasmparser` - the fuzzer crashes eventually, we think due to the signal handler interference between OCaml and Wasmtime - the spec interpreter has several cases that we skip for now but could be fuzzed with further work Co-authored-by: Alex Crichton <alex@alexcrichton.com> * fix: avoid SIGSEGV by explicitly initializing OCaml runtime first * review: use Wasmtime's API to retrieve exported functions Co-authored-by: Alex Crichton <alex@alexcrichton.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
//! Generate Wasm modules that contain a single instruction.
|
||||
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use super::ModuleConfig;
|
||||
use arbitrary::Unstructured;
|
||||
use wasm_encoder::{
|
||||
CodeSection, ExportKind, ExportSection, Function, FunctionSection, Instruction, Module,
|
||||
TypeSection, ValType,
|
||||
@@ -13,17 +14,38 @@ const FUNCTION_NAME: &'static str = "test";
|
||||
///
|
||||
/// By explicitly defining the parameter and result types (versus generating the
|
||||
/// module directly), we can more easily generate values of the right type.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone)]
|
||||
pub struct SingleInstModule<'a> {
|
||||
instruction: Instruction<'a>,
|
||||
parameters: &'a [ValType],
|
||||
results: &'a [ValType],
|
||||
feature: fn(&ModuleConfig) -> bool,
|
||||
}
|
||||
|
||||
impl<'a> SingleInstModule<'a> {
|
||||
/// Generate a binary Wasm module with a single exported function, `test`,
|
||||
/// Choose a single-instruction module that matches `config`.
|
||||
pub fn new(u: &mut Unstructured<'a>, config: &mut ModuleConfig) -> arbitrary::Result<&'a Self> {
|
||||
// To avoid skipping modules unnecessarily during fuzzing, fix up the
|
||||
// `ModuleConfig` to match the inherent limits of a single-instruction
|
||||
// module.
|
||||
config.config.min_funcs = 1;
|
||||
config.config.max_funcs = 1;
|
||||
config.config.min_tables = 0;
|
||||
config.config.max_tables = 0;
|
||||
config.config.min_memories = 0;
|
||||
config.config.max_memories = 0;
|
||||
|
||||
// Only select instructions that match the `ModuleConfig`.
|
||||
let instructions = &INSTRUCTIONS
|
||||
.iter()
|
||||
.filter(|i| (i.feature)(config))
|
||||
.collect::<Vec<_>>();
|
||||
u.choose(&instructions[..]).copied()
|
||||
}
|
||||
|
||||
/// Encode a binary Wasm module with a single exported function, `test`,
|
||||
/// that executes the single instruction.
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut module = Module::new();
|
||||
|
||||
// Encode the type section.
|
||||
@@ -61,12 +83,6 @@ impl<'a> SingleInstModule<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Arbitrary<'a> for &SingleInstModule<'_> {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
u.choose(&INSTRUCTIONS)
|
||||
}
|
||||
}
|
||||
|
||||
// MACROS
|
||||
//
|
||||
// These macros make it a bit easier to define the instructions available for
|
||||
@@ -91,39 +107,52 @@ macro_rules! valtype {
|
||||
|
||||
macro_rules! binary {
|
||||
($inst:ident, $rust_ty:tt) => {
|
||||
binary! { $inst, valtype!($rust_ty), valtype!($rust_ty) }
|
||||
binary! { $inst, $rust_ty, $rust_ty }
|
||||
};
|
||||
($inst:ident, $arguments_ty:expr, $result_ty:expr) => {
|
||||
($inst:ident, $arguments_ty:tt, $result_ty:tt) => {
|
||||
SingleInstModule {
|
||||
instruction: Instruction::$inst,
|
||||
parameters: &[$arguments_ty, $arguments_ty],
|
||||
results: &[$result_ty],
|
||||
parameters: &[valtype!($arguments_ty), valtype!($arguments_ty)],
|
||||
results: &[valtype!($result_ty)],
|
||||
feature: |_| true,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! compare {
|
||||
($inst:ident, $rust_ty:tt) => {
|
||||
binary! { $inst, valtype!($rust_ty), ValType::I32 }
|
||||
binary! { $inst, $rust_ty, i32 }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! unary {
|
||||
($inst:ident, $rust_ty:tt) => {
|
||||
unary! { $inst, valtype!($rust_ty), valtype!($rust_ty) }
|
||||
unary! { $inst, $rust_ty, $rust_ty }
|
||||
};
|
||||
($inst:ident, $argument_ty:expr, $result_ty:expr) => {
|
||||
($inst:ident, $argument_ty:tt, $result_ty:tt) => {
|
||||
SingleInstModule {
|
||||
instruction: Instruction::$inst,
|
||||
parameters: &[$argument_ty],
|
||||
results: &[$result_ty],
|
||||
parameters: &[valtype!($argument_ty)],
|
||||
results: &[valtype!($result_ty)],
|
||||
feature: |_| true,
|
||||
}
|
||||
};
|
||||
($inst:ident, $argument_ty:tt, $result_ty:tt, $feature:expr) => {
|
||||
SingleInstModule {
|
||||
instruction: Instruction::$inst,
|
||||
parameters: &[valtype!($argument_ty)],
|
||||
results: &[valtype!($result_ty)],
|
||||
feature: $feature,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! convert {
|
||||
($inst:ident, $from_ty:tt -> $to_ty:tt) => {
|
||||
unary! { $inst, valtype!($from_ty), valtype!($to_ty) }
|
||||
unary! { $inst, $from_ty, $to_ty }
|
||||
};
|
||||
($inst:ident, $from_ty:tt -> $to_ty:tt, $feature:expr) => {
|
||||
unary! { $inst, $from_ty, $to_ty, $feature }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -172,7 +201,7 @@ static INSTRUCTIONS: &[SingleInstModule] = &[
|
||||
binary!(I64Rotr, i64),
|
||||
// Integer comparison.
|
||||
unary!(I32Eqz, i32),
|
||||
unary!(I64Eqz, ValType::I64, ValType::I32),
|
||||
unary!(I64Eqz, i64, i32),
|
||||
compare!(I32Eq, i32),
|
||||
compare!(I64Eq, i64),
|
||||
compare!(I32Ne, i32),
|
||||
@@ -236,11 +265,11 @@ static INSTRUCTIONS: &[SingleInstModule] = &[
|
||||
compare!(F32Ge, f32),
|
||||
compare!(F64Ge, f64),
|
||||
// Integer conversions ("to integer").
|
||||
unary!(I32Extend8S, i32),
|
||||
unary!(I32Extend16S, i32),
|
||||
unary!(I64Extend8S, i64),
|
||||
unary!(I64Extend16S, i64),
|
||||
convert!(I64Extend32S, i64 -> i64),
|
||||
unary!(I32Extend8S, i32, i32, |c| c.config.sign_extension_enabled),
|
||||
unary!(I32Extend16S, i32, i32, |c| c.config.sign_extension_enabled),
|
||||
unary!(I64Extend8S, i64, i64, |c| c.config.sign_extension_enabled),
|
||||
unary!(I64Extend16S, i64, i64, |c| c.config.sign_extension_enabled),
|
||||
convert!(I64Extend32S, i64 -> i64, |c| c.config.sign_extension_enabled),
|
||||
convert!(I32WrapI64, i64 -> i32),
|
||||
convert!(I64ExtendI32S, i32 -> i64),
|
||||
convert!(I64ExtendI32U, i32 -> i64),
|
||||
@@ -252,14 +281,14 @@ static INSTRUCTIONS: &[SingleInstModule] = &[
|
||||
convert!(I64TruncF32U, f32 -> i64),
|
||||
convert!(I64TruncF64S, f64 -> i64),
|
||||
convert!(I64TruncF64U, f64 -> i64),
|
||||
convert!(I32TruncSatF32S, f32 -> i32),
|
||||
convert!(I32TruncSatF32U, f32 -> i32),
|
||||
convert!(I32TruncSatF64S, f64 -> i32),
|
||||
convert!(I32TruncSatF64U, f64 -> i32),
|
||||
convert!(I64TruncSatF32S, f32 -> i64),
|
||||
convert!(I64TruncSatF32U, f32 -> i64),
|
||||
convert!(I64TruncSatF64S, f64 -> i64),
|
||||
convert!(I64TruncSatF64U, f64 -> i64),
|
||||
convert!(I32TruncSatF32S, f32 -> i32, |c| c.config.saturating_float_to_int_enabled),
|
||||
convert!(I32TruncSatF32U, f32 -> i32, |c| c.config.saturating_float_to_int_enabled),
|
||||
convert!(I32TruncSatF64S, f64 -> i32, |c| c.config.saturating_float_to_int_enabled),
|
||||
convert!(I32TruncSatF64U, f64 -> i32, |c| c.config.saturating_float_to_int_enabled),
|
||||
convert!(I64TruncSatF32S, f32 -> i64, |c| c.config.saturating_float_to_int_enabled),
|
||||
convert!(I64TruncSatF32U, f32 -> i64, |c| c.config.saturating_float_to_int_enabled),
|
||||
convert!(I64TruncSatF64S, f64 -> i64, |c| c.config.saturating_float_to_int_enabled),
|
||||
convert!(I64TruncSatF64U, f64 -> i64, |c| c.config.saturating_float_to_int_enabled),
|
||||
convert!(I32ReinterpretF32, f32 -> i32),
|
||||
convert!(I64ReinterpretF64, f64 -> i64),
|
||||
// Floating-point conversions ("to float").
|
||||
@@ -287,8 +316,9 @@ mod test {
|
||||
instruction: Instruction::I32Add,
|
||||
parameters: &[ValType::I32, ValType::I32],
|
||||
results: &[ValType::I32],
|
||||
feature: |_| true,
|
||||
};
|
||||
let wasm = sut.encode();
|
||||
let wasm = sut.to_bytes();
|
||||
let wat = wasmprinter::print_bytes(wasm).unwrap();
|
||||
assert_eq!(
|
||||
wat,
|
||||
@@ -307,7 +337,7 @@ mod test {
|
||||
#[test]
|
||||
fn instructions_encode_to_valid_modules() {
|
||||
for inst in INSTRUCTIONS {
|
||||
assert!(wat::parse_bytes(&inst.encode()).is_ok());
|
||||
assert!(wat::parse_bytes(&inst.to_bytes()).is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user