This is an automated email from the ASF dual-hosted git repository.
zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git
The following commit(s) were added to refs/heads/main by this push:
new 5b999225 fix(arrow/array): widen dictionary index bounds checks (#1129)
5b999225 is described below
commit 5b999225fa9405ee412c22f674320410383386e7
Author: Minh Vu <[email protected]>
AuthorDate: Tue Aug 11 17:24:01 2026 +0200
fix(arrow/array): widen dictionary index bounds checks (#1129)
### Rationale for this change
Signed dictionary index validation converts the dictionary length to the
index type before comparing it with the largest index. A dictionary with
128 entries therefore turns the upper bound into -128 for int8 and
rejects valid indices.
### What changes are included in this PR?
Compare signed index values after widening them to uint64. Add coverage
for a valid int8 index at the dictionary size boundary.
### Are these changes tested?
- `go test ./arrow/array`
### Are there any user-facing changes?
Valid signed dictionary indices at the index type boundary are now
accepted. Negative and out-of-bounds indices continue to return errors.
---
arrow/array/dictionary.go | 8 ++---
arrow/array/dictionary_internal_test.go | 55 +++++++++++++++++++++++++++++++++
arrow/array/dictionary_test.go | 25 +++++++++++++++
3 files changed, 84 insertions(+), 4 deletions(-)
diff --git a/arrow/array/dictionary.go b/arrow/array/dictionary.go
index 38a43f77..435460e9 100644
--- a/arrow/array/dictionary.go
+++ b/arrow/array/dictionary.go
@@ -110,7 +110,7 @@ func checkIndexBounds(indices *Data, upperlimit uint64)
error {
case arrow.INT8:
data :=
arrow.Int8Traits.CastFromBytes(indices.buffers[1].Bytes())
min, max := utils.GetMinMaxInt8(data[start:end])
- if min < 0 || max >= int8(upperlimit) {
+ if min < 0 || uint64(max) >= upperlimit {
return fmt.Errorf("contains out of bounds index: min:
%d, max: %d", min, max)
}
case arrow.UINT8:
@@ -122,7 +122,7 @@ func checkIndexBounds(indices *Data, upperlimit uint64)
error {
case arrow.INT16:
data :=
arrow.Int16Traits.CastFromBytes(indices.buffers[1].Bytes())
min, max := utils.GetMinMaxInt16(data[start:end])
- if min < 0 || max >= int16(upperlimit) {
+ if min < 0 || uint64(max) >= upperlimit {
return fmt.Errorf("contains out of bounds index: min:
%d, max: %d", min, max)
}
case arrow.UINT16:
@@ -134,7 +134,7 @@ func checkIndexBounds(indices *Data, upperlimit uint64)
error {
case arrow.INT32:
data :=
arrow.Int32Traits.CastFromBytes(indices.buffers[1].Bytes())
min, max := utils.GetMinMaxInt32(data[start:end])
- if min < 0 || max >= int32(upperlimit) {
+ if min < 0 || uint64(max) >= upperlimit {
return fmt.Errorf("contains out of bounds index: min:
%d, max: %d", min, max)
}
case arrow.UINT32:
@@ -146,7 +146,7 @@ func checkIndexBounds(indices *Data, upperlimit uint64)
error {
case arrow.INT64:
data :=
arrow.Int64Traits.CastFromBytes(indices.buffers[1].Bytes())
min, max := utils.GetMinMaxInt64(data[start:end])
- if min < 0 || max >= int64(upperlimit) {
+ if min < 0 || uint64(max) >= upperlimit {
return fmt.Errorf("contains out of bounds index: min:
%d, max: %d", min, max)
}
case arrow.UINT64:
diff --git a/arrow/array/dictionary_internal_test.go
b/arrow/array/dictionary_internal_test.go
new file mode 100644
index 00000000..5c51a18e
--- /dev/null
+++ b/arrow/array/dictionary_internal_test.go
@@ -0,0 +1,55 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package array
+
+import (
+ "math"
+ "testing"
+
+ "github.com/apache/arrow-go/v18/arrow"
+ "github.com/apache/arrow-go/v18/arrow/memory"
+ "github.com/stretchr/testify/require"
+)
+
+func TestCheckIndexBoundsAllowsSignedIndexAtTypeLimit(t *testing.T) {
+ tests := []struct {
+ name string
+ indexType arrow.DataType
+ indexBytes []byte
+ upperLimit uint64
+ }{
+ {"int8", arrow.PrimitiveTypes.Int8,
+ arrow.Int8Traits.CastToBytes([]int8{math.MaxInt8}),
uint64(math.MaxInt8) + 1},
+ {"int16", arrow.PrimitiveTypes.Int16,
+ arrow.Int16Traits.CastToBytes([]int16{math.MaxInt16}),
uint64(math.MaxInt16) + 1},
+ {"int32", arrow.PrimitiveTypes.Int32,
+ arrow.Int32Traits.CastToBytes([]int32{math.MaxInt32}),
uint64(math.MaxInt32) + 1},
+ {"int64", arrow.PrimitiveTypes.Int64,
+ arrow.Int64Traits.CastToBytes([]int64{math.MaxInt64}),
uint64(math.MaxInt64) + 1},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ values := memory.NewBufferBytes(tt.indexBytes)
+ indices := NewData(tt.indexType, 1,
[]*memory.Buffer{nil, values}, nil, 0, 0)
+ values.Release()
+ defer indices.Release()
+
+ require.NoError(t, checkIndexBounds(indices,
tt.upperLimit))
+ })
+ }
+}
diff --git a/arrow/array/dictionary_test.go b/arrow/array/dictionary_test.go
index 24aab674..90f3c0bd 100644
--- a/arrow/array/dictionary_test.go
+++ b/arrow/array/dictionary_test.go
@@ -1207,6 +1207,31 @@ func TestDictionaryFromArrays(t *testing.T) {
}
}
+func TestValidatedDictionaryAllowsSignedIndexAtTypeLimit(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ indicesBuilder := array.NewInt8Builder(mem)
+ indicesBuilder.Append(127)
+ indices := indicesBuilder.NewArray()
+ indicesBuilder.Release()
+ defer indices.Release()
+
+ dictBuilder := array.NewStringBuilder(mem)
+ for i := 0; i < 128; i++ {
+ dictBuilder.AppendString(fmt.Sprintf("value-%d", i))
+ }
+ dict := dictBuilder.NewArray()
+ dictBuilder.Release()
+ defer dict.Release()
+
+ dictType := &arrow.DictionaryType{IndexType: arrow.PrimitiveTypes.Int8,
ValueType: arrow.BinaryTypes.String}
+ result, err := array.NewValidatedDictionaryArray(dictType, indices,
dict)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ defer result.Release()
+}
+
func TestListOfDictionary(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
defer mem.AssertSize(t, 0)