Port v8 fuzzer to the new framework (#4739)
* Port v8 fuzzer to the new framework This commit aims to improve the support for the new "meta" differential fuzzer added in #4515 by ensuring that all existing differential fuzzing is migrated to this new fuzzer. This PR includes features such as: * The V8 differential execution is migrated to the new framework. * `Config::set_differential_config` no longer force-disables wasm features, instead allowing them to be enabled as per the fuzz input. * `DiffInstance::{hash, hash}` was replaced with `DiffInstance::get_{memory,global}` to allow more fine-grained assertions. * Support for `FuncRef` and `ExternRef` have been added to `DiffValue` and `DiffValueType`. For now though generating an arbitrary `ExternRef` and `FuncRef` simply generates a null value. * Arbitrary `DiffValue::{F32,F64}` values are guaranteed to use canonical NaN representations to fix an issue with v8 where with the v8 engine we can't communicate non-canonical NaN values through JS. * `DiffEngine::evaluate` allows "successful failure" for cases where engines can't support that particular invocation, for example v8 can't support `v128` arguments or return values. * Smoke tests were added for each engine to ensure that a simple wasm module works at PR-time. * Statistics printed from the main fuzzer now include percentage-rates for chosen engines as well as percentage rates for styles-of-module. There's also a few small refactorings here and there but mostly just things I saw along the way. * Update the fuzzing README
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
//! Define the interface for differential evaluation of Wasm functions.
|
||||
|
||||
use crate::generators::{Config, DiffValue};
|
||||
use crate::generators::{Config, DiffValue, DiffValueType, WasmtimeConfig};
|
||||
use crate::oracles::{diff_wasmi::WasmiEngine, diff_wasmtime::WasmtimeEngine};
|
||||
use anyhow::Error;
|
||||
use arbitrary::Unstructured;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use wasmtime::Trap;
|
||||
|
||||
/// Pick one of the engines implemented in this module that is compatible with
|
||||
/// the Wasm features passed in `features` and, when fuzzing Wasmtime against
|
||||
@@ -14,31 +15,36 @@ pub fn choose(
|
||||
) -> arbitrary::Result<Box<dyn DiffEngine>> {
|
||||
// Filter out any engines that cannot match the given configuration.
|
||||
let mut engines: Vec<Box<dyn DiffEngine>> = vec![];
|
||||
let mut config: Config = u.arbitrary()?; // TODO change to WasmtimeConfig
|
||||
config.make_compatible_with(&existing_config);
|
||||
if let Result::Ok(e) = WasmtimeEngine::new(&config) {
|
||||
engines.push(e)
|
||||
let mut config2: WasmtimeConfig = u.arbitrary()?; // TODO change to WasmtimeConfig
|
||||
config2.make_compatible_with(&existing_config.wasmtime);
|
||||
let config2 = Config {
|
||||
wasmtime: config2,
|
||||
module_config: existing_config.module_config.clone(),
|
||||
};
|
||||
if let Result::Ok(e) = WasmtimeEngine::new(config2) {
|
||||
engines.push(Box::new(e))
|
||||
}
|
||||
if let Result::Ok(e) = WasmiEngine::new(&existing_config.module_config) {
|
||||
engines.push(e)
|
||||
engines.push(Box::new(e))
|
||||
}
|
||||
#[cfg(feature = "fuzz-spec-interpreter")]
|
||||
if let Result::Ok(e) =
|
||||
crate::oracles::diff_spec::SpecInterpreter::new(&existing_config.module_config)
|
||||
{
|
||||
engines.push(e)
|
||||
engines.push(Box::new(e))
|
||||
}
|
||||
#[cfg(not(any(windows, target_arch = "s390x")))]
|
||||
if let Result::Ok(e) = crate::oracles::diff_v8::V8Engine::new(&existing_config.module_config) {
|
||||
engines.push(Box::new(e))
|
||||
}
|
||||
|
||||
// Choose one of the remaining engines.
|
||||
if !engines.is_empty() {
|
||||
let index: usize = u.int_in_range(0..=engines.len() - 1)?;
|
||||
let engine = engines.swap_remove(index);
|
||||
log::debug!("selected engine: {}", engine.name());
|
||||
Ok(engine)
|
||||
} else {
|
||||
panic!("no engines to pick from");
|
||||
// Err(arbitrary::Error::EmptyChoose)
|
||||
}
|
||||
// Use the input of the fuzzer to pick an engine that we'll be fuzzing
|
||||
// Wasmtime against.
|
||||
assert!(!engines.is_empty());
|
||||
let index: usize = u.int_in_range(0..=engines.len() - 1)?;
|
||||
let engine = engines.swap_remove(index);
|
||||
log::debug!("selected engine: {}", engine.name());
|
||||
Ok(engine)
|
||||
}
|
||||
|
||||
/// Provide a way to instantiate Wasm modules.
|
||||
@@ -47,7 +53,11 @@ pub trait DiffEngine {
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Create a new instance with the given engine.
|
||||
fn instantiate(&self, wasm: &[u8]) -> anyhow::Result<Box<dyn DiffInstance>>;
|
||||
fn instantiate(&mut self, wasm: &[u8]) -> anyhow::Result<Box<dyn DiffInstance>>;
|
||||
|
||||
/// Tests that the wasmtime-originating `trap` matches the error this engine
|
||||
/// generated.
|
||||
fn assert_error_match(&self, trap: &Trap, err: Error);
|
||||
}
|
||||
|
||||
/// Provide a way to evaluate Wasm functions--a Wasm instance implemented by a
|
||||
@@ -57,19 +67,111 @@ pub trait DiffInstance {
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Evaluate an exported function with the given values.
|
||||
///
|
||||
/// Any error, such as a trap, should be returned through an `Err`. If this
|
||||
/// engine cannot invoke the function signature then `None` should be
|
||||
/// returned and this invocation will be skipped.
|
||||
fn evaluate(
|
||||
&mut self,
|
||||
function_name: &str,
|
||||
arguments: &[DiffValue],
|
||||
) -> anyhow::Result<Vec<DiffValue>>;
|
||||
results: &[DiffValueType],
|
||||
) -> anyhow::Result<Option<Vec<DiffValue>>>;
|
||||
|
||||
/// Check if instances of this kind are actually hashable--not all engines
|
||||
/// support this.
|
||||
fn is_hashable(&self) -> bool;
|
||||
/// Attempts to return the value of the specified global, returning `None`
|
||||
/// if this engine doesn't support retrieving globals at this time.
|
||||
fn get_global(&mut self, name: &str, ty: DiffValueType) -> Option<DiffValue>;
|
||||
|
||||
/// If the instance `is_hashable()`, this method will try to hash the
|
||||
/// following exported items in the instance: globals, memory.
|
||||
///
|
||||
/// TODO allow more types of hashers.
|
||||
fn hash(&mut self, state: &mut DefaultHasher) -> anyhow::Result<()>;
|
||||
/// Same as `get_global` but for memory.
|
||||
fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>>;
|
||||
}
|
||||
|
||||
/// Initialize any global state associated with runtimes that may be
|
||||
/// differentially executed against.
|
||||
pub fn setup_engine_runtimes() {
|
||||
#[cfg(feature = "fuzz-spec-interpreter")]
|
||||
crate::oracles::diff_spec::setup_ocaml_runtime();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn smoke_test_engine<T>(mk_engine: impl Fn(Config) -> anyhow::Result<T>)
|
||||
where
|
||||
T: DiffEngine,
|
||||
{
|
||||
use arbitrary::Arbitrary;
|
||||
use rand::prelude::*;
|
||||
|
||||
let mut rng = SmallRng::seed_from_u64(0);
|
||||
let mut buf = vec![0; 2048];
|
||||
let n = 100;
|
||||
for _ in 0..n {
|
||||
rng.fill_bytes(&mut buf);
|
||||
let u = Unstructured::new(&buf);
|
||||
let mut config = match Config::arbitrary_take_rest(u) {
|
||||
Ok(config) => config,
|
||||
Err(_) => continue,
|
||||
};
|
||||
// This will ensure that wasmtime, which uses this configuration
|
||||
// settings, can guaranteed instantiate a module.
|
||||
config.set_differential_config();
|
||||
|
||||
// Configure settings to ensure that any filters in engine constructors
|
||||
// try not to filter out this `Config`.
|
||||
config.module_config.config.reference_types_enabled = false;
|
||||
config.module_config.config.bulk_memory_enabled = false;
|
||||
config.module_config.config.memory64_enabled = false;
|
||||
config.module_config.config.threads_enabled = false;
|
||||
config.module_config.config.simd_enabled = false;
|
||||
config.module_config.config.min_funcs = 1;
|
||||
config.module_config.config.max_funcs = 1;
|
||||
config.module_config.config.min_tables = 0;
|
||||
config.module_config.config.max_tables = 0;
|
||||
|
||||
let mut engine = match mk_engine(config) {
|
||||
Ok(engine) => engine,
|
||||
Err(e) => {
|
||||
println!("skip {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let wasm = wat::parse_str(
|
||||
r#"
|
||||
(module
|
||||
(func (export "add") (param i32 i32) (result i32)
|
||||
local.get 0
|
||||
local.get 1
|
||||
i32.add)
|
||||
|
||||
(global (export "global") i32 i32.const 1)
|
||||
(memory (export "memory") 1)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let mut instance = engine.instantiate(&wasm).unwrap();
|
||||
let results = instance
|
||||
.evaluate(
|
||||
"add",
|
||||
&[DiffValue::I32(1), DiffValue::I32(2)],
|
||||
&[DiffValueType::I32],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(results, Some(vec![DiffValue::I32(3)]));
|
||||
|
||||
if let Some(val) = instance.get_global("global", DiffValueType::I32) {
|
||||
assert_eq!(val, DiffValue::I32(1));
|
||||
}
|
||||
|
||||
if let Some(val) = instance.get_memory("memory", false) {
|
||||
assert_eq!(val.len(), 65536);
|
||||
for i in val.iter() {
|
||||
assert_eq!(*i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
panic!("after {n} runs nothing ever ran, something is probably wrong");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user