[fuzz] Remove more fuzz targets (#4737)
* [fuzz] Remove the `differential` fuzz target This functionality is already covered by the `differential_meta` target. * [fuzz] Rename `differential_meta` to `differential` Now that the `differential_meta` fuzz target does everything that the existing `differential` target did and more, it can take over the original name.
This commit is contained in:
@@ -380,136 +380,6 @@ pub fn differential(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Instantiate the given Wasm module with each `Config` and call all of its
|
||||
/// exports. Modulo OOM, non-canonical NaNs, and usage of Wasm features that are
|
||||
/// or aren't enabled for different configs, we should get the same results when
|
||||
/// we call the exported functions for all of our different configs.
|
||||
///
|
||||
/// Returns `None` if a fuzz configuration was rejected (should happen rarely).
|
||||
pub fn differential_execution(
|
||||
wasm: &[u8],
|
||||
module_config: &generators::ModuleConfig,
|
||||
configs: &[generators::WasmtimeConfig],
|
||||
) -> Option<()> {
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
// We need at least two configs.
|
||||
if configs.len() < 2
|
||||
// And all the configs should be unique.
|
||||
|| configs.iter().collect::<HashSet<_>>().len() != configs.len()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut export_func_results: HashMap<String, Result<Box<[Val]>, Trap>> = Default::default();
|
||||
log_wasm(&wasm);
|
||||
|
||||
for fuzz_config in configs {
|
||||
let fuzz_config = generators::Config {
|
||||
module_config: module_config.clone(),
|
||||
wasmtime: fuzz_config.clone(),
|
||||
};
|
||||
log::debug!("fuzz config: {:?}", fuzz_config);
|
||||
|
||||
let mut store = fuzz_config.to_store();
|
||||
let module = compile_module(store.engine(), &wasm, true, &fuzz_config)?;
|
||||
|
||||
// TODO: we should implement tracing versions of these dummy imports
|
||||
// that record a trace of the order that imported functions were called
|
||||
// in and with what values. Like the results of exported functions,
|
||||
// calls to imports should also yield the same values for each
|
||||
// configuration, and we should assert that.
|
||||
let instance = match instantiate_with_dummy(&mut store, &module) {
|
||||
Some(instance) => instance,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let exports = instance
|
||||
.exports(&mut store)
|
||||
.filter_map(|e| {
|
||||
let name = e.name().to_string();
|
||||
e.into_func().map(|f| (name, f))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for (name, f) in exports {
|
||||
log::debug!("invoke export {:?}", name);
|
||||
let ty = f.ty(&store);
|
||||
let params = dummy::dummy_values(ty.params());
|
||||
let mut results = vec![Val::I32(0); ty.results().len()];
|
||||
let this_result = f
|
||||
.call(&mut store, ¶ms, &mut results)
|
||||
.map(|()| results.into())
|
||||
.map_err(|e| e.downcast::<Trap>().unwrap());
|
||||
|
||||
let existing_result = export_func_results
|
||||
.entry(name.to_string())
|
||||
.or_insert_with(|| this_result.clone());
|
||||
assert_same_export_func_result(&existing_result, &this_result, &name);
|
||||
}
|
||||
}
|
||||
|
||||
return Some(());
|
||||
|
||||
fn assert_same_export_func_result(
|
||||
lhs: &Result<Box<[Val]>, Trap>,
|
||||
rhs: &Result<Box<[Val]>, Trap>,
|
||||
func_name: &str,
|
||||
) {
|
||||
let fail = || {
|
||||
panic!(
|
||||
"differential fuzzing failed: exported func {} returned two \
|
||||
different results: {:?} != {:?}",
|
||||
func_name, lhs, rhs
|
||||
)
|
||||
};
|
||||
|
||||
match (lhs, rhs) {
|
||||
// Different compilation settings can lead to different amounts
|
||||
// of stack space being consumed, so if either the lhs or the rhs
|
||||
// hit a stack overflow then we discard the result of the other side
|
||||
// since if it ran successfully or trapped that's ok in both
|
||||
// situations.
|
||||
(Err(e), _) | (_, Err(e)) if e.trap_code() == Some(TrapCode::StackOverflow) => {}
|
||||
|
||||
(Err(a), Err(b)) => {
|
||||
if a.trap_code() != b.trap_code() {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
(Ok(lhs), Ok(rhs)) => {
|
||||
if lhs.len() != rhs.len() {
|
||||
fail();
|
||||
}
|
||||
for (lhs, rhs) in lhs.iter().zip(rhs.iter()) {
|
||||
match (lhs, rhs) {
|
||||
(Val::I32(lhs), Val::I32(rhs)) if lhs == rhs => continue,
|
||||
(Val::I64(lhs), Val::I64(rhs)) if lhs == rhs => continue,
|
||||
(Val::V128(lhs), Val::V128(rhs)) if lhs == rhs => continue,
|
||||
(Val::F32(lhs), Val::F32(rhs)) if f32_equal(*lhs, *rhs) => continue,
|
||||
(Val::F64(lhs), Val::F64(rhs)) if f64_equal(*lhs, *rhs) => continue,
|
||||
(Val::ExternRef(_), Val::ExternRef(_))
|
||||
| (Val::FuncRef(_), Val::FuncRef(_)) => continue,
|
||||
_ => fail(),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => fail(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn f32_equal(a: u32, b: u32) -> bool {
|
||||
let a = f32::from_bits(a);
|
||||
let b = f32::from_bits(b);
|
||||
a == b || (a.is_nan() && b.is_nan())
|
||||
}
|
||||
|
||||
fn f64_equal(a: u64, b: u64) -> bool {
|
||||
let a = f64::from_bits(a);
|
||||
let b = f64::from_bits(b);
|
||||
a == b || (a.is_nan() && b.is_nan())
|
||||
}
|
||||
|
||||
/// Invoke the given API calls.
|
||||
pub fn make_api_calls(api: generators::api::ApiCalls) {
|
||||
use crate::generators::api::ApiCall;
|
||||
|
||||
Reference in New Issue
Block a user