From b3807af62330238db7c8033f81eac491e3216cad Mon Sep 17 00:00:00 2001 From: SHIVANSH-ux-ys Date: Mon, 19 Oct 2026 14:01:24 +0530 Subject: [PATCH] GH-54570: [C++][Compute] Fix segfault casting null list with empty child to fixed_size_list When casting a ListArray or LargeListArray to FixedSizeListArray, if an array contains only null lists with an empty child array, cast_values->length is 0. Previously, BuildTakeIndices populated index 0 for null slots, and Take was called with TakeOptions::NoBoundsCheck(), dereferencing an unallocated child buffer and causing a segmentation fault. This commit resolves the issue by: 1. Handling empty child values (cast_values->length == 0) directly using MakeArrayOfNull rather than attempting Take. 2. Emitting null indices (builder.UnsafeAppendNull()) for null list slots in BuildTakeIndices instead of index 0. 3. Adding regression tests for casting null lists with empty children and unallocated buffers to FixedSizeList. Fixes: #54570 Signed-off-by: SHIVANSH-ux-ys --- .../compute/kernels/scalar_cast_nested.cc | 35 ++++++---- .../arrow/compute/kernels/scalar_cast_test.cc | 66 +++++++++++++++++++ 2 files changed, 87 insertions(+), 14 deletions(-) diff --git a/cpp/src/arrow/compute/kernels/scalar_cast_nested.cc b/cpp/src/arrow/compute/kernels/scalar_cast_nested.cc index 392fd9fbb705..653c33bcb10e 100644 --- a/cpp/src/arrow/compute/kernels/scalar_cast_nested.cc +++ b/cpp/src/arrow/compute/kernels/scalar_cast_nested.cc @@ -236,9 +236,9 @@ struct CastVarToFixedList { RETURN_NOT_OK(builder.Reserve(in_array.length * list_size)); for (int64_t offset_i = 0; offset_i < in_array.length; ++offset_i) { if (in_array.IsNull(offset_i)) { - // If element is null, just fill in the null slots with first value. + // If element is null, fill in the null slots with null values. for (int64_t j = 0; j < list_size; ++j) { - builder.UnsafeAppend(0); + builder.UnsafeAppendNull(); } } else { int64_t value_i = offsets[offset_i]; @@ -275,18 +275,25 @@ struct CastVarToFixedList { DCHECK(cast_values_datum.is_array()); std::shared_ptr cast_values = cast_values_datum.array(); - const auto* offsets = in_array.GetValues(1); - if (in_array.GetNullCount() > 0 && !nulls_have_correct_length) { - // We need to fill in the null slots, so we'll use Take on the values. - ARROW_ASSIGN_OR_RAISE(auto indices, BuildTakeIndices(in_array, list_size, ctx)); - ARROW_ASSIGN_OR_RAISE(auto take_result, - Take(cast_values, Datum(indices), - TakeOptions::NoBoundsCheck(), ctx->exec_context())); - DCHECK(take_result.is_array()); - cast_values = take_result.array(); - } else if (offsets[0] != 0) { - // No nulls, but we need to slice the values - cast_values = cast_values->Slice(offsets[0], in_array.length * list_size); + if (cast_values->length == 0) { + ARROW_ASSIGN_OR_RAISE( + auto null_values, + MakeArrayOfNull(child_type, in_array.length * list_size, ctx->memory_pool())); + cast_values = null_values->data(); + } else { + const auto* offsets = in_array.GetValues(1); + if (in_array.GetNullCount() > 0 && !nulls_have_correct_length) { + // We need to fill in the null slots, so we'll use Take on the values. + ARROW_ASSIGN_OR_RAISE(auto indices, BuildTakeIndices(in_array, list_size, ctx)); + ARROW_ASSIGN_OR_RAISE(auto take_result, + Take(cast_values, Datum(indices), + TakeOptions::NoBoundsCheck(), ctx->exec_context())); + DCHECK(take_result.is_array()); + cast_values = take_result.array(); + } else if (offsets[0] != 0 || cast_values->length > in_array.length * list_size) { + // No nulls or nulls have correct length, but we need to slice the values + cast_values = cast_values->Slice(offsets[0], in_array.length * list_size); + } } out_array->child_data.emplace_back(std::move(cast_values)); diff --git a/cpp/src/arrow/compute/kernels/scalar_cast_test.cc b/cpp/src/arrow/compute/kernels/scalar_cast_test.cc index 864ec4afce0c..a232511c2c79 100644 --- a/cpp/src/arrow/compute/kernels/scalar_cast_test.cc +++ b/cpp/src/arrow/compute/kernels/scalar_cast_test.cc @@ -3787,6 +3787,72 @@ TEST(Cast, ListToFSL) { // Invalid fixed_size_list cast if inconsistent size ASSERT_RAISES(Invalid, Cast(ArrayFromJSON(list(int32()), "[[0, 1, 2], null, [3, 4]]"), CastOptions::Safe(fixed_size_list(int32(), 3)))); + + // GH-54570: Null list array with empty child array cast to FixedSizeList + { + auto child = ArrayFromJSON(int64(), "[]"); + ASSERT_OK_AND_ASSIGN( + auto from, + ListArray::FromArrays(*ArrayFromJSON(int32(), "[0, 0]"), *child, + *ArrayFromJSON(boolean(), "[false]"))); + auto to = ArrayFromJSON(fixed_size_list(int64(), 3), "[null]"); + CheckCast(from, to); + } + + // GH-54570: LargeList with empty child cast to FixedSizeList + { + auto child = ArrayFromJSON(int64(), "[]"); + ASSERT_OK_AND_ASSIGN( + auto from, + LargeListArray::FromArrays(*ArrayFromJSON(int64(), "[0, 0]"), *child, + *ArrayFromJSON(boolean(), "[false]"))); + auto to = ArrayFromJSON(fixed_size_list(int64(), 3), "[null]"); + CheckCast(from, to); + } + + // GH-54570: Multiple null lists with empty child + { + auto child = ArrayFromJSON(int64(), "[]"); + ASSERT_OK_AND_ASSIGN( + auto from, + ListArray::FromArrays(*ArrayFromJSON(int32(), "[0, 0, 0]"), *child, + *ArrayFromJSON(boolean(), "[false, false]"))); + auto to = ArrayFromJSON(fixed_size_list(int64(), 2), "[null, null]"); + CheckCast(from, to); + } + + // GH-54570: Null list with unallocated/null child data buffers + { + auto child_data = ArrayData::Make(int64(), 0, {nullptr, nullptr}); + auto list_bitmap = ArrayFromJSON(boolean(), "[false]")->data()->buffers[1]; + auto list_offsets = ArrayFromJSON(int32(), "[0, 0]")->data()->buffers[1]; + auto list_data = ArrayData::Make(list(int64()), 1, {list_bitmap, list_offsets}, + {child_data}, 1); + auto from = MakeArray(list_data); + auto to = ArrayFromJSON(fixed_size_list(int64(), 3), "[null]"); + CheckCast(from, to); + } + + // GH-54570: Null slots with length 0 alongside valid slots + { + auto child = ArrayFromJSON(int32(), "[10, 20]"); + ASSERT_OK_AND_ASSIGN( + auto from, + ListArray::FromArrays(*ArrayFromJSON(int32(), "[0, 0, 2]"), *child, + *ArrayFromJSON(boolean(), "[false, true]"))); + auto to = ArrayFromJSON(fixed_size_list(int32(), 2), "[null, [10, 20]]"); + CheckCast(from, to); + } + + { + auto child = ArrayFromJSON(int32(), "[10, 20]"); + ASSERT_OK_AND_ASSIGN( + auto from, + ListArray::FromArrays(*ArrayFromJSON(int32(), "[0, 2, 2]"), *child, + *ArrayFromJSON(boolean(), "[true, false]"))); + auto to = ArrayFromJSON(fixed_size_list(int32(), 2), "[[10, 20], null]"); + CheckCast(from, to); + } } TEST(Cast, CastMap) {