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 621a4ed1 fix(array): validate map builder entry lengths (#957)
621a4ed1 is described below

commit 621a4ed1efb013b48dca30eecfaf4490178f9022
Author: Minh Vu <[email protected]>
AuthorDate: Sat Jul 18 19:09:59 2026 +0200

    fix(array): validate map builder entry lengths (#957)
    
    MapBuilder sized its child struct from the key builder without checking the 
item builder, so unequal key/item counts could create malformed map data or 
defer the failure to later array access. Validate before array construction: 
reject unequal key and item builder lengths, reject a child struct length 
exceeding the entry count, require the offset count to match the map count (or 
map count plus one), and validate any explicitly supplied final offset against 
the available entries. Invar [...]
---
 arrow/array/map.go      | 27 +++++++++++++++++--
 arrow/array/map_test.go | 71 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 96 insertions(+), 2 deletions(-)

diff --git a/arrow/array/map.go b/arrow/array/map.go
index 71d4d138..1d13ea88 100644
--- a/arrow/array/map.go
+++ b/arrow/array/map.go
@@ -274,9 +274,19 @@ func (b *MapBuilder) init(capacity int)                  { 
b.listBuilder.init(ca
 func (b *MapBuilder) resize(newBits int, init func(int)) { 
b.listBuilder.resize(newBits, init) }
 
 func (b *MapBuilder) adjustStructBuilderLen() {
+       keyLen, itemLen := b.keyBuilder.Len(), b.itemBuilder.Len()
+       if keyLen != itemLen {
+               panic(fmt.Errorf("%w: arrow/array: map key and item builders 
must have equal lengths (keys=%d, items=%d)",
+                       arrow.ErrInvalid, keyLen, itemLen))
+       }
+
        sb := b.listBuilder.ValueBuilder().(*StructBuilder)
-       if sb.Len() < b.keyBuilder.Len() {
-               valids := make([]bool, b.keyBuilder.Len()-sb.Len())
+       if sb.Len() > keyLen {
+               panic(fmt.Errorf("%w: arrow/array: map struct builder length 
exceeds key and item length (struct=%d, entries=%d)",
+                       arrow.ErrInvalid, sb.Len(), keyLen))
+       }
+       if sb.Len() < keyLen {
+               valids := make([]bool, keyLen-sb.Len())
                for i := range valids {
                        valids[i] = true
                }
@@ -284,6 +294,18 @@ func (b *MapBuilder) adjustStructBuilderLen() {
        }
 }
 
+func (b *MapBuilder) validateOffsets(entryLen int) {
+       offsets := b.listBuilder.offsets.(*Int32Builder)
+       if offsets.Len() != b.Len() && offsets.Len() != b.Len()+1 {
+               panic(fmt.Errorf("%w: arrow/array: map offset count must equal 
map length or map length plus one (offsets=%d, maps=%d)",
+                       arrow.ErrInvalid, offsets.Len(), b.Len()))
+       }
+       if offsets.Len() == b.Len()+1 && int(offsets.Value(b.Len())) != 
entryLen {
+               panic(fmt.Errorf("%w: arrow/array: map final offset must match 
key and item length (offset=%d, entries=%d)",
+                       arrow.ErrInvalid, offsets.Value(b.Len()), entryLen))
+       }
+}
+
 // NewArray creates a new Map array from the memory buffers used by the 
builder, and
 // resets the builder so it can be used again to build a new Map array.
 func (b *MapBuilder) NewArray() arrow.Array {
@@ -305,6 +327,7 @@ func (b *MapBuilder) NewMapArray() (a *Map) {
 
 func (b *MapBuilder) newData() (data *Data) {
        b.adjustStructBuilderLen()
+       b.validateOffsets(b.keyBuilder.Len())
        values := b.listBuilder.NewListArray()
        defer values.Release()
 
diff --git a/arrow/array/map_test.go b/arrow/array/map_test.go
index 3727e565..e45ac133 100644
--- a/arrow/array/map_test.go
+++ b/arrow/array/map_test.go
@@ -174,6 +174,77 @@ func TestMapArrayBuildIntToInt(t *testing.T) {
        assert.Equal(t, "[{[0 1 2 3 4 5] [1 1 2 3 5 8]} (null) {[0 1 2 3 4 5] 
[(null) (null) 0 1 (null) 2]} {[] []}]", arr.String())
 }
 
+func TestMapBuilderRejectsInvalidEntryLengths(t *testing.T) {
+       tests := []struct {
+               name      string
+               build     func(*array.MapBuilder)
+               panicText string
+       }{
+               {
+                       name: "more keys than items",
+                       build: func(b *array.MapBuilder) {
+                               b.Append(true)
+                               b.KeyBuilder().(*array.Int32Builder).Append(1)
+                       },
+                       panicText: "invalid: arrow/array: map key and item 
builders must have equal lengths (keys=1, items=0)",
+               },
+               {
+                       name: "more items than keys",
+                       build: func(b *array.MapBuilder) {
+                               b.Append(true)
+                               b.ItemBuilder().(*array.Int32Builder).Append(1)
+                       },
+                       panicText: "invalid: arrow/array: map key and item 
builders must have equal lengths (keys=0, items=1)",
+               },
+               {
+                       name: "struct longer than entries",
+                       build: func(b *array.MapBuilder) {
+                               b.Append(true)
+                               
b.ValueBuilder().(*array.StructBuilder).Append(true)
+                       },
+                       panicText: "invalid: arrow/array: map struct builder 
length exceeds key and item length (struct=1, entries=0)",
+               },
+               {
+                       name: "too few offsets",
+                       build: func(b *array.MapBuilder) {
+                               b.AppendValues(nil, []bool{true})
+                       },
+                       panicText: "invalid: arrow/array: map offset count must 
equal map length or map length plus one (offsets=0, maps=1)",
+               },
+               {
+                       name: "too many offsets",
+                       build: func(b *array.MapBuilder) {
+                               b.AppendValues([]int32{0, 0, 0}, []bool{true})
+                       },
+                       panicText: "invalid: arrow/array: map offset count must 
equal map length or map length plus one (offsets=3, maps=1)",
+               },
+               {
+                       name: "final offset exceeds entries",
+                       build: func(b *array.MapBuilder) {
+                               b.AppendValues([]int32{0, 2}, []bool{true})
+                               b.KeyBuilder().(*array.Int32Builder).Append(1)
+                               b.ItemBuilder().(*array.Int32Builder).Append(2)
+                       },
+                       panicText: "invalid: arrow/array: map final offset must 
match key and item length (offset=2, entries=1)",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       mem := 
memory.NewCheckedAllocator(memory.NewGoAllocator())
+                       defer mem.AssertSize(t, 0)
+
+                       b := array.NewMapBuilder(mem, 
arrow.PrimitiveTypes.Int32, arrow.PrimitiveTypes.Int32, false)
+                       defer b.Release()
+                       tt.build(b)
+
+                       assert.PanicsWithError(t, tt.panicText, func() {
+                               b.NewMapArray()
+                       })
+               })
+       }
+}
+
 func TestMapStringRoundTrip(t *testing.T) {
        // 1. create array
        dt := arrow.MapOf(arrow.BinaryTypes.String, arrow.PrimitiveTypes.Int32)

Reply via email to