Fuzzer: differential comparison of opt/no-opt
This commit is contained in:
parent
573667f37b
commit
2ff4d80286
|
@ -19,3 +19,11 @@ rayon = "1.5"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
addr2line = "0.19"
|
addr2line = "0.19"
|
||||||
|
|
||||||
|
# For fuzzing only. Versions must match those in fuzz/Cargo.toml.
|
||||||
|
libfuzzer-sys = { version = "0.4", optional = true }
|
||||||
|
wasm-smith = { version = "0.8", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
fuzzing = ["libfuzzer-sys", "wasm-smith"]
|
||||||
|
|
|
@ -19,6 +19,7 @@ wasmtime = "3.0"
|
||||||
|
|
||||||
[dependencies.waffle]
|
[dependencies.waffle]
|
||||||
path = ".."
|
path = ".."
|
||||||
|
features = ["fuzzing"]
|
||||||
|
|
||||||
# Prevent this from interfering with workspaces
|
# Prevent this from interfering with workspaces
|
||||||
[workspace]
|
[workspace]
|
||||||
|
@ -47,3 +48,9 @@ name = "differential"
|
||||||
path = "fuzz_targets/differential.rs"
|
path = "fuzz_targets/differential.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "opt_diff"
|
||||||
|
path = "fuzz_targets/opt_diff.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
|
@ -1,116 +1,17 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
use libfuzzer_sys::{arbitrary, fuzz_target};
|
use libfuzzer_sys::fuzz_target;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
use waffle::{FrontendOptions, Module};
|
use waffle::{FrontendOptions, Module};
|
||||||
|
|
||||||
fn reject(bytes: &[u8]) -> bool {
|
fuzz_target!(
|
||||||
let parser = wasmparser::Parser::new(0);
|
|module: wasm_smith::ConfiguredModule<waffle::fuzzing::Config>| {
|
||||||
let mut has_start = false;
|
|
||||||
let mut has_global_set = false;
|
|
||||||
let mut num_globals = 0;
|
|
||||||
for payload in parser.parse_all(bytes) {
|
|
||||||
match payload.unwrap() {
|
|
||||||
wasmparser::Payload::CodeSectionEntry(body) => {
|
|
||||||
for op in body.get_operators_reader().unwrap() {
|
|
||||||
let op = op.unwrap();
|
|
||||||
match op {
|
|
||||||
wasmparser::Operator::GlobalSet { .. } => {
|
|
||||||
has_global_set = true;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wasmparser::Payload::StartSection { .. } => {
|
|
||||||
has_start = true;
|
|
||||||
}
|
|
||||||
wasmparser::Payload::ExportSection(mut reader) => {
|
|
||||||
for _ in 0..reader.get_count() {
|
|
||||||
let e = reader.read().unwrap();
|
|
||||||
match &e.kind {
|
|
||||||
&wasmparser::ExternalKind::Global => {
|
|
||||||
num_globals += 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !has_start || !has_global_set || num_globals < 1 {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Config;
|
|
||||||
|
|
||||||
impl<'a> arbitrary::Arbitrary<'a> for Config {
|
|
||||||
fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
|
|
||||||
Ok(Config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl wasm_smith::Config for Config {
|
|
||||||
fn min_funcs(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
fn max_funcs(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
fn min_memories(&self) -> u32 {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
fn max_memories(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
fn min_globals(&self) -> usize {
|
|
||||||
10
|
|
||||||
}
|
|
||||||
fn max_globals(&self) -> usize {
|
|
||||||
10
|
|
||||||
}
|
|
||||||
fn min_tables(&self) -> u32 {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
fn max_tables(&self) -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
fn min_imports(&self) -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
fn max_imports(&self) -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
fn min_exports(&self) -> usize {
|
|
||||||
12
|
|
||||||
}
|
|
||||||
fn max_exports(&self) -> usize {
|
|
||||||
12
|
|
||||||
}
|
|
||||||
fn allow_start_export(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
fn canonicalize_nans(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
fn max_memory_pages(&self, _is_64: bool) -> u64 {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fuzz_target!(|module: wasm_smith::ConfiguredModule<Config>| {
|
|
||||||
let _ = env_logger::try_init();
|
let _ = env_logger::try_init();
|
||||||
log::debug!("original module: {:?}", module.module);
|
log::debug!("original module: {:?}", module.module);
|
||||||
|
|
||||||
let orig_bytes = module.module.to_bytes();
|
let orig_bytes = module.module.to_bytes();
|
||||||
|
|
||||||
if reject(&orig_bytes[..]) {
|
if waffle::fuzzing::reject(&orig_bytes[..]) {
|
||||||
log::debug!("Discarding fuzz run. Body:\n{:?}", module);
|
log::debug!("Discarding fuzz run. Body:\n{:?}", module);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
@ -155,7 +56,8 @@ fuzz_target!(|module: wasm_smith::ConfiguredModule<Config>| {
|
||||||
// what matters is that it terminated above without a trap (hence
|
// what matters is that it terminated above without a trap (hence
|
||||||
// halts in a reasonable time).
|
// halts in a reasonable time).
|
||||||
roundtrip_store.add_fuel(u64::MAX).unwrap();
|
roundtrip_store.add_fuel(u64::MAX).unwrap();
|
||||||
let roundtrip_instance = wasmtime::Instance::new(&mut roundtrip_store, &roundtrip_module, &[])
|
let roundtrip_instance =
|
||||||
|
wasmtime::Instance::new(&mut roundtrip_store, &roundtrip_module, &[])
|
||||||
.expect("cannot instantiate roundtripped wasm");
|
.expect("cannot instantiate roundtripped wasm");
|
||||||
|
|
||||||
// Ensure exports are equal.
|
// Ensure exports are equal.
|
||||||
|
@ -208,7 +110,8 @@ fuzz_target!(|module: wasm_smith::ConfiguredModule<Config>| {
|
||||||
}
|
}
|
||||||
|
|
||||||
success(total);
|
success(total);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
static TOTAL: AtomicU64 = AtomicU64::new(0);
|
static TOTAL: AtomicU64 = AtomicU64::new(0);
|
||||||
static SUCCESS: AtomicU64 = AtomicU64::new(0);
|
static SUCCESS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
56
fuzz/fuzz_targets/opt_diff.rs
Normal file
56
fuzz/fuzz_targets/opt_diff.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use waffle::{FrontendOptions, InterpContext, InterpResult, Module};
|
||||||
|
|
||||||
|
fuzz_target!(
|
||||||
|
|module: wasm_smith::ConfiguredModule<waffle::fuzzing::Config>| {
|
||||||
|
let _ = env_logger::try_init();
|
||||||
|
log::debug!("original module: {:?}", module.module);
|
||||||
|
|
||||||
|
let orig_bytes = module.module.to_bytes();
|
||||||
|
|
||||||
|
if waffle::fuzzing::reject(&orig_bytes[..]) {
|
||||||
|
log::debug!("Discarding fuzz run. Body:\n{:?}", module);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
log::info!("body: {:?}", module);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parsed_module =
|
||||||
|
Module::from_wasm_bytes(&orig_bytes[..], &FrontendOptions::default()).unwrap();
|
||||||
|
parsed_module.expand_all_funcs().unwrap();
|
||||||
|
|
||||||
|
let start = parsed_module.start_func.unwrap();
|
||||||
|
|
||||||
|
let mut orig_ctx = InterpContext::new(&parsed_module);
|
||||||
|
orig_ctx.fuel = 10000;
|
||||||
|
|
||||||
|
match orig_ctx.call(&parsed_module, start, &[]) {
|
||||||
|
InterpResult::OutOfFuel => {
|
||||||
|
// Silently reject.
|
||||||
|
log::trace!("Rejecting due to timeout in orig");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InterpResult::Ok(_) => {}
|
||||||
|
ret => panic!("Bad result: {:?}", ret),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut opt_module = parsed_module.clone();
|
||||||
|
opt_module.per_func_body(|body| body.optimize());
|
||||||
|
|
||||||
|
let mut opt_ctx = InterpContext::new(&opt_module);
|
||||||
|
// Allow a little leeway for opts to not actually optimize.
|
||||||
|
opt_ctx.fuel = 20000;
|
||||||
|
opt_ctx.call(&opt_module, start, &[]).ok().unwrap();
|
||||||
|
|
||||||
|
log::trace!(
|
||||||
|
"Orig ran in {} fuel; opt ran in {} fuel",
|
||||||
|
10000 - orig_ctx.fuel,
|
||||||
|
20000 - opt_ctx.fuel
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(orig_ctx.memories, opt_ctx.memories);
|
||||||
|
assert_eq!(orig_ctx.globals, opt_ctx.globals);
|
||||||
|
}
|
||||||
|
);
|
|
@ -171,3 +171,10 @@ impl<Idx: EntityRef, T: Clone + Debug + Default> IndexMut<Idx> for PerEntity<Idx
|
||||||
&mut self.0[idx.index()]
|
&mut self.0[idx.index()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Idx: EntityRef, T: Clone + Debug + Default + PartialEq> PartialEq for PerEntity<Idx, T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.0 == other.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<Idx: EntityRef, T: Clone + Debug + Default + PartialEq + Eq> Eq for PerEntity<Idx, T> {}
|
||||||
|
|
|
@ -11,9 +11,10 @@ mod wasi;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct InterpContext {
|
pub struct InterpContext {
|
||||||
memories: PerEntity<Memory, InterpMemory>,
|
pub memories: PerEntity<Memory, InterpMemory>,
|
||||||
tables: PerEntity<Table, InterpTable>,
|
pub tables: PerEntity<Table, InterpTable>,
|
||||||
globals: PerEntity<Global, ConstVal>,
|
pub globals: PerEntity<Global, ConstVal>,
|
||||||
|
pub fuel: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
type MultiVal = SmallVec<[ConstVal; 2]>;
|
type MultiVal = SmallVec<[ConstVal; 2]>;
|
||||||
|
@ -23,6 +24,7 @@ pub enum InterpResult {
|
||||||
Ok(MultiVal),
|
Ok(MultiVal),
|
||||||
Exit,
|
Exit,
|
||||||
Trap,
|
Trap,
|
||||||
|
OutOfFuel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InterpResult {
|
impl InterpResult {
|
||||||
|
@ -72,6 +74,7 @@ impl InterpContext {
|
||||||
memories,
|
memories,
|
||||||
tables,
|
tables,
|
||||||
globals,
|
globals,
|
||||||
|
fuel: u64::MAX,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +108,11 @@ impl InterpContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
self.fuel -= 1;
|
||||||
|
if self.fuel == 0 {
|
||||||
|
return InterpResult::OutOfFuel;
|
||||||
|
}
|
||||||
|
|
||||||
log::trace!("Interpreting block {}", frame.cur_block);
|
log::trace!("Interpreting block {}", frame.cur_block);
|
||||||
for &inst in &body.blocks[frame.cur_block].insts {
|
for &inst in &body.blocks[frame.cur_block].insts {
|
||||||
log::trace!("Evaluating inst {}", inst);
|
log::trace!("Evaluating inst {}", inst);
|
||||||
|
@ -290,15 +298,15 @@ impl InterpStackFrame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||||
pub struct InterpMemory {
|
pub struct InterpMemory {
|
||||||
data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
max_pages: usize,
|
pub max_pages: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||||
pub struct InterpTable {
|
pub struct InterpTable {
|
||||||
elements: Vec<Func>,
|
pub elements: Vec<Func>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
|
||||||
|
|
|
@ -22,3 +22,6 @@ pub use ops::{Ieee32, Ieee64, Operator};
|
||||||
|
|
||||||
mod interp;
|
mod interp;
|
||||||
pub use interp::*;
|
pub use interp::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "fuzzing")]
|
||||||
|
pub mod fuzzing;
|
||||||
|
|
Loading…
Reference in a new issue