Add basic arbitrary-model-tests based fuzzing

This commit is contained in:
Wim Looman
2020-01-12 12:32:24 +01:00
parent 4d64d51cd9
commit a69a5aab0d
7 changed files with 193 additions and 0 deletions
+3
View File
@@ -29,3 +29,6 @@ travis-ci = { repository = "Lokathor/tinyvec" }
[package.metadata.docs.rs]
all-features = true
[workspace]
members = ["fuzz"]
+2
View File
@@ -0,0 +1,2 @@
hfuzz_target/
hfuzz_workspace/
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "tinyvec-fuzz"
version = "0.1.0"
authors = []
edition = "2018"
publish = false
[dependencies]
tinyvec = { path = "..", features = ["nightly_slice_partition_dedup"] }
arbitrary-model-tests = { git = "https://github.com/jakubadamw/arbitrary-model-tests" }
honggfuzz = "0.5.45"
arbitrary = "0.2.0"
better-panic = "0.2.0"
derive_arbitrary = "0.2.0"
+11
View File
@@ -0,0 +1,11 @@
## Quickstart
```console
> cargo install honggfuzz
> cargo hfuzz run arrayish
```
When a crash is found:
```console
> cargo hfuzz run-debug arrayish hfuzz_workspace/arrayish/*.fuzz
```
+61
View File
@@ -0,0 +1,61 @@
use arbitrary::{Arbitrary, Unstructured};
use std::ops::{Range, RangeBounds, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive, Bound};
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ArbRange<T> {
Range(Range<T>),
RangeFrom(RangeFrom<T>),
RangeInclusive(RangeInclusive<T>),
RangeTo(RangeTo<T>),
RangeToInclusive(RangeToInclusive<T>),
}
impl<T> RangeBounds<T> for ArbRange<T> {
fn start_bound(&self) -> Bound<&T> {
match self {
ArbRange::Range(range) => range.start_bound(),
ArbRange::RangeFrom(range) => range.start_bound(),
ArbRange::RangeInclusive(range) => range.start_bound(),
ArbRange::RangeTo(range) => range.start_bound(),
ArbRange::RangeToInclusive(range) => range.start_bound(),
}
}
fn end_bound(&self) -> Bound<&T> {
match self {
ArbRange::Range(range) => range.end_bound(),
ArbRange::RangeFrom(range) => range.end_bound(),
ArbRange::RangeInclusive(range) => range.end_bound(),
ArbRange::RangeTo(range) => range.end_bound(),
ArbRange::RangeToInclusive(range) => range.end_bound(),
}
}
fn contains<U: ?Sized>(&self, item: &U) -> bool
where
T: PartialOrd<U>,
U: PartialOrd<T>,
{
match self {
ArbRange::Range(range) => range.contains(item),
ArbRange::RangeFrom(range) => range.contains(item),
ArbRange::RangeInclusive(range) => range.contains(item),
ArbRange::RangeTo(range) => range.contains(item),
ArbRange::RangeToInclusive(range) => range.contains(item),
}
}
}
impl<T: Arbitrary> Arbitrary for ArbRange<T> {
fn arbitrary<U: Unstructured + ?Sized>(u: &mut U) -> Result<Self, U::Error> {
let variant = u8::arbitrary(u)? % 5;
Ok(match variant {
0 => ArbRange::Range(T::arbitrary(u)?..T::arbitrary(u)?),
1 => ArbRange::RangeFrom(T::arbitrary(u)?..),
2 => ArbRange::RangeInclusive(T::arbitrary(u)?..=T::arbitrary(u)?),
3 => ArbRange::RangeTo(..T::arbitrary(u)?),
4 => ArbRange::RangeToInclusive(..=T::arbitrary(u)?),
_ => unreachable!(),
})
}
}
+99
View File
@@ -0,0 +1,99 @@
use derive_arbitrary::Arbitrary;
use arbitrary_model_tests::arbitrary_stateful_operations;
use honggfuzz::fuzz;
use std::{fmt::Debug, iter::FromIterator, ops::{RangeBounds, Bound}};
use tinyvec::ArrayishVec;
use tinyvec_fuzz::ArbRange;
const CAPACITY: usize = 32;
arbitrary_stateful_operations! {
model = Vec<T>,
tested = ArrayishVec<[T; CAPACITY]>,
type_parameters = <
T: Default + Clone + Debug + Eq + Ord,
R: RangeBounds<usize> + Clone + Debug,
>,
methods {
equal {
fn as_mut_slice(&mut self) -> &mut [T];
fn as_slice(&self) -> &[T];
fn clear(&mut self);
fn dedup(&mut self);
fn insert(&mut self, index: usize, item: T);
fn is_empty(&self) -> bool;
fn len(&self) -> usize;
fn push(&mut self, item: T);
fn truncate(&mut self, new_len: usize);
}
equal_with(Vec::from_iter) {
fn drain(&self, range: R) -> impl Iterator<Item = T>;
fn iter(&self) -> impl Iterator<Item = &T>;
fn iter_mut(&self) -> impl Iterator<Item = &mut T>;
}
}
pre {
match self {
Self::insert { index, .. } if index > model.len() => {
// TODO: Should test that these identically panic
return;
}
Self::insert { .. } | Self::push { .. } if model.len() == CAPACITY => {
return;
}
Self::drain { ref range } => {
// TODO: Should test that these identically panic
let start = match range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
// If it's already usize::max, doesn't really matter about adding 1
Bound::Included(&n) => n.checked_add(1).unwrap_or(n),
Bound::Excluded(&n) => n,
Bound::Unbounded => model.len(),
};
if start > end || end > model.len() {
return;
}
}
_ => {}
}
}
}
const MAX_RING_SIZE: usize = 16_384;
fn fuzz_cycle(data: &[u8]) -> Result<(), ()> {
use arbitrary::{Arbitrary, FiniteBuffer};
let mut ring = FiniteBuffer::new(&data, MAX_RING_SIZE).map_err(|_| ())?;
let mut model = Vec::<u16>::default();
let mut tested: ArrayishVec<[u16; 32]> = ArrayishVec::new();
let mut _op_trace = String::new();
while let Ok(op) = <op::Op<u16, ArbRange<usize>> as Arbitrary>::arbitrary(&mut ring) {
#[cfg(fuzzing_debug)]
_op_trace.push_str(&format!("{}\n", op.to_string()));
op.execute_and_compare(&mut model, &mut tested);
}
Ok(())
}
fn main() -> Result<(), ()> {
better_panic::install();
loop {
fuzz!(|data: &[u8]| {
let _ = fuzz_cycle(data);
});
}
}
+3
View File
@@ -0,0 +1,3 @@
mod arb_range;
pub use arb_range::ArbRange;