Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 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, features = ["arbitrary"] }
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
21 changes: 21 additions & 0 deletions fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use vortex_btrblocks::BtrBlocksCompressor;
use vortex_buffer::Buffer;
use vortex_dtype::DType;
use vortex_error::{VortexUnwrap, vortex_panic};
use vortex_expr::arbitrary::{filter_expr, projection_expr};
use vortex_expr::ExprRef;
use vortex_mask::Mask;
use vortex_scalar::Scalar;
use vortex_scalar::arbitrary::random_scalar;
Expand Down Expand Up @@ -224,3 +226,22 @@ 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)?,
})
}
}
1 change: 1 addition & 0 deletions vortex-expr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ version = { workspace = true }
workspace = true

[dependencies]
arbitrary = { workspace = true, optional = true }
dyn-hash = { workspace = true }
itertools = { workspace = true }
prost = { workspace = true, optional = true }
Expand Down
63 changes: 63 additions & 0 deletions vortex-expr/src/arbitrary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::cmp::max;

use arbitrary::{Result as AResult, Unstructured};
use vortex_dtype::{DType, FieldName};
use vortex_scalar::arbitrary::random_scalar;

use crate::{BinaryExpr, ExprRef, Operator, and_collect, get_item_scope, lit, pack};

pub fn projection_expr(u: &mut Unstructured<'_>, dtype: &DType) -> AResult<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::<AResult<Vec<_>>>()?;

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

pub fn filter_expr(u: &mut Unstructured<'_>, dtype: &DType) -> AResult<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::<AResult<Vec<_>>>()?;

Ok(and_collect(filters))
}

fn random_comparison(u: &mut Unstructured<'_>, col: &FieldName, dtype: &DType) -> AResult<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<'_>) -> AResult<Operator> {
Ok(match u.int_in_range(0..=5)? {
0 => Operator::Eq,
1 => Operator::NotEq,
2 => Operator::Gt,
3 => Operator::Gte,
4 => Operator::Lt,
5 => Operator::Lte,
_ => unreachable!("range 0..=5"),
})
}
2 changes: 2 additions & 0 deletions vortex-expr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use dyn_hash::DynHash;
mod binary;

mod analysis;
#[cfg(feature = "arbitrary")]
pub mod arbitrary;
mod between;
mod cast;
mod field;
Expand Down
Loading