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,12 +1,11 @@
|
||||
//! Evaluate an exported Wasm function using Wasmtime.
|
||||
|
||||
use crate::generators::{self, DiffValue};
|
||||
use crate::generators::{self, DiffValue, DiffValueType};
|
||||
use crate::oracles::dummy;
|
||||
use crate::oracles::engine::DiffInstance;
|
||||
use crate::oracles::{compile_module, engine::DiffEngine, instantiate_with_dummy, StoreLimits};
|
||||
use anyhow::{Context, Result};
|
||||
use std::hash::Hash;
|
||||
use std::slice;
|
||||
use wasmtime::{AsContextMut, Extern, FuncType, Instance, Module, Store, Val};
|
||||
use crate::oracles::{compile_module, engine::DiffEngine, StoreLimits};
|
||||
use anyhow::{Context, Error, Result};
|
||||
use wasmtime::{Extern, FuncType, Instance, Module, Store, Trap, Val};
|
||||
|
||||
/// A wrapper for using Wasmtime as a [`DiffEngine`].
|
||||
pub struct WasmtimeEngine {
|
||||
@@ -18,10 +17,8 @@ impl WasmtimeEngine {
|
||||
/// later. Ideally the store and engine could be built here but
|
||||
/// `compile_module` takes a [`generators::Config`]; TODO re-factor this if
|
||||
/// that ever changes.
|
||||
pub fn new(config: &generators::Config) -> Result<Box<Self>> {
|
||||
Ok(Box::new(Self {
|
||||
config: config.clone(),
|
||||
}))
|
||||
pub fn new(config: generators::Config) -> Result<Self> {
|
||||
Ok(Self { config })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +27,23 @@ impl DiffEngine for WasmtimeEngine {
|
||||
"wasmtime"
|
||||
}
|
||||
|
||||
fn instantiate(&self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
|
||||
fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
|
||||
let store = self.config.to_store();
|
||||
let module = compile_module(store.engine(), wasm, true, &self.config).unwrap();
|
||||
let instance = WasmtimeInstance::new(store, module)?;
|
||||
Ok(Box::new(instance))
|
||||
}
|
||||
|
||||
fn assert_error_match(&self, trap: &Trap, err: Error) {
|
||||
let trap2 = err.downcast::<Trap>().unwrap();
|
||||
assert_eq!(
|
||||
trap.trap_code(),
|
||||
trap2.trap_code(),
|
||||
"{}\nis not equal to\n{}",
|
||||
trap,
|
||||
trap2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a Wasmtime instance.
|
||||
@@ -50,7 +58,8 @@ pub struct WasmtimeInstance {
|
||||
impl WasmtimeInstance {
|
||||
/// Instantiate a new Wasmtime instance.
|
||||
pub fn new(mut store: Store<StoreLimits>, module: Module) -> Result<Self> {
|
||||
let instance = instantiate_with_dummy(&mut store, &module)
|
||||
let instance = dummy::dummy_linker(&mut store, &module)
|
||||
.and_then(|l| l.instantiate(&mut store, &module))
|
||||
.context("unable to instantiate module in wasmtime")?;
|
||||
Ok(Self { store, instance })
|
||||
}
|
||||
@@ -73,6 +82,44 @@ impl WasmtimeInstance {
|
||||
.map(|(n, f)| (n, f.ty(&self.store)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the list of globals and their types exported from this instance.
|
||||
pub fn exported_globals(&mut self) -> Vec<(String, DiffValueType)> {
|
||||
let globals = self
|
||||
.instance
|
||||
.exports(&mut self.store)
|
||||
.filter_map(|e| {
|
||||
let name = e.name();
|
||||
e.into_global().map(|g| (name.to_string(), g))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
globals
|
||||
.into_iter()
|
||||
.map(|(name, global)| {
|
||||
(
|
||||
name,
|
||||
global.ty(&self.store).content().clone().try_into().unwrap(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the list of exported memories and whether or not it's a shared
|
||||
/// memory.
|
||||
pub fn exported_memories(&mut self) -> Vec<(String, bool)> {
|
||||
self.instance
|
||||
.exports(&mut self.store)
|
||||
.filter_map(|e| {
|
||||
let name = e.name();
|
||||
match e.into_extern() {
|
||||
Extern::Memory(_) => Some((name.to_string(), false)),
|
||||
Extern::SharedMemory(_) => Some((name.to_string(), true)),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl DiffInstance for WasmtimeInstance {
|
||||
@@ -80,7 +127,12 @@ impl DiffInstance for WasmtimeInstance {
|
||||
"wasmtime"
|
||||
}
|
||||
|
||||
fn evaluate(&mut self, function_name: &str, arguments: &[DiffValue]) -> Result<Vec<DiffValue>> {
|
||||
fn evaluate(
|
||||
&mut self,
|
||||
function_name: &str,
|
||||
arguments: &[DiffValue],
|
||||
_results: &[DiffValueType],
|
||||
) -> Result<Option<Vec<DiffValue>>> {
|
||||
let arguments: Vec<_> = arguments.iter().map(Val::from).collect();
|
||||
|
||||
let function = self
|
||||
@@ -92,43 +144,34 @@ impl DiffInstance for WasmtimeInstance {
|
||||
function.call(&mut self.store, &arguments, &mut results)?;
|
||||
|
||||
let results = results.into_iter().map(Val::into).collect();
|
||||
Ok(results)
|
||||
Ok(Some(results))
|
||||
}
|
||||
|
||||
fn is_hashable(&self) -> bool {
|
||||
true
|
||||
fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
|
||||
Some(
|
||||
self.instance
|
||||
.get_global(&mut self.store, name)
|
||||
.unwrap()
|
||||
.get(&mut self.store)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
fn hash(&mut self, state: &mut std::collections::hash_map::DefaultHasher) -> Result<()> {
|
||||
let exports: Vec<_> = self
|
||||
.instance
|
||||
.exports(self.store.as_context_mut())
|
||||
.map(|e| e.into_extern())
|
||||
.collect();
|
||||
for e in exports {
|
||||
match e {
|
||||
Extern::Global(g) => {
|
||||
let val: DiffValue = g.get(&mut self.store).into();
|
||||
val.hash(state)
|
||||
}
|
||||
Extern::Memory(m) => {
|
||||
let data = m.data(&mut self.store);
|
||||
data.hash(state)
|
||||
}
|
||||
Extern::SharedMemory(m) => {
|
||||
let data = unsafe { slice::from_raw_parts(m.data() as *mut u8, m.data_size()) };
|
||||
data.hash(state)
|
||||
}
|
||||
Extern::Table(_) => {
|
||||
// TODO: it's unclear whether it is worth it to iterate
|
||||
// through the table and hash the values.
|
||||
}
|
||||
Extern::Func(_) => {
|
||||
// Note: no need to hash exported functions.
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {
|
||||
Some(if shared {
|
||||
let data = self
|
||||
.instance
|
||||
.get_shared_memory(&mut self.store, name)
|
||||
.unwrap()
|
||||
.data();
|
||||
unsafe { (*data).to_vec() }
|
||||
} else {
|
||||
self.instance
|
||||
.get_memory(&mut self.store, name)
|
||||
.unwrap()
|
||||
.data(&self.store)
|
||||
.to_vec()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +183,14 @@ impl From<&DiffValue> for Val {
|
||||
DiffValue::F32(n) => Val::F32(n),
|
||||
DiffValue::F64(n) => Val::F64(n),
|
||||
DiffValue::V128(n) => Val::V128(n),
|
||||
DiffValue::FuncRef { null } => {
|
||||
assert!(null);
|
||||
Val::FuncRef(None)
|
||||
}
|
||||
DiffValue::ExternRef { null } => {
|
||||
assert!(null);
|
||||
Val::ExternRef(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,8 +203,13 @@ impl Into<DiffValue> for Val {
|
||||
Val::F32(n) => DiffValue::F32(n),
|
||||
Val::F64(n) => DiffValue::F64(n),
|
||||
Val::V128(n) => DiffValue::V128(n),
|
||||
Val::FuncRef(_) => unimplemented!(),
|
||||
Val::ExternRef(_) => unimplemented!(),
|
||||
Val::FuncRef(f) => DiffValue::FuncRef { null: f.is_none() },
|
||||
Val::ExternRef(e) => DiffValue::ExternRef { null: e.is_none() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
crate::oracles::engine::smoke_test_engine(|config| WasmtimeEngine::new(config))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user