diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 522c25107f..166ff054f0 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -2,19 +2,23 @@ name = "vortex-fuzz" version = "0.0.0" publish = false -edition = "2021" +edition = { workspace = true } license = { workspace = true } [package.metadata] cargo-fuzz = true [dependencies] -libfuzzer-sys = "0.4" +libfuzzer-sys = { version = "0.4" } vortex-array = { workspace = true, features = ["arbitrary"] } vortex-dtype = { workspace = true } vortex-sampling-compressor = { workspace = true } vortex-scalar = { workspace = true } +[lib] +name = "vortex_fuzz" +path = "src/lib.rs" + [[bin]] name = "fuzz_target_1" diff --git a/fuzz/fuzz_targets/fuzz_target_1.rs b/fuzz/fuzz_targets/fuzz_target_1.rs index 2bc10f09dd..075bb5c0c0 100644 --- a/fuzz/fuzz_targets/fuzz_target_1.rs +++ b/fuzz/fuzz_targets/fuzz_target_1.rs @@ -2,81 +2,39 @@ use std::collections::HashSet; -use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured}; use libfuzzer_sys::{fuzz_target, Corpus}; use vortex::compute::slice; use vortex::compute::unary::scalar_at; use vortex::encoding::EncodingId; use vortex::Array; -use vortex_sampling_compressor::compressors::alp::ALPCompressor; -use vortex_sampling_compressor::compressors::bitpacked::BitPackedCompressor; -use vortex_sampling_compressor::compressors::dict::DictCompressor; -use vortex_sampling_compressor::compressors::r#for::FoRCompressor; -use vortex_sampling_compressor::compressors::roaring_bool::RoaringBoolCompressor; -use vortex_sampling_compressor::compressors::roaring_int::RoaringIntCompressor; -use vortex_sampling_compressor::compressors::runend::DEFAULT_RUN_END_COMPRESSOR; -use vortex_sampling_compressor::compressors::sparse::SparseCompressor; -use vortex_sampling_compressor::compressors::zigzag::ZigZagCompressor; +use vortex_fuzz::FuzzArrayAction; use vortex_sampling_compressor::compressors::CompressorRef; use vortex_sampling_compressor::SamplingCompressor; use vortex_scalar::{PValue, Scalar, ScalarValue}; -fuzz_target!(|data: &[u8]| -> Corpus { - let mut u = Unstructured::new(data); - - let array = Array::arbitrary(&mut u).unwrap(); +fuzz_target!(|fuzz_action: FuzzArrayAction| -> Corpus { + let FuzzArrayAction { array, action } = fuzz_action; // TODO(adamg): We actually might want to test empty things, but I'm punting this issue for now if array.is_empty() { return Corpus::Reject; }; - match u.int_in_range(0..=9).unwrap() { - 0 => { - let start = u.choose_index(array.len()).unwrap(); - let stop = u.choose_index(array.len() - start).unwrap() + start; - let slice = slice(&array, start, stop).unwrap(); - assert_slice(&array, &slice, start); - } - 1 => match fuzz_compress(&array, &ALPCompressor) { - Some(compressed_array) => assert_array_eq(&array, &compressed_array), - None => return Corpus::Reject, - }, - 2 => match fuzz_compress(&array, &BitPackedCompressor) { - Some(compressed_array) => assert_array_eq(&array, &compressed_array), - None => return Corpus::Reject, - }, - 3 => match fuzz_compress(&array, &DictCompressor) { - Some(compressed_array) => assert_array_eq(&array, &compressed_array), - None => return Corpus::Reject, - }, - 4 => match fuzz_compress(&array, &FoRCompressor) { - Some(compressed_array) => assert_array_eq(&array, &compressed_array), - None => return Corpus::Reject, - }, - 5 => match fuzz_compress(&array, &RoaringBoolCompressor) { - Some(compressed_array) => assert_array_eq(&array, &compressed_array), - None => return Corpus::Reject, - }, - 6 => match fuzz_compress(&array, &RoaringIntCompressor) { - Some(compressed_array) => assert_array_eq(&array, &compressed_array), - None => return Corpus::Reject, - }, - 7 => match fuzz_compress(&array, &DEFAULT_RUN_END_COMPRESSOR) { - Some(compressed_array) => assert_array_eq(&array, &compressed_array), - None => return Corpus::Reject, - }, - 8 => match fuzz_compress(&array, &SparseCompressor) { - Some(compressed_array) => assert_array_eq(&array, &compressed_array), - None => return Corpus::Reject, - }, - 9 => match fuzz_compress(&array, &ZigZagCompressor) { - Some(compressed_array) => assert_array_eq(&array, &compressed_array), + + match action { + vortex_fuzz::Action::Compress(c) => match fuzz_compress(&array, c.as_ref()) { + Some(compressed_array) => { + assert_array_eq(&array, &compressed_array); + Corpus::Keep + } None => return Corpus::Reject, }, - _ => unreachable!(), + vortex_fuzz::Action::Slice(range) => { + let slice = slice(&array, range.start, range.end).unwrap(); + assert_slice(&array, &slice, range.start); + Corpus::Keep + } + vortex_fuzz::Action::NoOp => Corpus::Reject, } - - Corpus::Keep }); fn fuzz_compress(array: &Array, compressor_ref: CompressorRef<'_>) -> Option { diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs new file mode 100644 index 0000000000..1545c4f104 --- /dev/null +++ b/fuzz/src/lib.rs @@ -0,0 +1,70 @@ +use std::ops::Range; + +use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured}; +use vortex::Array; +use vortex_sampling_compressor::compressors::alp::ALPCompressor; +use vortex_sampling_compressor::compressors::bitpacked::BitPackedCompressor; +use vortex_sampling_compressor::compressors::dict::DictCompressor; +use vortex_sampling_compressor::compressors::r#for::FoRCompressor; +use vortex_sampling_compressor::compressors::roaring_bool::RoaringBoolCompressor; +use vortex_sampling_compressor::compressors::roaring_int::RoaringIntCompressor; +use vortex_sampling_compressor::compressors::runend::DEFAULT_RUN_END_COMPRESSOR; +use vortex_sampling_compressor::compressors::sparse::SparseCompressor; +use vortex_sampling_compressor::compressors::zigzag::ZigZagCompressor; +use vortex_sampling_compressor::compressors::EncodingCompressor; + +pub struct FuzzArrayAction { + pub array: Array, + pub action: Action, +} + +impl std::fmt::Debug for FuzzArrayAction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FuzzArrayAction") + .field("action", &self.action) + .field("array", &self.array) + .finish() + } +} + +#[derive()] +pub enum Action { + NoOp, + Compress(Box), + Slice(Range), +} + +impl std::fmt::Debug for Action { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::NoOp => write!(f, "NoOp"), + Self::Slice(arg0) => f.debug_tuple("Slice").field(arg0).finish(), + Self::Compress(c) => write!(f, "Compress({})", c.id()), + } + } +} + +impl<'a> Arbitrary<'a> for FuzzArrayAction { + fn arbitrary(u: &mut Unstructured<'a>) -> Result { + let array = Array::arbitrary(u)?; + let action = match u.int_in_range(0..=9)? { + 0 => { + let start = u.choose_index(array.len())?; + let stop = u.choose_index(array.len() - start).unwrap() + start; + Action::Slice(start..stop) + } + 1 => Action::Compress(Box::new(ALPCompressor) as _), + 2 => Action::Compress(Box::new(BitPackedCompressor) as _), + 3 => Action::Compress(Box::new(DictCompressor) as _), + 4 => Action::Compress(Box::new(FoRCompressor) as _), + 5 => Action::Compress(Box::new(RoaringBoolCompressor) as _), + 6 => Action::Compress(Box::new(RoaringIntCompressor) as _), + 7 => Action::Compress(Box::new(DEFAULT_RUN_END_COMPRESSOR) as _), + 8 => Action::Compress(Box::new(SparseCompressor) as _), + 9 => Action::Compress(Box::new(ZigZagCompressor) as _), + _ => Action::NoOp, + }; + + Ok(Self { array, action }) + } +}