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:
Alex Crichton
2022-08-19 14:19:00 -05:00
committed by GitHub
parent 9758f5420e
commit fd98814b96
14 changed files with 891 additions and 695 deletions

View File

@@ -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))
}