peepmatic: Introduce the peepmatic-fuzzing crate
This crate contains oracles, generators, and fuzz targets for use with fuzzing engines (e.g. libFuzzer). This doesn't contain the actual `libfuzzer_sys::fuzz_target!` definitions (those are in the `peepmatic-fuzz` crate) but does those definitions are one liners calling out to functions defined in this crate.
This commit is contained in:
119
cranelift/peepmatic/crates/fuzzing/src/lib.rs
Normal file
119
cranelift/peepmatic/crates/fuzzing/src/lib.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
//! Utilities for fuzzing.
|
||||
//!
|
||||
//! The actual fuzz targets are defined in `peepmatic/fuzz/*`. This crate just
|
||||
//! has oracles and generators for fuzzing.
|
||||
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use rand::prelude::*;
|
||||
use std::fmt::Debug;
|
||||
use std::panic;
|
||||
use std::time;
|
||||
|
||||
pub mod automata;
|
||||
pub mod compile;
|
||||
pub mod interp;
|
||||
pub mod parser;
|
||||
|
||||
/// A quickcheck-style runner for fuzz targets.
|
||||
///
|
||||
/// This is *not* intended to replace a long-running, coverage-guided fuzzing
|
||||
/// engine like libFuzzer! This is only for defining quick, purely random tests
|
||||
/// for use with `cargo test` and CI.
|
||||
pub fn check<A>(mut f: impl FnMut(A))
|
||||
where
|
||||
A: Clone + Debug + Arbitrary,
|
||||
{
|
||||
let seed = rand::thread_rng().gen();
|
||||
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed);
|
||||
|
||||
const INITIAL_LENGTH: usize = 16;
|
||||
const MAX_LENGTH: usize = 4096;
|
||||
|
||||
let mut buf: Vec<u8> = (0..INITIAL_LENGTH).map(|_| rng.gen()).collect();
|
||||
let mut num_checked = 0;
|
||||
|
||||
let time_budget = time::Duration::from_secs(2);
|
||||
let then = time::Instant::now();
|
||||
|
||||
let (failing_input, panic_info) = loop {
|
||||
if num_checked > 0 && time::Instant::now().duration_since(then) > time_budget {
|
||||
eprintln!("Checked {} random inputs.", num_checked);
|
||||
return;
|
||||
}
|
||||
|
||||
match <A as Arbitrary>::arbitrary_take_rest(Unstructured::new(&buf)) {
|
||||
Ok(input) => {
|
||||
num_checked += 1;
|
||||
eprintln!("Checking input: {:#?}", input);
|
||||
if let Err(p) = panic::catch_unwind(panic::AssertUnwindSafe(|| f(input.clone()))) {
|
||||
break (input, p);
|
||||
}
|
||||
}
|
||||
Err(e @ arbitrary::Error::NotEnoughData) => {
|
||||
eprintln!("warning: {}", e);
|
||||
if *buf.last().unwrap() == 0 {
|
||||
if buf.len() < MAX_LENGTH {
|
||||
let new_size = std::cmp::min(buf.len() * 2, MAX_LENGTH);
|
||||
eprintln!("Growing buffer size to {}", new_size);
|
||||
let delta = new_size - buf.len();
|
||||
buf.reserve(delta);
|
||||
for _ in 0..delta {
|
||||
buf.push(rng.gen());
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
// Regenerate `buf` in the loop below and see if that
|
||||
// fixes things...
|
||||
eprintln!("Regenerating buffer data.");
|
||||
}
|
||||
} else {
|
||||
// Shrink values in the end of `buf`, which is where
|
||||
// `Arbitrary` pulls container lengths from. Then try again.
|
||||
eprintln!("Shrinking buffer's tail values.");
|
||||
let i = (buf.len() as f64).sqrt() as usize;
|
||||
for j in i..buf.len() {
|
||||
buf[j] /= 2;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("warning: {}", e);
|
||||
// Usually this happens because `A` requires a sequence utf-8
|
||||
// bytes but its given sequence wasn't valid utf-8. Just skip
|
||||
// this iteration and try again after we've updated `buf` below.
|
||||
}
|
||||
};
|
||||
|
||||
// Double the size of the buffer every so often, so we don't only
|
||||
// explore small inputs.
|
||||
if num_checked == buf.len() {
|
||||
buf.resize(std::cmp::min(buf.len() * 2, MAX_LENGTH), 0);
|
||||
}
|
||||
|
||||
for i in 0..buf.len() {
|
||||
buf[i] = rng.gen();
|
||||
}
|
||||
};
|
||||
|
||||
// Shrink the failing input.
|
||||
let mut smallest_failing_input = failing_input;
|
||||
let mut panic_info = panic_info;
|
||||
'shrinking: loop {
|
||||
eprintln!("Smallest failing input: {:#?}", smallest_failing_input);
|
||||
for input in smallest_failing_input.shrink() {
|
||||
if let Err(p) = panic::catch_unwind(panic::AssertUnwindSafe(|| f(input.clone()))) {
|
||||
smallest_failing_input = input;
|
||||
panic_info = p;
|
||||
continue 'shrinking;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Resume the panic for the smallest input.
|
||||
panic::resume_unwind(panic_info);
|
||||
}
|
||||
Reference in New Issue
Block a user