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)