From 738475497f49d9ba71fbe22915b71476900c9399 Mon Sep 17 00:00:00 2001 From: Zachary Dremann Date: Thu, 2 Feb 2023 22:18:12 -0500 Subject: [PATCH] Add a fuzzer --- fuzz/.gitignore | 4 + fuzz/Cargo.toml | 27 +++ fuzz/fuzz_targets/fuzz_ops.rs | 327 ++++++++++++++++++++++++++++++++++ fuzz/rust-toolchain.toml | 2 + justfile | 9 + 5 files changed, 369 insertions(+) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/fuzz_ops.rs create mode 100644 fuzz/rust-toolchain.toml diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..1a45eee --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..4426d04 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "croaring-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } + +croaring = { path = "../croaring" } + + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "fuzz_ops" +path = "fuzz_targets/fuzz_ops.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/fuzz_ops.rs b/fuzz/fuzz_targets/fuzz_ops.rs new file mode 100644 index 0000000..3c418cf --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_ops.rs @@ -0,0 +1,327 @@ +#![no_main] + +use croaring::{Bitmap, BitmapView}; +use libfuzzer_sys::arbitrary; +use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured}; +use libfuzzer_sys::fuzz_target; +use std::mem; +use std::ops::RangeInclusive; + +fuzz_target!(|input: FuzzInput| { + let mut lhs = Bitmap::create(); + let mut rhs = Bitmap::create(); + + for op in input.lhs_ops { + op.do_it(&mut lhs); + } + for op in input.rhs_ops { + op.do_it(&mut rhs); + } + + for op in &input.comp_ops { + op.do_it(&mut rhs, &lhs); + } + + for op in &input.view_ops { + op.do_it(&rhs); + op.do_it(&lhs); + } + let mut v = Vec::new(); + for side in [lhs.clone(), rhs] { + v.clear(); + let data = side.serialize(); + let data2 = side.serialize_frozen_into(&mut v); + let view1 = unsafe { BitmapView::deserialize(&data) }; + let view2 = unsafe { BitmapView::deserialize_frozen(data2) }; + for op in &input.view_ops { + op.do_it(&view1); + op.do_it(&view2); + } + for op in &input.comp_ops { + op.do_it(&mut lhs, &view1); + op.do_it(&mut lhs, &view2); + } + } +}); + +#[derive(Arbitrary, Debug)] +struct FuzzInput { + lhs_ops: Vec, + rhs_ops: Vec, + comp_ops: Vec, + view_ops: Vec, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +struct Num(u32); + +impl<'a> Arbitrary<'a> for Num { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + Ok(Self(u.arbitrary::()? % (0x1_0000 * 4))) + } +} + +#[derive(Arbitrary, Debug)] +enum MutableBitmapOperation { + Add(Num), + AddChecked(Num), + AddMany(Vec), + AddRange(RangeInclusive), + RemoveRange(RangeInclusive), + Clear, + Remove(Num), + RemoveChecked(Num), + FlipInplace(RangeInclusive), + ShrinkToFit, + RunOptimize, + RemoveRunCompression, + // Probably turn it into a bitmap + SetEveryOther { key: u16 }, +} + +#[derive(Arbitrary, Debug)] +enum ReadBitmapOp { + ContainsRange(RangeInclusive), + Contains(Num), + RangeCardinality(RangeInclusive), + Cardinality, + Flip(RangeInclusive), + ToVec, + GetSerializedSizeInBytes, + GetFrozenSerializedSizeInBytes, + Serialize, + SerializeFrozen, + IsEmpty, + AddOffset(i64), + IntersectWithRange(RangeInclusive), + Minimum, + Maximum, + Rank(Num), + Select(Num), + Statistics, + Clone, + Debug, + WithIter(Vec), +} + +#[derive(Arbitrary, Debug)] +enum BitmapCompOperation { + Eq, + IsSubset, + IsStrictSubset, + Intersect, + JacardIndex, + And, + Or, + Xor, + AndNot, +} + +#[derive(Arbitrary, Debug)] +enum IterOperation { + ResetAtOrAfter(u32), + ReadNext, + NextMany(u16), +} + +impl ReadBitmapOp { + fn do_it(&self, b: &Bitmap) { + match *self { + ReadBitmapOp::ContainsRange(ref r) => { + b.contains_range(r.start().0..=r.end().0); + } + ReadBitmapOp::Contains(i) => { + b.contains(i.0); + } + ReadBitmapOp::RangeCardinality(ref r) => { + b.range_cardinality(r.start().0..=r.end().0); + } + ReadBitmapOp::Cardinality => { + b.cardinality(); + } + ReadBitmapOp::Flip(ref r) => { + b.flip(r.start().0..=r.end().0); + } + ReadBitmapOp::ToVec => { + b.to_vec(); + } + ReadBitmapOp::GetSerializedSizeInBytes => { + b.get_serialized_size_in_bytes(); + } + ReadBitmapOp::GetFrozenSerializedSizeInBytes => { + b.get_frozen_serialized_size_in_bytes(); + } + ReadBitmapOp::Serialize => { + b.serialize(); + } + ReadBitmapOp::SerializeFrozen => { + let mut v = Vec::new(); + b.serialize_frozen_into(&mut v); + } + ReadBitmapOp::IsEmpty => { + b.is_empty(); + } + ReadBitmapOp::IntersectWithRange(ref r) => { + b.intersect_with_range(r.start().0..=r.end().0); + } + ReadBitmapOp::Minimum => { + b.minimum(); + } + ReadBitmapOp::Maximum => { + b.maximum(); + } + ReadBitmapOp::Rank(i) => { + b.rank(i.0); + } + ReadBitmapOp::Select(i) => { + b.select(i.0); + } + ReadBitmapOp::Statistics => { + b.statistics(); + } + ReadBitmapOp::Clone => { + drop(b.clone()); + } + ReadBitmapOp::Debug => { + use std::io::Write; + write!(std::io::sink(), "{:?}", b).unwrap(); + } + ReadBitmapOp::WithIter(ref iter_ops) => { + let mut iter = b.iter(); + for op in iter_ops { + match *op { + IterOperation::ResetAtOrAfter(i) => { + iter.reset_at_or_after(i); + } + IterOperation::ReadNext => { + iter.next(); + } + IterOperation::NextMany(n) => { + let mut v = vec![0; n as usize]; + iter.next_many(&mut v); + } + } + } + } + ReadBitmapOp::AddOffset(i) => { + b.add_offset(i); + } + } + } +} + +impl MutableBitmapOperation { + fn do_it(self, b: &mut Bitmap) { + match self { + MutableBitmapOperation::Add(i) => { + b.add(i.0); + } + MutableBitmapOperation::AddChecked(i) => { + b.add_checked(i.0); + } + MutableBitmapOperation::AddMany(items) => { + let items: &[u32] = unsafe { mem::transmute(&items[..]) }; + b.add_many(&items); + } + MutableBitmapOperation::AddRange(r) => { + b.add_range(r.start().0..=r.end().0); + } + MutableBitmapOperation::RemoveRange(r) => { + b.remove_range(r.start().0..=r.end().0); + } + MutableBitmapOperation::Clear => { + b.clear(); + } + MutableBitmapOperation::Remove(i) => { + b.remove(i.0); + } + MutableBitmapOperation::RemoveChecked(i) => { + b.remove_checked(i.0); + } + MutableBitmapOperation::FlipInplace(r) => { + b.flip_inplace(r.start().0..=r.end().0); + } + MutableBitmapOperation::ShrinkToFit => { + b.shrink_to_fit(); + } + MutableBitmapOperation::RunOptimize => { + b.run_optimize(); + } + MutableBitmapOperation::RemoveRunCompression => { + b.remove_run_compression(); + } + MutableBitmapOperation::SetEveryOther { key } => { + let key = u64::from(key); + for i in (key * 0x1_0000..=(key + 1) * 0x1_0000).step_by(2) { + b.add(i as u32); + } + } + } + } +} + +impl BitmapCompOperation { + fn do_it(&self, lhs: &mut Bitmap, rhs: &Bitmap) { + match *self { + BitmapCompOperation::Eq => { + drop(lhs == rhs); + assert_eq!(lhs, lhs); + } + BitmapCompOperation::IsSubset => { + lhs.is_subset(rhs); + assert!(lhs.is_subset(lhs)); + } + BitmapCompOperation::IsStrictSubset => { + lhs.is_strict_subset(rhs); + assert!(!lhs.is_strict_subset(lhs)); + } + BitmapCompOperation::Intersect => { + lhs.intersect(rhs); + assert!(lhs.is_empty() || lhs.intersect(lhs)); + } + BitmapCompOperation::JacardIndex => { + lhs.jaccard_index(rhs); + lhs.jaccard_index(lhs); + } + BitmapCompOperation::And => { + assert_eq!(lhs.and(lhs), *lhs); + + let res = lhs.and(rhs); + assert_eq!(res.cardinality(), lhs.and_cardinality(rhs)); + lhs.and_inplace(rhs); + assert_eq!(*lhs, res); + } + BitmapCompOperation::Or => { + assert_eq!(lhs.or(lhs), *lhs); + + let res = lhs.or(rhs); + assert_eq!(res.cardinality(), lhs.or_cardinality(rhs)); + assert_eq!(res, Bitmap::fast_or(&[lhs, rhs])); + assert_eq!(res, Bitmap::fast_or_heap(&[lhs, rhs])); + + lhs.or_inplace(rhs); + assert_eq!(*lhs, res); + } + BitmapCompOperation::Xor => { + assert!(lhs.xor(lhs).is_empty()); + + let res = lhs.xor(rhs); + assert_eq!(res.cardinality(), lhs.xor_cardinality(rhs)); + assert_eq!(res, Bitmap::fast_xor(&[lhs, rhs])); + + lhs.xor_inplace(rhs); + assert_eq!(*lhs, res); + } + BitmapCompOperation::AndNot => { + assert!(lhs.andnot(lhs).is_empty()); + + let res = lhs.andnot(rhs); + assert_eq!(res.cardinality(), lhs.andnot_cardinality(rhs)); + + lhs.andnot_inplace(rhs); + assert_eq!(*lhs, res); + } + } + } +} diff --git a/fuzz/rust-toolchain.toml b/fuzz/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/fuzz/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/justfile b/justfile index 6e6eb6b..10aeba2 100755 --- a/justfile +++ b/justfile @@ -37,3 +37,12 @@ test_serialization_files: cd croaring/tests/data/ && \ cc create_serialization.c {{croaring_source / 'roaring.c'}} -I {{croaring_source}} -Wall -o create_serialization && \ ./create_serialization + +_get_cargo_fuzz: + command -v cargo-fuzz >/dev/null 2>&1 || cargo install cargo-fuzz + +fuzz: _get_cargo_fuzz + cd fuzz && \ + ASAN_OPTIONS="detect_leaks=1 detect_stack_use_after_return=1" \ + CC=clang CFLAGS=-fsanitize=address \ + cargo fuzz run fuzz_ops -s address -- -max_len=10000 -rss_limit_mb=4096 \ No newline at end of file