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"},

Reply via email to