Skip to content

Fuzzing Crash: fill_null on DecimalArray fails when fill value has null decimal_value #5744

@github-actions

Description

@github-actions

Fuzzing Crash Report

Analysis

Crash Location: vortex-array/src/arrays/decimal/compute/fill_null.rs:35 (in fill_null function)

Error Message:

top-level fill_null ensure non-null fill value

Stack Trace:

   6: fill_null
             at ./vortex-array/src/arrays/decimal/compute/fill_null.rs:35:26
   7: invoke<vortex_array::arrays::decimal::vtable::DecimalVTable>
             at ./vortex-array/src/compute/fill_null.rs:84:13
   8: invoke
             at ./vortex-array/src/compute/fill_null.rs:114:42
   9: invoke
             at ./vortex-array/src/compute/mod.rs:149:34
  10: fill_null
             at ./vortex-array/src/compute/fill_null.rs:54:10
  11: run_fuzz_action
             at ./fuzz/src/array/mod.rs:594:33

Root Cause:

The fuzzer discovered a bug in the decimal fill_null implementation. When attempting to fill null values in a DecimalArray using a fill value scalar, the code panics because the fill value's decimal_value() returns None, even though the scalar has NonNullable dtype.

Looking at the crash location in vortex-array/src/arrays/decimal/compute/fill_null.rs:35:

let fill_value = fill_value
    .as_decimal()
    .decimal_value()
    .and_then(|v| v.cast::<T>())
    .vortex_expect("top-level fill_null ensure non-null fill value");

The issue occurs with:

  • Array type: DecimalArray with I256 values (decimal256)
  • Decimal dtype: Decimal(precision=39, scale=-53, Nullable)
  • Fill value scalar: Decimal(precision=39, scale=-53, NonNullable) with I256 value -341667777821006224650557019145323413491
  • Operation sequence:
    1. Take operation on DecimalArray (produces array with nulls)
    2. Compress operation
    3. FillNull operation (crashes here)

The fill value scalar shows as NonNullable and appears to have a valid I256 value in the debug output, but internally decimal_value() returns None, causing the panic. This suggests an issue with:

  1. How decimal scalars with I256/I128 values store or retrieve their internal values
  2. Type casting or conversion issues for decimal256 values
  3. The scalar's internal state being inconsistent with its dtype's nullability
