Skip to content

Commit 234f670

Browse files
committed
fix: wrap vectorByteSize errors as typed MarshalError/UnmarshalError and return non-nil empty slice for dim==0 fast paths
- Wrap vectorByteSize overflow errors with marshalErrorf/unmarshalErrorf at all 8 call sites so callers that type-assert on MarshalError/UnmarshalError see consistent error types. - Add dim==0 guard in unmarshalVectorFloat32/Float64/Int32/Int64 to return a non-nil empty slice when data is non-nil empty, matching the generic path. - Add TestVectorByteSizeErrorType and TestUnmarshalVectorFastPathZeroDimNonNilSlice.
1 parent 9f7f5c5 commit 234f670

2 files changed

Lines changed: 192 additions & 9 deletions

File tree

marshal.go

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,9 @@ func unmarshalVector(info VectorType, data []byte, value interface{}) error {
950950
if len(data) > 0 {
951951
return unmarshalErrorf("unmarshal vector: %d bytes of data for 0-dimension vector", len(data))
952952
}
953+
if k == reflect.Array && rv.Len() != 0 {
954+
return unmarshalErrorf("unmarshal vector: array of size %d cannot store vector of 0 dimensions", rv.Len())
955+
}
953956
if k == reflect.Slice {
954957
rv.Set(reflect.MakeSlice(t, 0, 0))
955958
}
@@ -1011,9 +1014,12 @@ func unmarshalVector(info VectorType, data []byte, value interface{}) error {
10111014
var vectorBufPool = sync.Pool{}
10121015

10131016
func getVectorBuf(size int) []byte {
1014-
if size <= 0 {
1017+
if size < 0 {
10151018
return nil
10161019
}
1020+
if size == 0 {
1021+
return make([]byte, 0)
1022+
}
10171023
if v := vectorBufPool.Get(); v != nil {
10181024
if buf, ok := v.([]byte); ok {
10191025
if cap(buf) >= size {
@@ -1058,7 +1064,7 @@ func marshalVectorFloat32(dim int, vec []float32) ([]byte, error) {
10581064
}
10591065
size, err := vectorByteSize(dim, 4)
10601066
if err != nil {
1061-
return nil, err
1067+
return nil, marshalErrorf("%v", err)
10621068
}
10631069
buf := getVectorBuf(size)
10641070
off := 0
@@ -1083,7 +1089,7 @@ func marshalVectorFloat64(dim int, vec []float64) ([]byte, error) {
10831089
}
10841090
size, err := vectorByteSize(dim, 8)
10851091
if err != nil {
1086-
return nil, err
1092+
return nil, marshalErrorf("%v", err)
10871093
}
10881094
buf := getVectorBuf(size)
10891095
off := 0
@@ -1106,11 +1112,19 @@ func unmarshalVectorFloat32(dim int, data []byte, dst *[]float32) error {
11061112
}
11071113
expected, err := vectorByteSize(dim, 4)
11081114
if err != nil {
1109-
return err
1115+
return unmarshalErrorf("%v", err)
11101116
}
11111117
if len(data) != expected {
11121118
return unmarshalErrorf("unmarshal vector<float, %d>: expected %d bytes, got %d", dim, expected, len(data))
11131119
}
1120+
if dim == 0 {
1121+
if *dst == nil {
1122+
*dst = make([]float32, 0)
1123+
} else {
1124+
*dst = (*dst)[:0]
1125+
}
1126+
return nil
1127+
}
11141128
vec := *dst
11151129
if cap(vec) >= dim {
11161130
vec = vec[:dim]
@@ -1137,11 +1151,19 @@ func unmarshalVectorFloat64(dim int, data []byte, dst *[]float64) error {
11371151
}
11381152
expected, err := vectorByteSize(dim, 8)
11391153
if err != nil {
1140-
return err
1154+
return unmarshalErrorf("%v", err)
11411155
}
11421156
if len(data) != expected {
11431157
return unmarshalErrorf("unmarshal vector<double, %d>: expected %d bytes, got %d", dim, expected, len(data))
11441158
}
1159+
if dim == 0 {
1160+
if *dst == nil {
1161+
*dst = make([]float64, 0)
1162+
} else {
1163+
*dst = (*dst)[:0]
1164+
}
1165+
return nil
1166+
}
11451167
vec := *dst
11461168
if cap(vec) >= dim {
11471169
vec = vec[:dim]
@@ -1170,7 +1192,7 @@ func marshalVectorInt32(dim int, vec []int32) ([]byte, error) {
11701192
}
11711193
size, err := vectorByteSize(dim, 4)
11721194
if err != nil {
1173-
return nil, err
1195+
return nil, marshalErrorf("%v", err)
11741196
}
11751197
buf := getVectorBuf(size)
11761198
off := 0
@@ -1195,7 +1217,7 @@ func marshalVectorInt64(dim int, vec []int64) ([]byte, error) {
11951217
}
11961218
size, err := vectorByteSize(dim, 8)
11971219
if err != nil {
1198-
return nil, err
1220+
return nil, marshalErrorf("%v", err)
11991221
}
12001222
buf := getVectorBuf(size)
12011223
off := 0
@@ -1218,11 +1240,19 @@ func unmarshalVectorInt32(dim int, data []byte, dst *[]int32) error {
12181240
}
12191241
expected, err := vectorByteSize(dim, 4)
12201242
if err != nil {
1221-
return err
1243+
return unmarshalErrorf("%v", err)
12221244
}
12231245
if len(data) != expected {
12241246
return unmarshalErrorf("unmarshal vector<int, %d>: expected %d bytes, got %d", dim, expected, len(data))
12251247
}
1248+
if dim == 0 {
1249+
if *dst == nil {
1250+
*dst = make([]int32, 0)
1251+
} else {
1252+
*dst = (*dst)[:0]
1253+
}
1254+
return nil
1255+
}
12261256
vec := *dst
12271257
if cap(vec) >= dim {
12281258
vec = vec[:dim]
@@ -1249,11 +1279,19 @@ func unmarshalVectorInt64(dim int, data []byte, dst *[]int64) error {
12491279
}
12501280
expected, err := vectorByteSize(dim, 8)
12511281
if err != nil {
1252-
return err
1282+
return unmarshalErrorf("%v", err)
12531283
}
12541284
if len(data) != expected {
12551285
return unmarshalErrorf("unmarshal vector<bigint, %d>: expected %d bytes, got %d", dim, expected, len(data))
12561286
}
1287+
if dim == 0 {
1288+
if *dst == nil {
1289+
*dst = make([]int64, 0)
1290+
} else {
1291+
*dst = (*dst)[:0]
1292+
}
1293+
return nil
1294+
}
12571295
vec := *dst
12581296
if cap(vec) >= dim {
12591297
vec = vec[:dim]

marshal_vector_test.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package gocql
99

1010
import (
1111
"encoding/binary"
12+
"errors"
1213
"math"
1314
"reflect"
1415
"strconv"
@@ -759,6 +760,9 @@ func TestMarshalVector_EmptyVector(t *testing.T) {
759760
if err != nil {
760761
t.Fatalf("marshalVector: %v", err)
761762
}
763+
if data == nil {
764+
t.Error("expected non-nil empty data for non-nil empty vector, got nil (would encode as CQL NULL)")
765+
}
762766
if len(data) != 0 {
763767
t.Errorf("expected empty data, got %d bytes", len(data))
764768
}
@@ -779,6 +783,9 @@ func TestMarshalVector_EmptyVector(t *testing.T) {
779783
if err != nil {
780784
t.Fatalf("marshalVector: %v", err)
781785
}
786+
if data == nil {
787+
t.Error("expected non-nil empty data for non-nil empty vector, got nil (would encode as CQL NULL)")
788+
}
782789
if len(data) != 0 {
783790
t.Errorf("expected empty data, got %d bytes", len(data))
784791
}
@@ -799,6 +806,9 @@ func TestMarshalVector_EmptyVector(t *testing.T) {
799806
if err != nil {
800807
t.Fatalf("marshalVector: %v", err)
801808
}
809+
if data == nil {
810+
t.Error("expected non-nil empty data for non-nil empty vector, got nil (would encode as CQL NULL)")
811+
}
802812
if len(data) != 0 {
803813
t.Errorf("expected empty data, got %d bytes", len(data))
804814
}
@@ -819,6 +829,9 @@ func TestMarshalVector_EmptyVector(t *testing.T) {
819829
if err != nil {
820830
t.Fatalf("marshalVector: %v", err)
821831
}
832+
if data == nil {
833+
t.Error("expected non-nil empty data for non-nil empty vector, got nil (would encode as CQL NULL)")
834+
}
822835
if len(data) != 0 {
823836
t.Errorf("expected empty data, got %d bytes", len(data))
824837
}
@@ -1058,6 +1071,138 @@ func TestVectorBufPool_Concurrency(t *testing.T) {
10581071
wg.Wait()
10591072
}
10601073

1074+
// TestVectorByteSizeErrorType verifies that vectorByteSize overflow errors,
1075+
// when propagated through unmarshal fast paths, are returned as typed
1076+
// UnmarshalError rather than plain fmt.Errorf values. The marshal fast paths
1077+
// check len(vec) != dim before reaching vectorByteSize, so they cannot be
1078+
// tested with real slices large enough to trigger overflow.
1079+
func TestVectorByteSizeErrorType(t *testing.T) {
1080+
// Use a dimension that overflows on all platforms: math.MaxInt/4+1 * 4 > MaxInt.
1081+
overflowDim4 := math.MaxInt/4 + 1
1082+
overflowDim8 := math.MaxInt/8 + 1
1083+
1084+
t.Run("unmarshal_4byte", func(t *testing.T) {
1085+
tests := []struct {
1086+
name string
1087+
fn func() error
1088+
}{
1089+
{"float32", func() error { var dst []float32; return unmarshalVectorFloat32(overflowDim4, []byte{}, &dst) }},
1090+
{"int32", func() error { var dst []int32; return unmarshalVectorInt32(overflowDim4, []byte{}, &dst) }},
1091+
}
1092+
for _, tc := range tests {
1093+
t.Run(tc.name, func(t *testing.T) {
1094+
err := tc.fn()
1095+
if err == nil {
1096+
t.Fatal("expected overflow error, got nil")
1097+
}
1098+
var ue UnmarshalError
1099+
if !errors.As(err, &ue) {
1100+
t.Errorf("expected UnmarshalError, got %T: %v", err, err)
1101+
}
1102+
if !strings.Contains(err.Error(), "overflow") {
1103+
t.Errorf("expected overflow in error message, got: %v", err)
1104+
}
1105+
})
1106+
}
1107+
})
1108+
1109+
t.Run("unmarshal_8byte", func(t *testing.T) {
1110+
tests := []struct {
1111+
name string
1112+
fn func() error
1113+
}{
1114+
{"float64", func() error { var dst []float64; return unmarshalVectorFloat64(overflowDim8, []byte{}, &dst) }},
1115+
{"int64", func() error { var dst []int64; return unmarshalVectorInt64(overflowDim8, []byte{}, &dst) }},
1116+
}
1117+
for _, tc := range tests {
1118+
t.Run(tc.name, func(t *testing.T) {
1119+
err := tc.fn()
1120+
if err == nil {
1121+
t.Fatal("expected overflow error, got nil")
1122+
}
1123+
var ue UnmarshalError
1124+
if !errors.As(err, &ue) {
1125+
t.Errorf("expected UnmarshalError, got %T: %v", err, err)
1126+
}
1127+
if !strings.Contains(err.Error(), "overflow") {
1128+
t.Errorf("expected overflow in error message, got: %v", err)
1129+
}
1130+
})
1131+
}
1132+
})
1133+
}
1134+
1135+
// TestUnmarshalVectorFastPathZeroDimNonNilSlice verifies that unmarshal fast
1136+
// paths return a non-nil empty slice (not nil) when dim==0 and data is non-nil
1137+
// empty, matching the generic path behavior.
1138+
func TestUnmarshalVectorFastPathZeroDimNonNilSlice(t *testing.T) {
1139+
t.Run("float32", func(t *testing.T) {
1140+
var dst []float32
1141+
if err := unmarshalVectorFloat32(0, []byte{}, &dst); err != nil {
1142+
t.Fatalf("unexpected error: %v", err)
1143+
}
1144+
if dst == nil {
1145+
t.Error("expected non-nil empty slice, got nil")
1146+
}
1147+
if len(dst) != 0 {
1148+
t.Errorf("expected len 0, got %d", len(dst))
1149+
}
1150+
})
1151+
1152+
t.Run("float64", func(t *testing.T) {
1153+
var dst []float64
1154+
if err := unmarshalVectorFloat64(0, []byte{}, &dst); err != nil {
1155+
t.Fatalf("unexpected error: %v", err)
1156+
}
1157+
if dst == nil {
1158+
t.Error("expected non-nil empty slice, got nil")
1159+
}
1160+
if len(dst) != 0 {
1161+
t.Errorf("expected len 0, got %d", len(dst))
1162+
}
1163+
})
1164+
1165+
t.Run("int32", func(t *testing.T) {
1166+
var dst []int32
1167+
if err := unmarshalVectorInt32(0, []byte{}, &dst); err != nil {
1168+
t.Fatalf("unexpected error: %v", err)
1169+
}
1170+
if dst == nil {
1171+
t.Error("expected non-nil empty slice, got nil")
1172+
}
1173+
if len(dst) != 0 {
1174+
t.Errorf("expected len 0, got %d", len(dst))
1175+
}
1176+
})
1177+
1178+
t.Run("int64", func(t *testing.T) {
1179+
var dst []int64
1180+
if err := unmarshalVectorInt64(0, []byte{}, &dst); err != nil {
1181+
t.Fatalf("unexpected error: %v", err)
1182+
}
1183+
if dst == nil {
1184+
t.Error("expected non-nil empty slice, got nil")
1185+
}
1186+
if len(dst) != 0 {
1187+
t.Errorf("expected len 0, got %d", len(dst))
1188+
}
1189+
})
1190+
1191+
// Also verify that an existing non-nil dst is preserved as non-nil [:0].
1192+
t.Run("float32_existing_dst", func(t *testing.T) {
1193+
dst := make([]float32, 5)
1194+
if err := unmarshalVectorFloat32(0, []byte{}, &dst); err != nil {
1195+
t.Fatalf("unexpected error: %v", err)
1196+
}
1197+
if dst == nil {
1198+
t.Error("expected non-nil empty slice, got nil")
1199+
}
1200+
if len(dst) != 0 {
1201+
t.Errorf("expected len 0, got %d", len(dst))
1202+
}
1203+
})
1204+
}
1205+
10611206
// --- Test 11: Oversized buffers not pooled ---
10621207

10631208
func TestVectorBufPool_OversizedNotPooled(t *testing.T) {

0 commit comments

Comments
 (0)