From 8a275bc54b8c39f136228ce2acffff25c2116580 Mon Sep 17 00:00:00 2001 From: JonSnow1807 Date: Wed, 21 Oct 2026 11:24:34 -0500 Subject: [PATCH] GH-54908: [C++] Reject non-empty union arrays with missing type ids or offsets buffers Validate() accepted a non-empty union array whose type ids buffer is null, or a dense union whose offsets buffer is null, and ValidateFull() then dereferenced the null buffer and crashed. Report such arrays as invalid instead, like the existing "Non-empty array but offsets are null" check for list and binary types. Empty arrays may still omit these buffers. --- cpp/src/arrow/array/array_union_test.cc | 47 +++++++++++++++++++++++++ cpp/src/arrow/array/validate.cc | 9 +++++ 2 files changed, 56 insertions(+) diff --git a/cpp/src/arrow/array/array_union_test.cc b/cpp/src/arrow/array/array_union_test.cc index ec93329d0839..50ce8bb9d1e5 100644 --- a/cpp/src/arrow/array/array_union_test.cc +++ b/cpp/src/arrow/array/array_union_test.cc @@ -211,6 +211,53 @@ TEST(TestSparseUnionArray, Validate) { ASSERT_RAISES(Invalid, arr->ValidateFull()); } +TEST(TestSparseUnionArray, ValidateMissingTypeIds) { + auto type = sparse_union({field("a", int32())}); + auto children = ArrayDataVector{ArrayFromJSON(int32(), "[4, 5]")->data()}; + + auto data = ArrayData::Make(type, /*length=*/2, {nullptr, nullptr}, children); + ASSERT_RAISES_WITH_MESSAGE(Invalid, + "Invalid: Non-empty union array but type ids are null", + MakeArray(data)->Validate()); + ASSERT_RAISES_WITH_MESSAGE(Invalid, + "Invalid: Non-empty union array but type ids are null", + MakeArray(data)->ValidateFull()); + + // An empty array does not need a type ids buffer + data = ArrayData::Make(type, /*length=*/0, {nullptr, nullptr}, children); + ASSERT_OK(MakeArray(data)->ValidateFull()); +} + +TEST(TestDenseUnionArray, ValidateMissingBuffers) { + auto type = dense_union({field("a", int32())}); + auto children = ArrayDataVector{ArrayFromJSON(int32(), "[7]")->data()}; + auto type_ids = ArrayFromJSON(int8(), "[0]")->data()->buffers[1]; + auto offsets = ArrayFromJSON(int32(), "[0]")->data()->buffers[1]; + + auto data = ArrayData::Make(type, /*length=*/1, {nullptr, nullptr, offsets}, children); + ASSERT_RAISES_WITH_MESSAGE(Invalid, + "Invalid: Non-empty union array but type ids are null", + MakeArray(data)->Validate()); + ASSERT_RAISES_WITH_MESSAGE(Invalid, + "Invalid: Non-empty union array but type ids are null", + MakeArray(data)->ValidateFull()); + + data = ArrayData::Make(type, /*length=*/1, {nullptr, type_ids, nullptr}, children); + ASSERT_RAISES_WITH_MESSAGE(Invalid, + "Invalid: Non-empty dense union array but offsets are null", + MakeArray(data)->Validate()); + ASSERT_RAISES_WITH_MESSAGE(Invalid, + "Invalid: Non-empty dense union array but offsets are null", + MakeArray(data)->ValidateFull()); + + data = ArrayData::Make(type, /*length=*/1, {nullptr, type_ids, offsets}, children); + ASSERT_OK(MakeArray(data)->ValidateFull()); + + // An empty array does not need type ids or offsets buffers + data = ArrayData::Make(type, /*length=*/0, {nullptr, nullptr, nullptr}, children); + ASSERT_OK(MakeArray(data)->ValidateFull()); +} + TEST(TestSparseUnionArray, Comparison) { auto ints1 = ArrayFromJSON(int32(), "[1, 2, 3, 4, 5, 6]"); auto ints2 = ArrayFromJSON(int32(), "[1, 2, -3, 4, -5, 6]"); diff --git a/cpp/src/arrow/array/validate.cc b/cpp/src/arrow/array/validate.cc index 16bc9187af46..13e74d8c3c89 100644 --- a/cpp/src/arrow/array/validate.cc +++ b/cpp/src/arrow/array/validate.cc @@ -334,6 +334,15 @@ struct ValidateArrayImpl { } Status Visit(const UnionType& type) { + if (data.length > 0) { + if (!IsBufferValid(1)) { + return Status::Invalid("Non-empty union array but type ids are null"); + } + if (type.mode() == UnionMode::DENSE && !IsBufferValid(2)) { + return Status::Invalid("Non-empty dense union array but offsets are null"); + } + } + for (int i = 0; i < type.num_fields(); ++i) { const auto& field_data = *data.child_data[i];