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 5d4c79d5 fix(arreflect): validate primitive conversion assignment
overflow checks (#903)
5d4c79d5 is described below
commit 5d4c79d59c7d94402eee495185005794a8e59b09
Author: Minh Vu <[email protected]>
AuthorDate: Fri Jul 10 18:28:14 2026 +0200
fix(arreflect): validate primitive conversion assignment overflow checks
(#903)
## Summary
Prevent silent corruption in `arreflect` primitive conversions by
validating numeric assignment bounds before setting through reflection.
## Why this fix
`setPrimitiveValue` used `SetInt/SetUint/SetFloat` directly for Arrow
numeric types. That allowed values outside the target Go type range to
wrap/truncate, so bad data could pass through without errors until
consumed by fast paths relying on value integrity.
## Changes
- Added explicit overflow checks with:
- `OverflowInt`
- `OverflowUint`
- `OverflowFloat`
- Return `ErrTypeMismatch`-wrapped errors on overflow.
- Preserve existing valid conversion behavior.
## Files
- `arrow/array/arreflect/reflect_arrow_to_go.go`
- `arrow/array/arreflect/reflect_arrow_to_go_test.go`
## Validation
- Added tests for overflow cases:
- `int64` -> `int8`
- `uint64` -> `uint8`
- `float64` -> `float32`
### Bug details
- Severity: 82/100
- Ask product?: 0/100
---
arrow/array/arreflect/reflect_arrow_to_go.go | 57 +++++++++++++++++++----
arrow/array/arreflect/reflect_arrow_to_go_test.go | 40 ++++++++++++++++
2 files changed, 87 insertions(+), 10 deletions(-)
diff --git a/arrow/array/arreflect/reflect_arrow_to_go.go
b/arrow/array/arreflect/reflect_arrow_to_go.go
index c5b1288b..086dbe6c 100644
--- a/arrow/array/arreflect/reflect_arrow_to_go.go
+++ b/arrow/array/arreflect/reflect_arrow_to_go.go
@@ -161,52 +161,89 @@ func setPrimitiveValue(v reflect.Value, arr arrow.Array,
i int) error {
if !isIntKind(v.Kind()) {
return fmt.Errorf("cannot set int8 into %s: %w",
v.Type(), ErrTypeMismatch)
}
- v.SetInt(int64(arr.(*array.Int8).Value(i)))
+ val := int64(arr.(*array.Int8).Value(i))
+ if v.OverflowInt(val) {
+ return fmt.Errorf("cannot set int8 value %d into %s:
%w", val, v.Type(), ErrTypeMismatch)
+ }
+ v.SetInt(val)
case arrow.INT16:
if !isIntKind(v.Kind()) {
return fmt.Errorf("cannot set int16 into %s: %w",
v.Type(), ErrTypeMismatch)
}
- v.SetInt(int64(arr.(*array.Int16).Value(i)))
+ val := int64(arr.(*array.Int16).Value(i))
+ if v.OverflowInt(val) {
+ return fmt.Errorf("cannot set int16 value %d into %s:
%w", val, v.Type(), ErrTypeMismatch)
+ }
+ v.SetInt(val)
case arrow.INT32:
if !isIntKind(v.Kind()) {
return fmt.Errorf("cannot set int32 into %s: %w",
v.Type(), ErrTypeMismatch)
}
- v.SetInt(int64(arr.(*array.Int32).Value(i)))
+ val := int64(arr.(*array.Int32).Value(i))
+ if v.OverflowInt(val) {
+ return fmt.Errorf("cannot set int32 value %d into %s:
%w", val, v.Type(), ErrTypeMismatch)
+ }
+ v.SetInt(val)
case arrow.INT64:
if !isIntKind(v.Kind()) {
return fmt.Errorf("cannot set int64 into %s: %w",
v.Type(), ErrTypeMismatch)
}
- v.SetInt(arr.(*array.Int64).Value(i))
+ val := arr.(*array.Int64).Value(i)
+ if v.OverflowInt(val) {
+ return fmt.Errorf("cannot set int64 value %d into %s:
%w", val, v.Type(), ErrTypeMismatch)
+ }
+ v.SetInt(val)
case arrow.UINT8:
if !isUintKind(v.Kind()) {
return fmt.Errorf("cannot set uint8 into %s: %w",
v.Type(), ErrTypeMismatch)
}
- v.SetUint(uint64(arr.(*array.Uint8).Value(i)))
+ val := uint64(arr.(*array.Uint8).Value(i))
+ if v.OverflowUint(val) {
+ return fmt.Errorf("cannot set uint8 value %d into %s:
%w", val, v.Type(), ErrTypeMismatch)
+ }
+ v.SetUint(val)
case arrow.UINT16:
if !isUintKind(v.Kind()) {
return fmt.Errorf("cannot set uint16 into %s: %w",
v.Type(), ErrTypeMismatch)
}
- v.SetUint(uint64(arr.(*array.Uint16).Value(i)))
+ val := uint64(arr.(*array.Uint16).Value(i))
+ if v.OverflowUint(val) {
+ return fmt.Errorf("cannot set uint16 value %d into %s:
%w", val, v.Type(), ErrTypeMismatch)
+ }
+ v.SetUint(val)
case arrow.UINT32:
if !isUintKind(v.Kind()) {
return fmt.Errorf("cannot set uint32 into %s: %w",
v.Type(), ErrTypeMismatch)
}
- v.SetUint(uint64(arr.(*array.Uint32).Value(i)))
+ val := uint64(arr.(*array.Uint32).Value(i))
+ if v.OverflowUint(val) {
+ return fmt.Errorf("cannot set uint32 value %d into %s:
%w", val, v.Type(), ErrTypeMismatch)
+ }
+ v.SetUint(val)
case arrow.UINT64:
if !isUintKind(v.Kind()) {
return fmt.Errorf("cannot set uint64 into %s: %w",
v.Type(), ErrTypeMismatch)
}
- v.SetUint(arr.(*array.Uint64).Value(i))
+ val := arr.(*array.Uint64).Value(i)
+ if v.OverflowUint(val) {
+ return fmt.Errorf("cannot set uint64 value %d into %s:
%w", val, v.Type(), ErrTypeMismatch)
+ }
+ v.SetUint(val)
case arrow.FLOAT32:
if !isFloatKind(v.Kind()) {
return fmt.Errorf("cannot set float32 into %s: %w",
v.Type(), ErrTypeMismatch)
}
- v.SetFloat(float64(arr.(*array.Float32).Value(i)))
+ val := float64(arr.(*array.Float32).Value(i))
+ v.SetFloat(val)
case arrow.FLOAT64:
if !isFloatKind(v.Kind()) {
return fmt.Errorf("cannot set float64 into %s: %w",
v.Type(), ErrTypeMismatch)
}
- v.SetFloat(arr.(*array.Float64).Value(i))
+ val := arr.(*array.Float64).Value(i)
+ if v.OverflowFloat(val) {
+ return fmt.Errorf("cannot set float64 value %f into %s:
%w", val, v.Type(), ErrTypeMismatch)
+ }
+ v.SetFloat(val)
default:
return fmt.Errorf("unsupported primitive type %v: %w",
arr.DataType(), ErrUnsupportedType)
}
diff --git a/arrow/array/arreflect/reflect_arrow_to_go_test.go
b/arrow/array/arreflect/reflect_arrow_to_go_test.go
index 1a17b33a..eceb5abf 100644
--- a/arrow/array/arreflect/reflect_arrow_to_go_test.go
+++ b/arrow/array/arreflect/reflect_arrow_to_go_test.go
@@ -17,6 +17,7 @@
package arreflect
import (
+ "math"
"reflect"
"testing"
"time"
@@ -217,6 +218,19 @@ func TestSetPrimitiveValue(t *testing.T) {
assert.ErrorIs(t,
setPrimitiveValue(reflect.ValueOf(&bad).Elem(), arr, 0), ErrTypeMismatch)
})
+ t.Run("int64 overflow into int8 returns error", func(t *testing.T) {
+ b := array.NewInt64Builder(mem)
+ defer b.Release()
+ b.Append(int64(300))
+ arr := b.NewArray().(*array.Int64)
+ defer arr.Release()
+
+ got := int8(7)
+ err := setPrimitiveValue(reflect.ValueOf(&got).Elem(), arr, 0)
+ assert.ErrorIs(t, err, ErrTypeMismatch)
+ assert.Equal(t, int8(7), got)
+ })
+
t.Run("uint16", func(t *testing.T) {
b := array.NewUint16Builder(mem)
defer b.Release()
@@ -232,6 +246,19 @@ func TestSetPrimitiveValue(t *testing.T) {
assert.ErrorIs(t,
setPrimitiveValue(reflect.ValueOf(&bad).Elem(), arr, 0), ErrTypeMismatch)
})
+ t.Run("uint64 overflow into uint8 returns error", func(t *testing.T) {
+ b := array.NewUint64Builder(mem)
+ defer b.Release()
+ b.Append(300)
+ arr := b.NewArray().(*array.Uint64)
+ defer arr.Release()
+
+ got := uint8(7)
+ err := setPrimitiveValue(reflect.ValueOf(&got).Elem(), arr, 0)
+ assert.ErrorIs(t, err, ErrTypeMismatch)
+ assert.Equal(t, uint8(7), got)
+ })
+
t.Run("uint32", func(t *testing.T) {
b := array.NewUint32Builder(mem)
defer b.Release()
@@ -288,6 +315,19 @@ func TestSetPrimitiveValue(t *testing.T) {
assert.ErrorIs(t,
setPrimitiveValue(reflect.ValueOf(&bad).Elem(), arr, 0), ErrTypeMismatch)
})
+ t.Run("float64 overflow into float32 returns error", func(t *testing.T)
{
+ b := array.NewFloat64Builder(mem)
+ defer b.Release()
+ b.Append(math.MaxFloat64)
+ arr := b.NewArray().(*array.Float64)
+ defer arr.Release()
+
+ got := float32(1)
+ err := setPrimitiveValue(reflect.ValueOf(&got).Elem(), arr, 0)
+ assert.ErrorIs(t, err, ErrTypeMismatch)
+ assert.Equal(t, float32(1), got)
+ })
+
t.Run("float64 mismatch", func(t *testing.T) {
b := array.NewFloat64Builder(mem)
defer b.Release()