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 03d7ddfd fix(arrow/scalar): validate FromScalar destinations (#1106)
03d7ddfd is described below
commit 03d7ddfd9c893258990e2a9dc14c96f83830015b
Author: Minh Vu <[email protected]>
AuthorDate: Mon Aug 10 18:45:46 2026 +0200
fix(arrow/scalar): validate FromScalar destinations (#1106)
### Rationale for this change
FromScalar accepts any pointer and then assumes it points to a struct.
Nil pointers and pointers to non-struct values can panic instead of
returning an error.
### What changes are included in this PR?
Validate the pointer, nil state, and target kind before using
reflection. Preserve the existing custom TypeFromScalar path and cover
invalid targets without allowing a panic.
### Are these changes tested?
- `go test ./arrow/scalar`
### Are there any user-facing changes?
No API changes. This corrects the reported behavior while preserving the
existing ownership and compatibility contracts.
---
arrow/scalar/parse.go | 10 ++++++--
arrow/scalar/scalar_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 70 insertions(+), 2 deletions(-)
diff --git a/arrow/scalar/parse.go b/arrow/scalar/parse.go
index 6d1e6a33..69108e8f 100644
--- a/arrow/scalar/parse.go
+++ b/arrow/scalar/parse.go
@@ -55,15 +55,21 @@ func FromScalar(sc *Struct, val interface{}) error {
return nil
}
+ v := reflect.ValueOf(val)
+ if v.Kind() == reflect.Ptr && v.IsNil() {
+ return errors.New("fromscalar must be given a non-nil pointer
to an object to populate")
+ }
if v, ok := val.(TypeFromScalar); ok {
return v.FromStructScalar(sc)
}
- v := reflect.ValueOf(val)
if v.Kind() != reflect.Ptr {
return errors.New("fromscalar must be given a pointer to an
object to populate")
}
- value := reflect.Indirect(v)
+ value := v.Elem()
+ if value.Kind() != reflect.Struct {
+ return errors.New("fromscalar must be given a pointer to a
struct to populate")
+ }
for i := 0; i < value.Type().NumField(); i++ {
fld := value.Type().Field(i)
diff --git a/arrow/scalar/scalar_test.go b/arrow/scalar/scalar_test.go
index 5634864f..3cec8f97 100644
--- a/arrow/scalar/scalar_test.go
+++ b/arrow/scalar/scalar_test.go
@@ -1291,6 +1291,29 @@ type OptionValTest struct {
func (OptionValTest) TypeName() string { return "OptionValTest" }
+type typedNilFromScalar struct{}
+
+func (s *typedNilFromScalar) FromStructScalar(*scalar.Struct) error {
+ _ = *s
+ return nil
+}
+
+type customFromScalarTarget int
+
+func (v *customFromScalarTarget) FromStructScalar(*scalar.Struct) error {
+ *v = 42
+ return nil
+}
+
+type valueFromScalarTarget struct {
+ output *int
+}
+
+func (v valueFromScalarTarget) FromStructScalar(*scalar.Struct) error {
+ *v.output = 42
+ return nil
+}
+
func TestToScalar(t *testing.T) {
ot := &OptionValTest{ToType: arrow.BinaryTypes.String, Allow: true}
sc, err := scalar.ToScalar(ot, memory.DefaultAllocator)
@@ -1339,6 +1362,45 @@ func TestToScalar(t *testing.T) {
assert.Equal(t, expected, sc.String())
}
+func TestFromScalarRejectsInvalidTargets(t *testing.T) {
+ input, err := scalar.ToScalar(&OptionValTest{ToType:
arrow.BinaryTypes.String, Allow: true}, memory.DefaultAllocator)
+ require.NoError(t, err)
+ structScalar := input.(*scalar.Struct)
+ defer structScalar.Release()
+
+ assertErrorWithoutPanic := func(target interface{}) {
+ var got error
+ assert.NotPanics(t, func() {
+ got = scalar.FromScalar(structScalar, target)
+ })
+ assert.Error(t, got)
+ }
+
+ assertErrorWithoutPanic(nil)
+ var typedNil *OptionValTest
+ assertErrorWithoutPanic(typedNil)
+ var typedNilCustom *typedNilFromScalar
+ assertErrorWithoutPanic(typedNilCustom)
+ value := 0
+ assertErrorWithoutPanic(&value)
+ values := []int{}
+ assertErrorWithoutPanic(&values)
+ assertErrorWithoutPanic(OptionValTest{})
+
+ var custom customFromScalarTarget
+ require.NoError(t, scalar.FromScalar(structScalar, &custom))
+ assert.Equal(t, customFromScalarTarget(42), custom)
+
+ valueOutput := 0
+ require.NoError(t, scalar.FromScalar(structScalar,
valueFromScalarTarget{output: &valueOutput}))
+ assert.Equal(t, 42, valueOutput)
+
+ var output OptionValTest
+ require.NoError(t, scalar.FromScalar(structScalar, &output))
+ assert.Equal(t, arrow.BinaryTypes.String, output.ToType)
+ assert.True(t, output.Allow)
+}
+
func TestFromScalarMetadataDoesNotPrependEmptyEntries(t *testing.T) {
meta := arrow.NewMetadata(
[]string{"option", "captain", "souper"},