diff --git a/Cargo.toml b/Cargo.toml index c212aec..7544800 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,10 @@ travis-ci = { repository = "Lokathor/tinyvec" } [package.metadata.docs.rs] all-features = true + +[workspace] +members = ["fuzz"] + +[[test]] +name = "tinyvec" +required-features = ["extern_crate_alloc"] diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..4a6287c --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,2 @@ +hfuzz_target/ +hfuzz_workspace/ diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..e73ed6a --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tinyvec-fuzz" +version = "0.1.0" +authors = [] +edition = "2018" +publish = false + +[dependencies] +tinyvec = { path = "..", features = ["extern_crate_alloc", "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" diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 0000000..868d7a6 --- /dev/null +++ b/fuzz/README.md @@ -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 +``` diff --git a/fuzz/src/arb_range.rs b/fuzz/src/arb_range.rs new file mode 100644 index 0000000..f0db947 --- /dev/null +++ b/fuzz/src/arb_range.rs @@ -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 { + Range(Range), + RangeFrom(RangeFrom), + RangeInclusive(RangeInclusive), + RangeTo(RangeTo), + RangeToInclusive(RangeToInclusive), +} + +impl RangeBounds for ArbRange { + 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(&self, item: &U) -> bool + where + T: PartialOrd, + U: PartialOrd, + { + 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 Arbitrary for ArbRange { + fn arbitrary(u: &mut U) -> Result { + 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!(), + }) + } +} diff --git a/fuzz/src/bin/arrayish.rs b/fuzz/src/bin/arrayish.rs new file mode 100644 index 0000000..9fc5b30 --- /dev/null +++ b/fuzz/src/bin/arrayish.rs @@ -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, + tested = ArrayishVec<[T; CAPACITY]>, + + type_parameters = < + T: Default + Clone + Debug + Eq + Ord, + R: RangeBounds + 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; + fn iter(&self) -> impl Iterator; + fn iter_mut(&self) -> impl Iterator; + } + } + + 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::::default(); + let mut tested: ArrayishVec<[u16; 32]> = ArrayishVec::new(); + + let mut _op_trace = String::new(); + while let Ok(op) = > 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); + }); + } +} diff --git a/fuzz/src/bin/tinyvec.rs b/fuzz/src/bin/tinyvec.rs new file mode 100644 index 0000000..85d240e --- /dev/null +++ b/fuzz/src/bin/tinyvec.rs @@ -0,0 +1,96 @@ +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::TinyVec; +use tinyvec_fuzz::ArbRange; + +const CAPACITY: usize = 32; + +arbitrary_stateful_operations! { + model = Vec, + tested = TinyVec<[T; CAPACITY]>, + + type_parameters = < + T: Default + Clone + Debug + Eq + Ord, + R: RangeBounds + 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; + fn iter(&self) -> impl Iterator; + fn iter_mut(&self) -> impl Iterator; + } + } + + pre { + match self { + Self::insert { index, .. } if index > model.len() => { + // TODO: Should test that these identically panic + 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::::default(); + let mut tested: TinyVec<[u16; 32]> = TinyVec::new(); + + let mut _op_trace = String::new(); + while let Ok(op) = > 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); + }); + } +} diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs new file mode 100644 index 0000000..3a0dba8 --- /dev/null +++ b/fuzz/src/lib.rs @@ -0,0 +1,3 @@ +mod arb_range; + +pub use arb_range::ArbRange; diff --git a/src/arrayish_vec.rs b/src/arrayish_vec.rs index 4a78425..ee87285 100644 --- a/src/arrayish_vec.rs +++ b/src/arrayish_vec.rs @@ -200,8 +200,8 @@ impl ArrayishVec { Bound::Unbounded => 0, }; let end = match range.end_bound() { - Bound::Included(x) => *x, - Bound::Excluded(x) => x - 1, + Bound::Included(x) => x + 1, + Bound::Excluded(x) => *x, Bound::Unbounded => self.len, }; assert!( diff --git a/src/tiny_vec.rs b/src/tiny_vec.rs index db4e933..25135ea 100644 --- a/src/tiny_vec.rs +++ b/src/tiny_vec.rs @@ -215,8 +215,8 @@ impl TinyVec { Bound::Unbounded => 0, }; let end = match range.end_bound() { - Bound::Included(x) => *x, - Bound::Excluded(x) => x - 1, + Bound::Included(x) => x + 1, + Bound::Excluded(x) => *x, Bound::Unbounded => self.len(), }; assert!( diff --git a/tests/basic_tests.rs b/tests/basic_tests.rs index 4eca2dc..8b1367b 100644 --- a/tests/basic_tests.rs +++ b/tests/basic_tests.rs @@ -1,6 +1,7 @@ #![allow(bad_style)] use tinyvec::*; +use std::iter::FromIterator; #[test] fn test_a_vec() { @@ -121,3 +122,32 @@ fn ArrayishVec_remove() { assert_eq!(av.remove(1), 2); assert_eq!(&av[..], &[1, 3][..]); } + +#[test] +fn ArrayishVec_drain() { + let mut av: ArrayishVec<[i32; 10]> = Default::default(); + av.push(1); + av.push(2); + av.push(3); + + assert_eq!(Vec::from_iter(av.clone().drain(..)), vec![1, 2, 3]); + + assert_eq!(Vec::from_iter(av.clone().drain(..2)), vec![1, 2]); + assert_eq!(Vec::from_iter(av.clone().drain(..3)), vec![1, 2, 3]); + + assert_eq!(Vec::from_iter(av.clone().drain(..=1)), vec![1, 2]); + assert_eq!(Vec::from_iter(av.clone().drain(..=2)), vec![1, 2, 3]); + + assert_eq!(Vec::from_iter(av.clone().drain(0..)), vec![1, 2, 3]); + assert_eq!(Vec::from_iter(av.clone().drain(1..)), vec![2, 3]); + + assert_eq!(Vec::from_iter(av.clone().drain(0..2)), vec![1, 2]); + assert_eq!(Vec::from_iter(av.clone().drain(0..3)), vec![1, 2, 3]); + assert_eq!(Vec::from_iter(av.clone().drain(1..2)), vec![2]); + assert_eq!(Vec::from_iter(av.clone().drain(1..3)), vec![2, 3]); + + assert_eq!(Vec::from_iter(av.clone().drain(0..=1)), vec![1, 2]); + assert_eq!(Vec::from_iter(av.clone().drain(0..=2)), vec![1, 2, 3]); + assert_eq!(Vec::from_iter(av.clone().drain(1..=1)), vec![2]); + assert_eq!(Vec::from_iter(av.clone().drain(1..=2)), vec![2, 3]); +} diff --git a/tests/tinyvec.rs b/tests/tinyvec.rs new file mode 100644 index 0000000..86756d2 --- /dev/null +++ b/tests/tinyvec.rs @@ -0,0 +1,33 @@ +#![allow(bad_style)] + +use tinyvec::*; +use std::iter::FromIterator; + +#[test] +fn TinyVec_drain() { + let mut tv: TinyVec<[i32; 10]> = Default::default(); + tv.push(1); + tv.push(2); + tv.push(3); + + assert_eq!(Vec::from_iter(tv.clone().drain(..)), vec![1, 2, 3]); + + assert_eq!(Vec::from_iter(tv.clone().drain(..2)), vec![1, 2]); + assert_eq!(Vec::from_iter(tv.clone().drain(..3)), vec![1, 2, 3]); + + assert_eq!(Vec::from_iter(tv.clone().drain(..=1)), vec![1, 2]); + assert_eq!(Vec::from_iter(tv.clone().drain(..=2)), vec![1, 2, 3]); + + assert_eq!(Vec::from_iter(tv.clone().drain(0..)), vec![1, 2, 3]); + assert_eq!(Vec::from_iter(tv.clone().drain(1..)), vec![2, 3]); + + assert_eq!(Vec::from_iter(tv.clone().drain(0..2)), vec![1, 2]); + assert_eq!(Vec::from_iter(tv.clone().drain(0..3)), vec![1, 2, 3]); + assert_eq!(Vec::from_iter(tv.clone().drain(1..2)), vec![2]); + assert_eq!(Vec::from_iter(tv.clone().drain(1..3)), vec![2, 3]); + + assert_eq!(Vec::from_iter(tv.clone().drain(0..=1)), vec![1, 2]); + assert_eq!(Vec::from_iter(tv.clone().drain(0..=2)), vec![1, 2, 3]); + assert_eq!(Vec::from_iter(tv.clone().drain(1..=1)), vec![2]); + assert_eq!(Vec::from_iter(tv.clone().drain(1..=2)), vec![2, 3]); +}