fuzz: Add a fuzz target for table.{get,set} operations

This new fuzz target exercises sequences of `table.get`s, `table.set`s, and
GCs.

It already found a couple bugs:

* Some leaks due to ref count cycles between stores and host-defined functions
  closing over those stores.

* If there are no live references for a PC, Cranelift can avoid emiting an
  associated stack map. This was running afoul of a debug assertion.
This commit is contained in:
Nick Fitzgerald
2020-06-26 16:13:55 -07:00
parent 8c5f59c0cf
commit 98e899f6b3
10 changed files with 253 additions and 30 deletions

View File

@@ -13,12 +13,15 @@
pub mod dummy;
use dummy::dummy_imports;
use std::cell::Cell;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wasmtime::*;
use wasmtime_wast::WastContext;
static CNT: AtomicUsize = AtomicUsize::new(0);
fn log_wasm(wasm: &[u8]) {
static CNT: AtomicUsize = AtomicUsize::new(0);
if !log::log_enabled!(log::Level::Debug) {
return;
}
@@ -33,6 +36,16 @@ fn log_wasm(wasm: &[u8]) {
}
}
fn log_wat(wat: &str) {
if !log::log_enabled!(log::Level::Debug) {
return;
}
let i = CNT.fetch_add(1, SeqCst);
let name = format!("testcase{}.wat", i);
std::fs::write(&name, wat).expect("failed to write wat file");
}
/// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
/// panic or segfault or anything else that can be detected "passively".
///
@@ -400,3 +413,55 @@ pub fn spectest(config: crate::generators::Config, test: crate::generators::Spec
.run_buffer(test.file, test.contents.as_bytes())
.unwrap();
}
/// Execute a series of `table.get` and `table.set` operations.
pub fn table_ops(config: crate::generators::Config, ops: crate::generators::table_ops::TableOps) {
let _ = env_logger::try_init();
let num_dropped = Rc::new(Cell::new(0));
{
let mut config = config.to_wasmtime();
config.wasm_reference_types(true);
let engine = Engine::new(&config);
let store = Store::new(&engine);
let wat = ops.to_wat_string();
log_wat(&wat);
let module = match Module::new(&engine, &wat) {
Ok(m) => m,
Err(_) => return,
};
// To avoid timeouts, limit the number of explicit GCs we perform per
// test case.
const MAX_GCS: usize = 5;
let num_gcs = Cell::new(0);
let gc = Func::wrap(&store, move |caller: Caller| {
if num_gcs.get() < MAX_GCS {
caller.store().gc();
num_gcs.set(num_gcs.get() + 1);
}
});
let instance = Instance::new(&store, &module, &[gc.into()]).unwrap();
let run = instance.get_func("run").unwrap();
let args: Vec<_> = (0..ops.num_params())
.map(|_| Val::ExternRef(Some(ExternRef::new(CountDrops(num_dropped.clone())))))
.collect();
let _ = run.call(&args);
}
assert_eq!(num_dropped.get(), ops.num_params());
return;
struct CountDrops(Rc<Cell<u8>>);
impl Drop for CountDrops {
fn drop(&mut self) {
self.0.set(self.0.get().checked_add(1).unwrap());
}
}
}