Debug Output
FuzzArrayAction {
    array: DecimalArray {
        dtype: Decimal(
            DecimalDType {
                precision: 39,
                scale: -53,
            },
            Nullable,
        ),
        values: Buffer<u8> {
            length: 96,
            alignment: Alignment(
                16,
            ),
            as_slice: [117, 73, 73, 73, 73, 228, 216, 221, 140, 146, 73, 163, 37, 123, 121, 84, ...],
        },
        values_type: I256,
        validity: AllValid,
        stats_set: ArrayStats {
            inner: RwLock {
                data: StatsSet {
                    values: [],
                },
            },
        },
    },
    actions: [
        (
            Take(
                BitPackedArray {
                    offset: 0,
                    len: 29,
                    dtype: Primitive(
                        U64,
                        Nullable,
                    ),
                    bit_width: 0,
                    packed: Buffer<u8> {
                        length: 0,
                        alignment: Alignment(
                            8,
                        ),
                        as_slice: [],
                    },
                    patches: None,
                    validity: Array(
                        BoolArray {
                            dtype: Bool(
                                NonNullable,
                            ),
                            bits: BitBuffer {
                                buffer: Buffer<u8> {
                                    length: 4,
                                    alignment: Alignment(
                                        1,
                                    ),
                                    as_slice: [32, 0, 0, 0],
                                },
                                offset: 0,
                                len: 29,
                            },
                            validity: NonNullable,
                            stats_set: ArrayStats {
                                inner: RwLock {
                                    data: StatsSet {
                                        values: [
                                            (
                                                Sum,
                                                Exact(
                                                    ScalarValue(
                                                        Primitive(
                                                            U64(
                                                                1,
                                                            ),
                                                        ),
                                                    ),
                                                ),
                                            ),
                                        ],
                                    },
                                },
                            },
                        },
                    ),
                    stats_set: ArrayStats {
                        inner: RwLock {
                            data: StatsSet {
                                values: [],
                            },
                        },
                    },
                },
            ),
            Array(
                DecimalArray {
                    dtype: Decimal(
                        DecimalDType {
                            precision: 39,
                            scale: -53,
                        },
                        Nullable,
                    ),
                    values: Buffer<u8> {
                        length: 928,
                        alignment: Alignment(
                            16,
                        ),
                        as_slice: [117, 73, 73, 73, 73, 228, 216, 221, 140, 146, 73, 163, 37, 123, 121, 84, ...],
                    },
                    values_type: I256,
                    validity: Array(
                        BoolArray {
                            dtype: Bool(
                                NonNullable,
                            ),
                            bits: BitBuffer {
                                buffer: Buffer<u8> {
                                    length: 4,
                                    alignment: Alignment(
                                        1,
                                    ),
                                    as_slice: [32, 0, 0, 0],
                                },
                                offset: 0,
                                len: 29,
                            },
                            validity: NonNullable,
                            stats_set: ArrayStats {
                                inner: RwLock {
                                    data: StatsSet {
                                        values: [],
                                    },
                                },
                            },
                        },
                    ),
                    stats_set: ArrayStats {
                        inner: RwLock {
                            data: StatsSet {
                                values: [],
                            },
                        },
                    },
                },
            ),
        ),
        (
            Compress(
                Default,
            ),
            Array(
                DecimalArray {
                    dtype: Decimal(
                        DecimalDType {
                            precision: 39,
                            scale: -53,
                        },
                        Nullable,
                    ),
                    values: Buffer<u8> {
                        length: 928,
                        alignment: Alignment(
                            16,
                        ),
                        as_slice: [117, 73, 73, 73, 73, 228, 216, 221, 140, 146, 73, 163, 37, 123, 121, 84, ...],
                    },
                    values_type: I256,
                    validity: Array(
                        BoolArray {
                            dtype: Bool(
                                NonNullable,
                            ),
                            bits: BitBuffer {
                                buffer: Buffer<u8> {
                                    length: 4,
                                    alignment: Alignment(
                                        1,
                                    ),
                                    as_slice: [32, 0, 0, 0],
                                },
                                offset: 0,
                                len: 29,
                            },
                            validity: NonNullable,
                            stats_set: ArrayStats {
                                inner: RwLock {
                                    data: StatsSet {
                                        values: [],
                                    },
                                },
                            },
                        },
                    ),
                    stats_set: ArrayStats {
                        inner: RwLock {
                            data: StatsSet {
                                values: [],
                            },
                        },
                    },
                },
            ),
        ),
        (
            FillNull(
                Scalar {
                    dtype: Decimal(
                        DecimalDType {
                            precision: 39,
                            scale: -53,
                        },
                        NonNullable,
                    ),
                    value: ScalarValue(
                        Decimal(
                            I256(
                                i256(
                                    -341667777821006224650557019145323413491,
                                ),
                            ),
                        ),
                    ),
                },
            ),
            Array(
                DecimalArray {
                    dtype: Decimal(
                        DecimalDType {
                            precision: 39,
                            scale: -53,
                        },
                        NonNullable,
                    ),
                    values: Buffer<u8> {
                        length: 928,
                        alignment: Alignment(
                            16,
                        ),
                        as_slice: [13, 0, 0, 0, 128, 224, 79, 240, 209, 78, 100, 13, 248, 45, 245, 254, ...],
                    },
                    values_type: I256,
                    validity: NonNullable,
                    stats_set: ArrayStats {
                        inner: RwLock {
                            data: StatsSet {
                                values: [],
                            },
                        },
                    },
                },
            ),
        ),
    ],
}

Summary

Reproduction

  1. Download the crash artifact:

  2. Reproduce locally:

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

Auto-created by fuzzing workflow with Claude analysis

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions