Skip to content

Commit

Permalink
Generate more structured inputs for fuzzing (#635)
Browse files Browse the repository at this point in the history
Closes #629. Doesn't introduce any functional changes but will make
future extension easier IMO.
  • Loading branch information
AdamGS authored Aug 15, 2024
1 parent 879bced commit 9baeaf4
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 62 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ edition = "2021"
rust-version = "1.76"

[workspace.dependencies]
libfuzzer-sys = "0.4"
ahash = "0.8.11"
allocator-api2 = "0.2.16"
anyhow = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion bench-vortex/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ arrow-schema = { workspace = true }
arrow-select = { workspace = true }
bytes = { workspace = true }
bzip2 = { workspace = true }
csv = { workspace = true }
clap = { workspace = true, features = ["derive"] }
csv = { workspace = true }
datafusion = { workspace = true }
enum-iterator = { workspace = true }
flexbuffers = { workspace = true }
Expand Down
7 changes: 5 additions & 2 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@
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 = { workspace = true }
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"
Expand Down
73 changes: 15 additions & 58 deletions fuzz/fuzz_targets/fuzz_target_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,38 @@

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::{Action, 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, actions } = 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 &actions[0] {
Action::Compress(c) => match fuzz_compress(&array, *c) {
Some(compressed_array) => {
assert_array_eq(&array, &compressed_array);
Corpus::Keep
}
None => return Corpus::Reject,
},
_ => unreachable!(),
Action::Slice(range) => {
let slice = slice(&array, range.start, range.end).unwrap();
assert_slice(&array, &slice, range.start);
Corpus::Keep
}
}

Corpus::Keep
});

fn fuzz_compress(array: &Array, compressor_ref: CompressorRef<'_>) -> Option<Array> {
Expand Down
71 changes: 71 additions & 0 deletions fuzz/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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 actions: Vec<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.actions)
.field("array", &self.array)
.finish()
}
}

#[derive()]
pub enum Action {
Compress(&'static dyn EncodingCompressor),
Slice(Range<usize>),
}

impl std::fmt::Debug for Action {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
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<Self> {
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)? + start;
Action::Slice(start..stop)
}
1 => Action::Compress(&ALPCompressor),
2 => Action::Compress(&BitPackedCompressor),
3 => Action::Compress(&DictCompressor),
4 => Action::Compress(&FoRCompressor),
5 => Action::Compress(&RoaringBoolCompressor),
6 => Action::Compress(&RoaringIntCompressor),
7 => Action::Compress(&DEFAULT_RUN_END_COMPRESSOR),
8 => Action::Compress(&SparseCompressor),
9 => Action::Compress(&ZigZagCompressor),
_ => unreachable!(),
};

Ok(Self {
array,
actions: vec![action],
})
}
}
2 changes: 1 addition & 1 deletion vortex-expr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ prost = { workspace = true, optional = true }
serde = { workspace = true, optional = true, features = ["derive"] }
vortex-dtype = { workspace = true }
vortex-error = { workspace = true }
vortex-scalar = { workspace = true }
vortex-proto = { workspace = true, optional = true }
vortex-scalar = { workspace = true }

[features]
datafusion = ["dep:datafusion-common", "dep:datafusion-expr", "vortex-scalar/datafusion"]
Expand Down

0 comments on commit 9baeaf4

Please sign in to comment.