diff --git a/bytes.go b/bytes.go index cf971b0..c9fe3e2 100644 --- a/bytes.go +++ b/bytes.go @@ -39,7 +39,12 @@ func bytesNativeFromBinary(buf []byte) (interface{}, []byte, error) { if size > int64(len(buf)) { return nil, nil, fmt.Errorf("cannot decode binary bytes: %s", io.ErrShortBuffer) } - return buf[:size], buf[size:], nil + + // allocate new byte slice to avoid referencing the same underlying array + out := make([]byte, size) + copy(out, buf[:size]) + + return out, buf[size:], nil } func stringNativeFromBinary(buf []byte) (interface{}, []byte, error) { diff --git a/bytes_test.go b/bytes_test.go index b2c4c8c..4d53da0 100644 --- a/bytes_test.go +++ b/bytes_test.go @@ -10,7 +10,9 @@ package goavro import ( + "bytes" "encoding/json" + "fmt" "strings" "testing" ) @@ -199,3 +201,49 @@ func TestStringCodecAcceptsBytes(t *testing.T) { testTextEncodePass(t, schema, []byte("abcd"), []byte(`"abcd"`)) }) } + +func TestBytesUnderlyingArray(t *testing.T) { + ensureMap := func(expectedMap, actualMap map[string]interface{}) { + if actual, expected := len(actualMap), len(expectedMap); actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + for k, v := range actualMap { + if actual, expected := fmt.Sprintf("%s", expectedMap[k]), fmt.Sprintf("%s", v); actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + } + } + + c, err := NewCodec(`{"name":"r1","type":"record","fields":[{"name":"foo","type":"bytes"},{"name":"bar","type":"bytes"}]}`) + ensureError(t, err) + + datumIn := map[string]interface{}{ + "foo": []byte("abc"), + "bar": []byte("def"), + } + + buf, err := c.BinaryFromNative(nil, datumIn) + ensureError(t, err) + if expected := []byte("\x06abc\x06def"); !bytes.Equal(buf, expected) { + t.Errorf("GOT: %#v; WANT: %#v", buf, expected) + } + + // round trip + datumOut, buf, err := c.NativeFromBinary(buf) + ensureError(t, err) + if actual, expected := len(buf), 0; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + + datumOutMap, ok := datumOut.(map[string]interface{}) + if !ok { + t.Errorf("GOT: %#v; WANT: %#v", ok, true) + } + ensureMap(datumIn, datumOutMap) + + // manipulate foo + _ = append(datumOutMap["foo"].([]byte), 0, 0) + + // datumOutMap should stay unchanged + ensureMap(datumIn, datumOutMap) +}