Skip to content

Commit 9c96662

Browse files
authored
Fix fuzzer incorrect mask application (#6877)
#6515 - Apply validity & mask in fuzzer target generation. Before, validity & !mask was generated. - Add tracing logging to fuzzer - Add debug output for fuzzer mask, info output for mistmatches, initial array, and fuzz actions Signed-off-by: Mikhail Kot <mikhail@spiraldb.com>
1 parent e0f2dbb commit 9c96662

11 files changed

Lines changed: 131 additions & 37 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fuzz/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ vortex-file = { workspace = true, optional = true }
5050
tokio = { workspace = true, features = ["rt", "macros"], optional = true }
5151
vortex-cuda = { workspace = true, optional = true }
5252

53+
tracing = { workspace = true }
54+
tracing-subscriber = { workspace = true }
55+
5356
[lints]
5457
workspace = true
5558

fuzz/fuzz_targets/array_ops.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ use vortex_error::vortex_panic;
1010
use vortex_fuzz::FuzzArrayAction;
1111
use vortex_fuzz::run_fuzz_action;
1212

13-
fuzz_target!(|fuzz_action: FuzzArrayAction| -> Corpus {
13+
fuzz_target!(
14+
init: {
15+
tracing_subscriber::fmt::init();
16+
},
17+
|fuzz_action: FuzzArrayAction| -> Corpus {
1418
match run_fuzz_action(fuzz_action) {
1519
Ok(true) => Corpus::Keep,
1620
Ok(false) => Corpus::Reject,

fuzz/src/array/mask.rs

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,31 @@ use vortex_error::VortexResult;
2323
use vortex_mask::AllOr;
2424
use vortex_mask::Mask;
2525

26-
/// Set to false any entries for which the mask is true.
27-
///
26+
/// Apply a logical AND of a validity and a mask.
27+
/// This needs to be coherent with applications of Mask.
2828
/// The result is always nullable. The result has the same length as self.
2929
#[inline]
3030
pub fn mask_validity(validity: &Validity, mask: &Mask) -> Validity {
31-
match mask.bit_buffer() {
32-
AllOr::All => Validity::AllInvalid,
33-
AllOr::None => validity.clone().into_nullable(),
34-
AllOr::Some(make_invalid) => match validity {
31+
let out = match mask.bit_buffer() {
32+
AllOr::All => validity.clone().into_nullable(),
33+
AllOr::None => Validity::AllInvalid,
34+
AllOr::Some(make_valid) => match validity {
35+
Validity::AllInvalid => Validity::AllInvalid,
3536
Validity::NonNullable | Validity::AllValid => {
36-
Validity::from_bit_buffer(!make_invalid, Nullability::Nullable)
37+
Validity::from_bit_buffer(make_valid.clone(), Nullability::Nullable)
3738
}
38-
Validity::AllInvalid => Validity::AllInvalid,
3939
Validity::Array(is_valid) => {
4040
let is_valid = is_valid.to_bool();
4141
Validity::from_bit_buffer(
42-
is_valid.to_bit_buffer() & !make_invalid,
42+
is_valid.to_bit_buffer() & make_valid,
4343
Nullability::Nullable,
4444
)
4545
}
4646
},
47-
}
47+
};
48+
49+
tracing::debug!(validity = ?validity, mask = ?mask, out = ?out, "generated fuzzer mask");
50+
out
4851
}
4952

5053
/// Apply mask on the canonical form of the array to get a consistent baseline.
@@ -173,7 +176,7 @@ mod tests {
173176
#[test]
174177
fn test_mask_bool_array() {
175178
let array = BoolArray::from_iter([true, false, true, false, true]);
176-
let mask = Mask::from_iter([true, false, false, true, false]);
179+
let mask = Mask::from_iter([false, true, true, false, true]);
177180

178181
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
179182

@@ -184,7 +187,7 @@ mod tests {
184187
#[test]
185188
fn test_mask_primitive_array() {
186189
let array = PrimitiveArray::from_iter([1i32, 2, 3, 4, 5]);
187-
let mask = Mask::from_iter([false, true, false, true, false]);
190+
let mask = Mask::from_iter([true, false, true, false, true]);
188191

189192
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
190193

@@ -195,7 +198,7 @@ mod tests {
195198
#[test]
196199
fn test_mask_primitive_array_with_nulls() {
197200
let array = PrimitiveArray::from_option_iter([Some(1i32), None, Some(3), Some(4), None]);
198-
let mask = Mask::from_iter([true, false, false, true, false]);
201+
let mask = Mask::from_iter([false, true, true, false, true]);
199202

200203
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
201204

@@ -210,7 +213,7 @@ mod tests {
210213
[Some(1i128), Some(2), Some(3), Some(4), Some(5)],
211214
dtype,
212215
);
213-
let mask = Mask::from_iter([false, false, true, false, false]);
216+
let mask = Mask::from_iter([true, true, false, true, true]);
214217

215218
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
216219

@@ -222,7 +225,7 @@ mod tests {
222225
#[test]
223226
fn test_mask_varbinview_array() {
224227
let array = VarBinViewArray::from_iter_str(["one", "two", "three", "four", "five"]);
225-
let mask = Mask::from_iter([true, false, true, false, true]);
228+
let mask = Mask::from_iter([false, true, false, true, false]);
226229

227230
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
228231

@@ -241,7 +244,7 @@ mod tests {
241244
.with_zero_copy_to_list(true)
242245
};
243246

244-
let mask = Mask::from_iter([false, true, false]);
247+
let mask = Mask::from_iter([true, false, true]);
245248

246249
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
247250

@@ -257,7 +260,7 @@ mod tests {
257260
let array =
258261
FixedSizeListArray::try_new(elements, 2, Nullability::NonNullable.into(), 3).unwrap();
259262

260-
let mask = Mask::from_iter([true, false, true]);
263+
let mask = Mask::from_iter([false, true, false]);
261264

262265
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
263266

@@ -281,7 +284,7 @@ mod tests {
281284
)
282285
.unwrap();
283286

284-
let mask = Mask::from_iter([false, true, false]);
287+
let mask = Mask::from_iter([true, false, true]);
285288

286289
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
287290

@@ -292,9 +295,9 @@ mod tests {
292295
}
293296

294297
#[test]
295-
fn test_mask_all_true() {
298+
fn test_mask_all_false() {
296299
let array = PrimitiveArray::from_iter([1i32, 2, 3, 4, 5]);
297-
let mask = Mask::AllTrue(5);
300+
let mask = Mask::AllFalse(5);
298301

299302
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
300303

@@ -303,9 +306,9 @@ mod tests {
303306
}
304307

305308
#[test]
306-
fn test_mask_all_false() {
309+
fn test_mask_all_true() {
307310
let array = PrimitiveArray::from_iter([1i32, 2, 3, 4, 5]);
308-
let mask = Mask::AllFalse(5);
311+
let mask = Mask::AllTrue(5);
309312

310313
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
311314

@@ -317,10 +320,9 @@ mod tests {
317320
#[test]
318321
fn test_mask_empty_array() {
319322
let array = PrimitiveArray::from_iter(Vec::<i32>::new());
320-
let mask = Mask::AllFalse(0);
321-
322-
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
323-
324-
assert_eq!(result.len(), 0);
323+
for mask in [Mask::AllFalse(0), Mask::AllTrue(0)] {
324+
let result = mask_canonical_array(array.to_canonical().unwrap(), &mask).unwrap();
325+
assert_eq!(result.len(), 0);
326+
}
325327
}
326328
}

fuzz/src/array/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use strum::EnumIter;
3737
use strum::IntoEnumIterator;
3838
pub(crate) use sum::*;
3939
pub(crate) use take::*;
40+
use tracing::info;
4041
use vortex_array::ArrayRef;
4142
use vortex_array::DynArray;
4243
use vortex_array::IntoArray;
@@ -560,7 +561,14 @@ pub fn run_fuzz_action(fuzz_action: FuzzArrayAction) -> crate::error::VortexFuzz
560561
let FuzzArrayAction { array, actions } = fuzz_action;
561562
let mut current_array = array.to_array();
562563

564+
info!(
565+
"Initial array:\nTree:\n{}Values:\n{:#}",
566+
current_array.display_tree(),
567+
current_array.display_values()
568+
);
569+
563570
for (i, (action, expected)) in actions.into_iter().enumerate() {
571+
info!(id = i, action = ?action);
564572
match action {
565573
Action::Compress(strategy) => {
566574
let canonical = current_array

fuzz/src/error.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,29 @@ impl Display for VortexFuzzError {
9797
"MinMax mismatch: expected {lhs:?} got {rhs:?} in step {step}\nBacktrace:\n{backtrace}"
9898
)
9999
}
100-
VortexFuzzError::ArrayNotEqual(expected, actual, idx, lhs, rhs, step, backtrace) => {
100+
VortexFuzzError::ArrayNotEqual(
101+
expected_scalar,
102+
actual_scalar,
103+
idx,
104+
expected_array,
105+
current_array,
106+
step,
107+
backtrace,
108+
) => {
109+
let expected_tree = expected_array.display_tree();
110+
let current_tree = current_array.display_tree();
111+
let expected_values = expected_array.display_values();
112+
let current_values = current_array.display_values();
101113
write!(
102114
f,
103-
"{expected} != {actual} at index {idx}, lhs is {} rhs is {} in step {step}\nBacktrace:\n{backtrace}",
104-
lhs.display_tree(),
105-
rhs.display_tree(),
115+
"Mismatch at step {step} at index {idx}\n\
116+
Expected scalar:\n{expected_scalar}\n\
117+
Actual scalar:\n{actual_scalar}\n\
118+
Expected tree:\n{expected_tree}\n\
119+
Current tree:\n{current_tree}\
120+
Expected values:\n{expected_values:#}\n\
121+
Current values:\n{current_values:#}\
122+
\n{backtrace}"
106123
)
107124
}
108125
VortexFuzzError::DTypeMismatch(lhs, rhs, step, backtrace) => {

vortex-array/src/display/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,8 +476,9 @@ impl dyn DynArray + '_ {
476476
DisplayOptions::CommaSeparatedScalars {
477477
omit_comma_after_space,
478478
} => {
479-
write!(f, "[")?;
479+
write!(f, "{}", if f.alternate() { "[\n" } else { "[" })?;
480480
let sep = if *omit_comma_after_space { "," } else { ", " };
481+
let sep = if f.alternate() { ",\n" } else { sep };
481482
write!(
482483
f,
483484
"{}",
@@ -487,7 +488,7 @@ impl dyn DynArray + '_ {
487488
.map_or_else(|e| format!("<error: {e}>"), |s| s.to_string()))
488489
.format(sep)
489490
)?;
490-
write!(f, "]")
491+
write!(f, "{}", if f.alternate() { "\n]" } else { "]" })
491492
}
492493
DisplayOptions::TreeDisplay {
493494
buffers,

vortex-array/src/validity.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use crate::scalar_fn::fns::binary::Binary;
3636
use crate::scalar_fn::fns::operators::Operator;
3737

3838
/// Validity information for an array
39-
#[derive(Clone, Debug)]
39+
#[derive(Clone)]
4040
pub enum Validity {
4141
/// Items *can't* be null
4242
NonNullable,
@@ -50,6 +50,17 @@ pub enum Validity {
5050
Array(ArrayRef),
5151
}
5252

53+
impl Debug for Validity {
54+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55+
match self {
56+
Self::NonNullable => write!(f, "NonNullable"),
57+
Self::AllValid => write!(f, "AllValid"),
58+
Self::AllInvalid => write!(f, "AllInvalid"),
59+
Self::Array(arr) => write!(f, "SomeValid({})", arr.as_ref().display_values()),
60+
}
61+
}
62+
}
63+
5364
impl Validity {
5465
/// Make a step towards canonicalising validity if necessary
5566
pub fn execute(self, ctx: &mut ExecutionCtx) -> VortexResult<Validity> {

vortex-buffer/public-api.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,10 @@ impl core::fmt::Debug for vortex_buffer::BitBuffer
336336

337337
pub fn vortex_buffer::BitBuffer::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
338338

339+
impl core::fmt::Display for vortex_buffer::BitBuffer
340+
341+
pub fn vortex_buffer::BitBuffer::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
342+
339343
impl core::iter::traits::collect::FromIterator<bool> for vortex_buffer::BitBuffer
340344

341345
pub fn vortex_buffer::BitBuffer::from_iter<T: core::iter::traits::collect::IntoIterator<Item = bool>>(iter: T) -> Self

vortex-buffer/src/bit/buf.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// SPDX-License-Identifier: Apache-2.0
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

4+
use std::fmt::Display;
5+
use std::fmt::Formatter;
6+
use std::fmt::Result as FmtResult;
47
use std::ops::BitAnd;
58
use std::ops::BitOr;
69
use std::ops::BitXor;
@@ -35,6 +38,18 @@ pub struct BitBuffer {
3538
len: usize,
3639
}
3740

41+
const LIMIT_LEN: usize = 16;
42+
impl Display for BitBuffer {
43+
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
44+
let limit = f.precision().unwrap_or(LIMIT_LEN);
45+
let buf: Vec<bool> = self.into_iter().take(limit).collect();
46+
f.debug_struct("BitBuffer")
47+
.field("len", &self.len)
48+
.field("buffer", &buf)
49+
.finish()
50+
}
51+
}
52+
3853
impl PartialEq for BitBuffer {
3954
fn eq(&self, other: &Self) -> bool {
4055
if self.len != other.len {

0 commit comments

Comments
 (0)