Skip to content

Commit 9466616

Browse files
Fix ListViewArray size/offset type mismatch after compression
Fixes issue where CompactCompressor independently narrowed offset and size arrays for ListViewArray, resulting in incompatible types (e.g., U8 offsets with U16 sizes). The fix ensures that when sizes require a larger type than offsets after narrowing, offsets are cast to match the size type. This resolves the fuzzing crash: 'size type U16 (max 65535) must fit within offset type U8 (max 255)' Includes regression test that reproduces the crash scenario. Fixes #5322 Signed-off-by: Claude <[email protected]> Co-authored-by: Joe Isaacs <[email protected]>
1 parent 31b5671 commit 9466616

File tree

1 file changed

+64
-5
lines changed

1 file changed

+64
-5
lines changed

vortex-layout/src/layouts/compact.rs

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,29 @@ impl CompactCompressor {
136136
let compressed_elems = self.compress(listview.elements())?;
137137

138138
// Note that since the type of our offsets and sizes is not encoded in our `DType`,
139-
// we can narrow the widths.
140-
let compressed_offsets =
141-
self.compress(&listview.offsets().to_primitive().narrow()?.into_array())?;
142-
let compressed_sizes =
143-
self.compress(&listview.sizes().to_primitive().narrow()?.into_array())?;
139+
// we can narrow the widths. However, we must ensure that the size type can fit
140+
// within the offset type, so we need to determine compatible types.
141+
let narrowed_offsets = listview.offsets().to_primitive().narrow()?;
142+
let narrowed_sizes = listview.sizes().to_primitive().narrow()?;
143+
144+
// Ensure compatible types: if sizes have a larger type than offsets,
145+
// we need to cast offsets to match
146+
let sizes_ptype = narrowed_sizes.ptype();
147+
let offsets_ptype = narrowed_offsets.ptype();
148+
149+
let (final_offsets, final_sizes) = if sizes_ptype.byte_width() > offsets_ptype.byte_width() {
150+
// Cast offsets to match sizes type
151+
let casted_offsets = vortex_array::compute::cast(
152+
&narrowed_offsets.into_array(),
153+
narrowed_sizes.dtype(),
154+
)?;
155+
(casted_offsets, narrowed_sizes.into_array())
156+
} else {
157+
(narrowed_offsets.into_array(), narrowed_sizes.into_array())
158+
};
159+
160+
let compressed_offsets = self.compress(&final_offsets)?;
161+
let compressed_sizes = self.compress(&final_sizes)?;
144162

145163
// SAFETY: Since compression does not change the logical values of arrays, this is
146164
// effectively the same array but represented differently, so all invariants that
@@ -251,4 +269,45 @@ mod tests {
251269
assert_arrays_eq!(decompressed_array.as_ref(), columns[i].as_ref());
252270
}
253271
}
272+
273+
#[test]
274+
fn test_listview_narrow_compatible_types() {
275+
use vortex_array::arrays::ListViewArray;
276+
277+
let compressor = CompactCompressor::default();
278+
279+
// Create a ListViewArray where after narrowing:
280+
// - offsets would narrow to U8 (all values < 255)
281+
// - sizes would narrow to U16 (some values > 255)
282+
// This reproduces the fuzzer crash where sizes had a larger type than offsets
283+
let elements = PrimitiveArray::new(
284+
buffer![1u32; 300], // 300 elements of value 1
285+
Validity::NonNullable,
286+
)
287+
.into_array();
288+
289+
// Offsets that fit in U8 (all < 255)
290+
let offsets = PrimitiveArray::new(
291+
buffer![0u32, 10, 20, 30], // All offsets < 255
292+
Validity::NonNullable,
293+
)
294+
.into_array();
295+
296+
// Sizes where at least one exceeds U8 max (255)
297+
let sizes = PrimitiveArray::new(
298+
buffer![10u32, 10, 10, 260], // Last size is 260 > 255, requires U16
299+
Validity::NonNullable,
300+
)
301+
.into_array();
302+
303+
let listview =
304+
ListViewArray::new(elements.clone(), offsets, sizes, Validity::NonNullable).unwrap();
305+
306+
// This should not panic - the fix ensures compatible types
307+
let compressed = compressor.compress(listview.as_ref()).unwrap();
308+
309+
// Verify the compressed array is still valid
310+
let decompressed = compressed.to_canonical().into_array();
311+
assert_eq!(decompressed.len(), 4);
312+
}
254313
}

0 commit comments

Comments
 (0)