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:
@@ -5,8 +5,8 @@ use libfuzzer_sys::fuzz_target;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::sync::Once;
|
||||
use wasmtime_fuzzing::generators::{Config, DiffValue, SingleInstModule};
|
||||
use wasmtime_fuzzing::oracles::diff_spec;
|
||||
use wasmtime::Trap;
|
||||
use wasmtime_fuzzing::generators::{Config, DiffValue, DiffValueType, SingleInstModule};
|
||||
use wasmtime_fuzzing::oracles::diff_wasmtime::WasmtimeInstance;
|
||||
use wasmtime_fuzzing::oracles::{differential, engine, log_wasm};
|
||||
|
||||
@@ -14,11 +14,8 @@ use wasmtime_fuzzing::oracles::{differential, engine, log_wasm};
|
||||
// executed by this fuzz target.
|
||||
const NUM_INVOCATIONS: usize = 5;
|
||||
|
||||
// Keep track of how many WebAssembly modules we actually executed (i.e. ran to
|
||||
// completion) versus how many were tried.
|
||||
static TOTAL_INVOCATIONS: AtomicUsize = AtomicUsize::new(0);
|
||||
static TOTAL_SUCCESSES: AtomicUsize = AtomicUsize::new(0);
|
||||
static TOTAL_ATTEMPTED: AtomicUsize = AtomicUsize::new(0);
|
||||
// Statistics about what's actually getting executed during fuzzing
|
||||
static STATS: RuntimeStats = RuntimeStats::new();
|
||||
|
||||
// The spec interpreter requires special one-time setup.
|
||||
static SETUP: Once = Once::new();
|
||||
@@ -26,7 +23,7 @@ static SETUP: Once = Once::new();
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
// To avoid a uncaught `SIGSEGV` due to signal handlers; see comments on
|
||||
// `setup_ocaml_runtime`.
|
||||
SETUP.call_once(|| diff_spec::setup_ocaml_runtime());
|
||||
SETUP.call_once(|| engine::setup_engine_runtimes());
|
||||
|
||||
// Errors in `run` have to do with not enough input in `data`, which we
|
||||
// ignore here since it doesn't affect how we'd like to fuzz.
|
||||
@@ -34,16 +31,7 @@ fuzz_target!(|data: &[u8]| {
|
||||
});
|
||||
|
||||
fn run(data: &[u8]) -> Result<()> {
|
||||
let successes = TOTAL_SUCCESSES.load(SeqCst);
|
||||
let attempts = TOTAL_ATTEMPTED.fetch_add(1, SeqCst);
|
||||
if attempts > 1 && attempts % 1_000 == 0 {
|
||||
println!("=== Execution rate ({} successes / {} attempted modules): {}% (total invocations: {}) ===",
|
||||
successes,
|
||||
attempts,
|
||||
successes as f64 / attempts as f64 * 100f64,
|
||||
TOTAL_INVOCATIONS.load(SeqCst)
|
||||
);
|
||||
}
|
||||
STATS.bump_attempts();
|
||||
|
||||
let mut u = Unstructured::new(data);
|
||||
let mut config: Config = u.arbitrary()?;
|
||||
@@ -51,19 +39,20 @@ fn run(data: &[u8]) -> Result<()> {
|
||||
|
||||
// Generate the Wasm module.
|
||||
let wasm = if u.arbitrary()? {
|
||||
// TODO figure out if this always eats up the rest of the unstructured;
|
||||
// can we limit the number of instructions/functions.
|
||||
STATS.wasm_smith_modules.fetch_add(1, SeqCst);
|
||||
let module = config.generate(&mut u, Some(1000))?;
|
||||
module.to_bytes()
|
||||
} else {
|
||||
STATS.single_instruction_modules.fetch_add(1, SeqCst);
|
||||
let module = SingleInstModule::new(&mut u, &mut config.module_config)?;
|
||||
module.to_bytes()
|
||||
};
|
||||
log_wasm(&wasm);
|
||||
|
||||
// Choose a left-hand side Wasm engine.
|
||||
let lhs = engine::choose(&mut u, &config)?;
|
||||
let mut lhs = engine::choose(&mut u, &config)?;
|
||||
let lhs_instance = lhs.instantiate(&wasm);
|
||||
STATS.bump_engine(lhs.name());
|
||||
|
||||
// Choose a right-hand side Wasm engine--this will always be Wasmtime.
|
||||
let rhs_store = config.to_store();
|
||||
@@ -73,7 +62,11 @@ fn run(data: &[u8]) -> Result<()> {
|
||||
// If we fail to instantiate, check that both sides do.
|
||||
let (mut lhs_instance, mut rhs_instance) = match (lhs_instance, rhs_instance) {
|
||||
(Ok(l), Ok(r)) => (l, r),
|
||||
(Err(_), Err(_)) => return Ok(()), // TODO match the error messages.
|
||||
(Err(l), Err(r)) => {
|
||||
let err = r.downcast::<Trap>().expect("not a trap");
|
||||
lhs.assert_error_match(&err, l);
|
||||
return Ok(());
|
||||
}
|
||||
(l, r) => panic!(
|
||||
"failed to instantiate only one side: {:?} != {:?}",
|
||||
l.err(),
|
||||
@@ -89,21 +82,116 @@ fn run(data: &[u8]) -> Result<()> {
|
||||
.params()
|
||||
.map(|t| DiffValue::arbitrary_of_type(&mut u, t.try_into().unwrap()))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
differential(lhs_instance.as_mut(), &mut rhs_instance, &name, &arguments)
|
||||
.expect("failed to run differential evaluation");
|
||||
let result_tys = signature
|
||||
.results()
|
||||
.map(|t| DiffValueType::try_from(t).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
differential(
|
||||
lhs_instance.as_mut(),
|
||||
&mut rhs_instance,
|
||||
&name,
|
||||
&arguments,
|
||||
&result_tys,
|
||||
)
|
||||
.expect("failed to run differential evaluation");
|
||||
|
||||
// We evaluate the same function with different arguments until we
|
||||
// hit a predetermined limit or we run out of unstructured data--it
|
||||
// does not make sense to re-evaluate the same arguments over and
|
||||
// over.
|
||||
invocations += 1;
|
||||
TOTAL_INVOCATIONS.fetch_add(1, SeqCst);
|
||||
STATS.total_invocations.fetch_add(1, SeqCst);
|
||||
if invocations > NUM_INVOCATIONS || u.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TOTAL_SUCCESSES.fetch_add(1, SeqCst);
|
||||
STATS.successes.fetch_add(1, SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RuntimeStats {
|
||||
/// Total number of fuzz inputs processed
|
||||
attempts: AtomicUsize,
|
||||
|
||||
/// Number of times we've invoked engines
|
||||
total_invocations: AtomicUsize,
|
||||
|
||||
/// Number of times a fuzz input finished all the way to the end without any
|
||||
/// sort of error (including `Arbitrary` errors)
|
||||
successes: AtomicUsize,
|
||||
|
||||
// Counters for which engine was chosen
|
||||
wasmi: AtomicUsize,
|
||||
v8: AtomicUsize,
|
||||
spec: AtomicUsize,
|
||||
wasmtime: AtomicUsize,
|
||||
|
||||
// Counters for which style of module is chosen
|
||||
wasm_smith_modules: AtomicUsize,
|
||||
single_instruction_modules: AtomicUsize,
|
||||
}
|
||||
|
||||
impl RuntimeStats {
|
||||
const fn new() -> RuntimeStats {
|
||||
RuntimeStats {
|
||||
attempts: AtomicUsize::new(0),
|
||||
total_invocations: AtomicUsize::new(0),
|
||||
successes: AtomicUsize::new(0),
|
||||
wasmi: AtomicUsize::new(0),
|
||||
v8: AtomicUsize::new(0),
|
||||
spec: AtomicUsize::new(0),
|
||||
wasmtime: AtomicUsize::new(0),
|
||||
wasm_smith_modules: AtomicUsize::new(0),
|
||||
single_instruction_modules: AtomicUsize::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn bump_attempts(&self) {
|
||||
let attempts = self.attempts.fetch_add(1, SeqCst);
|
||||
if attempts == 0 || attempts % 1_000 != 0 {
|
||||
return;
|
||||
}
|
||||
let successes = self.successes.load(SeqCst);
|
||||
println!(
|
||||
"=== Execution rate ({} successes / {} attempted modules): {:.02}% ===",
|
||||
successes,
|
||||
attempts,
|
||||
successes as f64 / attempts as f64 * 100f64,
|
||||
);
|
||||
|
||||
let v8 = self.v8.load(SeqCst);
|
||||
let spec = self.spec.load(SeqCst);
|
||||
let wasmi = self.wasmi.load(SeqCst);
|
||||
let wasmtime = self.wasmtime.load(SeqCst);
|
||||
let total = v8 + spec + wasmi + wasmtime;
|
||||
println!(
|
||||
"\twasmi: {:.02}%, spec: {:.02}%, wasmtime: {:.02}%, v8: {:.02}%",
|
||||
wasmi as f64 / total as f64 * 100f64,
|
||||
spec as f64 / total as f64 * 100f64,
|
||||
wasmtime as f64 / total as f64 * 100f64,
|
||||
v8 as f64 / total as f64 * 100f64,
|
||||
);
|
||||
|
||||
let wasm_smith = self.wasm_smith_modules.load(SeqCst);
|
||||
let single_inst = self.single_instruction_modules.load(SeqCst);
|
||||
let total = wasm_smith + single_inst;
|
||||
println!(
|
||||
"\twasm-smith: {:.02}%, single-inst: {:.02}%",
|
||||
wasm_smith as f64 / total as f64 * 100f64,
|
||||
single_inst as f64 / total as f64 * 100f64,
|
||||
);
|
||||
}
|
||||
|
||||
fn bump_engine(&self, name: &str) {
|
||||
match name {
|
||||
"wasmi" => self.wasmi.fetch_add(1, SeqCst),
|
||||
"wasmtime" => self.wasmtime.fetch_add(1, SeqCst),
|
||||
"spec" => self.spec.fetch_add(1, SeqCst),
|
||||
"v8" => self.v8.fetch_add(1, SeqCst),
|
||||
_ => return,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user