Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ vortex-btrblocks = { workspace = true }
vortex-buffer = { workspace = true }
vortex-dtype = { workspace = true, features = ["arbitrary"] }
vortex-error = { workspace = true }
vortex-expr = { workspace = true }
vortex-file = { workspace = true }
vortex-mask = { workspace = true }
vortex-scalar = { workspace = true, features = ["arbitrary"] }
Expand Down
14 changes: 11 additions & 3 deletions fuzz/fuzz_targets/file_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@ use arrow_ord::sort::SortOptions;
use futures_util::TryStreamExt;
use libfuzzer_sys::{Corpus, fuzz_target};
use vortex_array::arrays::ChunkedArray;
use vortex_array::arrays::arbitrary::ArbitraryArray;
use vortex_array::arrow::IntoArrowArray;
use vortex_array::compute::{Operator, compare};
use vortex_array::{Array, ArrayRef, Canonical, IntoArray, ToCanonical};
use vortex_buffer::ByteBufferMut;
use vortex_dtype::{DType, StructFields};
use vortex_error::{VortexExpect, VortexUnwrap, vortex_panic};
use vortex_expr::root;
use vortex_file::{VortexOpenOptions, VortexWriteOptions};
use vortex_fuzz::FuzzFileAction;
use vortex_utils::aliases::DefaultHashBuilder;
use vortex_utils::aliases::hash_set::HashSet;

fuzz_target!(|array_data: ArbitraryArray| -> Corpus {
let array_data = array_data.0;
fuzz_target!(|fuzz: FuzzFileAction| -> Corpus {
let FuzzFileAction {
array,
projection,
filter,
} = fuzz;
let array_data = array;

if has_nullable_struct(array_data.dtype()) || has_duplicate_field_names(array_data.dtype()) {
return Corpus::Reject;
Expand All @@ -41,6 +47,8 @@ fuzz_target!(|array_data: ArbitraryArray| -> Corpus {
.vortex_unwrap()
.scan()
.vortex_unwrap()
.with_projection(projection.unwrap_or_else(|| root()))
.with_some_filter(filter)
.into_array_stream()
.vortex_unwrap()
.try_collect::<Vec<_>>()
Expand Down
80 changes: 79 additions & 1 deletion fuzz/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![feature(error_generic_member_access)]
extern crate core;

mod compare;
pub mod error;
Expand All @@ -8,6 +9,7 @@ mod slice;
mod sort;
mod take;

use std::cmp::max;
use std::fmt::Debug;
use std::iter;
use std::ops::{Range, RangeInclusive};
Expand All @@ -21,8 +23,9 @@ use vortex_array::search_sorted::{SearchResult, SearchSortedSide};
use vortex_array::{Array, ArrayRef, IntoArray};
use vortex_btrblocks::BtrBlocksCompressor;
use vortex_buffer::Buffer;
use vortex_dtype::DType;
use vortex_dtype::{DType, FieldName};
use vortex_error::{VortexUnwrap, vortex_panic};
use vortex_expr::{BinaryExpr, ExprRef, and_collect, get_item_scope, lit, pack};
use vortex_mask::Mask;
use vortex_scalar::Scalar;
use vortex_scalar::arbitrary::random_scalar;
Expand Down Expand Up @@ -224,3 +227,78 @@ fn actions_for_dtype(dtype: &DType) -> HashSet<usize> {
_ => ALL_ACTIONS.collect(),
}
}

#[derive(Debug)]
pub struct FuzzFileAction {
pub array: ArrayRef,
pub projection: Option<ExprRef>,
pub filter: Option<ExprRef>,
}

impl<'a> Arbitrary<'a> for FuzzFileAction {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let array = ArbitraryArray::arbitrary(u)?.0;
let dtype = array.dtype().clone();
Ok(FuzzFileAction {
array,
projection: projection_expr(u, &dtype)?,
filter: filter_expr(u, &dtype)?,
})
}
}

fn projection_expr(u: &mut Unstructured<'_>, dtype: &DType) -> Result<Option<ExprRef>> {
let Some(struct_dtype) = dtype.as_struct() else {
return Ok(None);
};

let column_count = u.int_in_range::<usize>(0..=max(struct_dtype.nfields(), 10))?;

let cols = (0..column_count)
.map(|_| {
let get_item = u.choose(struct_dtype.names().iter().as_slice())?;
Ok((get_item.clone(), get_item_scope(get_item.clone())))
})
.collect::<Result<Vec<_>>>()?;

Ok(Some(pack(cols, u.arbitrary()?)))
}

fn filter_expr(u: &mut Unstructured<'_>, dtype: &DType) -> Result<Option<ExprRef>> {
let Some(struct_dtype) = dtype.as_struct() else {
return Ok(None);
};

let filter_count = u.int_in_range::<usize>(0..=max(struct_dtype.nfields(), 10))?;

let filters = (0..filter_count)
.map(|_| {
let (col, dtype) =
u.choose_iter(struct_dtype.names().iter().zip(struct_dtype.fields()))?;
random_comparison(u, col, &dtype)
})
.collect::<Result<Vec<_>>>()?;

Ok(and_collect(filters))
}

fn random_comparison(u: &mut Unstructured<'_>, col: &FieldName, dtype: &DType) -> Result<ExprRef> {
let scalar = random_scalar(u, dtype)?;
Ok(BinaryExpr::new_expr(
get_item_scope(col.clone()),
arbitrary_comparison_operator(u)?,
lit(scalar),
))
}

fn arbitrary_comparison_operator(u: &mut Unstructured<'_>) -> Result<vortex_expr::Operator> {
Ok(match u.int_in_range(0..=5)? {
0 => vortex_expr::Operator::Eq,
1 => vortex_expr::Operator::NotEq,
2 => vortex_expr::Operator::Gt,
3 => vortex_expr::Operator::Gte,
4 => vortex_expr::Operator::Lt,
5 => vortex_expr::Operator::Lte,
_ => unreachable!("range 0..=5"),
})
}
2 changes: 1 addition & 1 deletion vortex-array/src/arrays/arbitrary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ fn split_number_into_parts(n: usize, parts: usize) -> Vec<usize> {
.collect()
}

fn random_array(u: &mut Unstructured, dtype: &DType, len: Option<usize>) -> Result<ArrayRef> {
pub fn random_array(u: &mut Unstructured, dtype: &DType, len: Option<usize>) -> Result<ArrayRef> {
let num_chunks = u.int_in_range(1..=3)?;
let chunk_lens = len.map(|l| split_number_into_parts(l, num_chunks));
let mut chunks = (0..num_chunks)
Expand Down
Loading