Skip to content

Fuzzing Crash: array_ops - Filter operation produces incorrect result for ListViewArray with empty structs #5410

Description

@github-actions

Fuzzing Crash Report

Analysis

Crash Location: fuzz/fuzz_targets/array_ops.rs:37

Error Message:

called `Result::unwrap()` on an `Err` value: [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}] != [] at index 2, lhs is root: vortex.listview(list({}?)?, len=4) nbytes=65 B (100.00%)

Stack Trace:

   0: std::backtrace_rs::backtrace::libunwind::trace
   1: std::backtrace_rs::backtrace::trace_unsynchronized
   2: std::backtrace::Backtrace::create
   3: assert_array_eq at ./fuzz/fuzz_targets/array_ops.rs:174:17
   4: __libfuzzer_sys_run at ./fuzz/fuzz_targets/array_ops.rs:37:17
   5: rust_fuzzer_test_input
   6: {closure#0}
   7: do_call<libfuzzer_sys::test_input_wrap::{closure_env#0}, i32>

Root Cause: The fuzzer detected incorrect behavior when applying a Filter operation to a ListViewArray containing empty struct elements.

The test flow:

  1. Starts with a ListViewArray (len=6) containing list(struct{}) elements with 20 empty struct elements
  2. Applies Filter operation with a mask (step 1)
  3. Expected result: List with 0 empty struct elements (elements_len: 0)
  4. Actual result: ListViewArray still showing 20 empty struct elements (elements_len: 20)

The issue appears to be that when filtering a ListViewArray, the filter operation correctly adjusts the top-level array length (from 6 to 4) and the offsets/sizes, but fails to properly update or filter the underlying elements array. This leaves stale element references in the filtered array.

After the filter:

  • Expected (List encoding): vortex.list with elements_len: 0, containing an empty struct array
  • Actual (ListViewArray encoding): vortex.listview with elements_len: 20, still referencing 20 empty structs that should have been removed

The compression step (step 2) appears to convert the ListViewArray to a List encoding, which then produces the correct result with 0 elements, exposing the discrepancy.

Debug Output
FuzzArrayAction {
    array: ListViewArray {
        dtype: List(
            Struct(
                StructFields {
                    names: FieldNames(
                        [],
                    ),
                    dtypes: [],
                },
                Nullable,
            ),
            Nullable,
        ),
        elements: StructArray {
            len: 20,
            dtype: Struct(
                StructFields {
                    names: FieldNames(
                        [],
                    ),
                    dtypes: [],
                },
                Nullable,
            ),
            fields: [],
            validity: AllValid,
        },
        offsets: PrimitiveArray {
            dtype: Primitive(I32, NonNullable),
            buffer: Buffer<u8> { length: 24, alignment: Alignment(4), as_slice: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...] },
            validity: NonNullable,
        },
        sizes: PrimitiveArray {
            dtype: Primitive(I32, NonNullable),
            buffer: Buffer<u8> { length: 24, alignment: Alignment(4), as_slice: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...] },
            validity: NonNullable,
        },
        is_zero_copy_to_list: true,
        validity: Array(
            BoolArray {
                dtype: Bool(NonNullable),
                buffer: BitBuffer { buffer: Buffer<u8> { length: 1, alignment: Alignment(1), as_slice: [16] }, offset: 0, len: 6 },
                validity: NonNullable,
            },
        ),
    },
    actions: [
        (
            Filter(
                Values(
                    MaskValues {
                        buffer: BitBuffer { buffer: Buffer<u8> { length: 1, alignment: Alignment(1), as_slice: [53] }, offset: 0, len: 6 },
                        indices: OnceLock(<uninit>),
                        slices: OnceLock(<uninit>),
                        true_count: 4,
                        density: 0.6666666666666666,
                    },
                ),
            ),
            Array(ListViewArray { len: 4, elements_len: 20, ... }),
        ),
        (
            Compress(Default),
            Array(ListViewArray { len: 4, elements_len: 20, ... }),
        ),
    ],
}

Summary

Reproduction

  1. Download the crash artifact:

  2. Reproduce locally:

# The artifact contains array_ops/crash-d43755f1f95c5825dffcc2a4ebf0534bafa45791
cargo +nightly fuzz run --sanitizer=none array_ops array_ops/crash-d43755f1f95c5825dffcc2a4ebf0534bafa45791
  1. Get full backtrace:
RUST_BACKTRACE=full cargo +nightly fuzz run --sanitizer=none array_ops array_ops/crash-d43755f1f95c5825dffcc2a4ebf0534bafa45791

Auto-created by fuzzing workflow with Claude analysis

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA bug issuefuzzerIssues detected by the fuzzer

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions