blob: bd100bf77aa4096d2721f82c94ede75dc4b016ef [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vom
import (
"bytes"
"fmt"
"io"
"math/rand"
"reflect"
"runtime"
"strings"
"sync"
"testing"
"v.io/v23/vdl"
"v.io/v23/vom/testdata/data80"
"v.io/v23/vom/testdata/data81"
"v.io/v23/vom/testdata/types"
)
func TestDecoder(t *testing.T) {
for _, test := range append(data80.Tests, data81.Tests...) {
// Decode hex patterns into binary data.
binversion, err := binFromHexPat(test.HexVersion)
if err != nil {
t.Errorf("%s: couldn't convert to binary from hexversion: %q", test.Name, test.HexVersion)
continue
}
bintype, err := binFromHexPat(test.HexType)
if err != nil {
t.Errorf("%s: couldn't convert to binary from hextype: %q", test.Name, test.HexType)
continue
}
binvalue, err := binFromHexPat(test.HexValue)
if err != nil {
t.Errorf("%s: couldn't convert to binary from hexvalue: %q", test.Name, test.HexValue)
continue
}
name := test.Name + " [vdl.Value]"
testDecodeVDL(t, name, binversion+bintype+binvalue, test.Value)
name = test.Name + " [vdl.Value] (with TypeDecoder)"
testDecodeVDLWithTypeDecoder(t, name, binversion, bintype, binvalue, test.Value)
name = test.Name + " [vdl.Any]"
testDecodeVDL(t, name, binversion+bintype+binvalue, vdl.AnyValue(test.Value))
name = test.Name + " [vdl.Any] (with TypeDecoder)"
testDecodeVDLWithTypeDecoder(t, name, binversion, bintype, binvalue, vdl.AnyValue(test.Value))
// Convert into Go value for the rest of our tests.
goValue, err := toGoValue(test.Value)
if err != nil {
t.Errorf("%s: %v", test.Name, err)
continue
}
name = test.Name + " [go value]"
testDecodeGo(t, name, binversion+bintype+binvalue, reflect.TypeOf(goValue), goValue)
name = test.Name + " [go value] (with TypeDecoder)"
testDecodeGoWithTypeDecoder(t, name, binversion, bintype, binvalue, reflect.TypeOf(goValue), goValue)
name = test.Name + " [go interface]"
testDecodeGo(t, name, binversion+bintype+binvalue, reflect.TypeOf((*interface{})(nil)).Elem(), goValue)
name = test.Name + " [go interface] (with TypeDecoder)"
testDecodeGoWithTypeDecoder(t, name, binversion, bintype, binvalue, reflect.TypeOf((*interface{})(nil)).Elem(), goValue)
}
}
func testDecodeVDL(t *testing.T, name, bin string, value *vdl.Value) {
for _, mode := range AllReadModes {
head := fmt.Sprintf("%s (%s)", name, mode)
decoder := NewDecoder(mode.TestReader(strings.NewReader(bin)))
if value == nil {
value = vdl.ZeroValue(vdl.AnyType)
}
got := vdl.ZeroValue(value.Type())
if err := decoder.Decode(got); err != nil {
t.Errorf("%s: Decode failed: %v", head, err)
return
}
if want := value; !vdl.EqualValue(got, want) {
t.Errorf("%s: Decode mismatch\nGOT %v\nWANT %v", head, got, want)
return
}
}
// Test single-shot vom.Decode twice, to ensure we test the cache hit case.
testDecodeVDLSingleShot(t, name, bin, value)
testDecodeVDLSingleShot(t, name, bin, value)
}
func testDecodeVDLSingleShot(t *testing.T, name, bin string, value *vdl.Value) {
// Test the single-shot vom.Decode.
head := fmt.Sprintf("%s (single-shot)", name)
got := vdl.ZeroValue(value.Type())
if err := Decode([]byte(bin), got); err != nil {
t.Errorf("%s: Decode failed: %v", head, err)
return
}
if want := value; !vdl.EqualValue(got, want) {
t.Errorf("%s: Decode mismatch\nGOT %v\nWANT %v", head, got, want)
return
}
}
func testDecodeVDLWithTypeDecoder(t *testing.T, name, binversion, bintype, binvalue string, value *vdl.Value) {
for _, mode := range AllReadModes {
head := fmt.Sprintf("%s (%s)", name, mode)
typedec := NewTypeDecoder(mode.TestReader(strings.NewReader(binversion + bintype)))
typedec.Start()
decoder := NewDecoderWithTypeDecoder(mode.TestReader(strings.NewReader(binversion+binvalue)), typedec)
if value == nil {
value = vdl.ZeroValue(vdl.AnyType)
}
got := vdl.ZeroValue(value.Type())
if err := decoder.Decode(got); err != nil {
t.Errorf("%s: Decode failed: %v", head, err)
return
}
if want := value; !vdl.EqualValue(got, want) {
t.Errorf("%s: Decode mismatch\nGOT %v\nWANT %v", head, got, want)
return
}
typedec.Stop()
}
}
func testDecodeGo(t *testing.T, name, bin string, rt reflect.Type, want interface{}) {
for _, mode := range AllReadModes {
head := fmt.Sprintf("%s (%s)", name, mode)
decoder := NewDecoder(mode.TestReader(strings.NewReader(bin)))
var got interface{}
if rt != nil {
got = reflect.New(rt).Elem().Interface()
}
if err := decoder.Decode(&got); err != nil {
t.Errorf("%s: Decode failed: %v", head, err)
return
}
if !vdl.DeepEqual(got, want) {
t.Errorf("%s: Decode mismatch\nGOT %T %+v\nWANT %T %+v", head, got, got, want, want)
return
}
}
// Test single-shot vom.Decode twice, to ensure we test the cache hit case.
testDecodeGoSingleShot(t, name, bin, rt, want)
testDecodeGoSingleShot(t, name, bin, rt, want)
}
func testDecodeGoSingleShot(t *testing.T, name, bin string, rt reflect.Type, want interface{}) {
head := fmt.Sprintf("%s (single-shot)", name)
var got interface{}
if rt != nil {
got = reflect.New(rt).Elem().Interface()
}
if err := Decode([]byte(bin), &got); err != nil {
t.Errorf("%s: Decode failed: %v", head, err)
return
}
if !vdl.DeepEqual(got, want) {
t.Errorf("%s: Decode mismatch\nGOT %T %+v\nWANT %T %+v", head, got, got, want, want)
return
}
}
func testDecodeGoWithTypeDecoder(t *testing.T, name, binversion, bintype, binvalue string, rt reflect.Type, want interface{}) {
for _, mode := range AllReadModes {
head := fmt.Sprintf("%s (%s)", name, mode)
typedec := NewTypeDecoder(mode.TestReader(strings.NewReader(binversion + bintype)))
typedec.Start()
decoder := NewDecoderWithTypeDecoder(mode.TestReader(strings.NewReader(binversion+binvalue)), typedec)
var got interface{}
if rt != nil {
got = reflect.New(rt).Elem().Interface()
}
if err := decoder.Decode(&got); err != nil {
t.Errorf("%s: Decode failed: %v", head, err)
return
}
if !vdl.DeepEqual(got, want) {
t.Errorf("%s: Decode mismatch\nGOT %T %+v\nWANT %T %+v", head, got, got, want, want)
return
}
typedec.Stop()
}
}
// TestRoundtrip* tests test encoding and then decoding results in various modes.
func TestRoundtrip(t *testing.T) { testRoundtrip(t, false, 1) }
func TestRoundtripWithTypeDecoder_1(t *testing.T) { testRoundtrip(t, true, 1) }
func TestRoundtripWithTypeDecoder_5(t *testing.T) { testRoundtrip(t, true, 5) }
func TestRoundtripWithTypeDecoder_10(t *testing.T) { testRoundtrip(t, true, 10) }
func TestRoundtripWithTypeDecoder_20(t *testing.T) { testRoundtrip(t, true, 20) }
func testRoundtrip(t *testing.T, withTypeEncoderDecoder bool, concurrency int) {
mp := runtime.GOMAXPROCS(runtime.NumCPU())
defer runtime.GOMAXPROCS(mp)
tests := []struct {
In, Want interface{}
}{
// Test that encoding nil/empty composites leads to nil.
{[]byte(nil), []byte(nil)},
{[]byte{}, []byte(nil)},
{[]int64(nil), []int64(nil)},
{[]int64{}, []int64(nil)},
{map[string]int64(nil), map[string]int64(nil)},
{map[string]int64{}, map[string]int64(nil)},
{struct{}{}, struct{}{}},
{struct{ A []byte }{nil}, struct{ A []byte }{}},
{struct{ A []byte }{[]byte{}}, struct{ A []byte }{}},
{struct{ A []int64 }{nil}, struct{ A []int64 }{}},
{struct{ A []int64 }{[]int64{}}, struct{ A []int64 }{}},
// Test that encoding nil typeobject leads to AnyType.
{(*vdl.Type)(nil), vdl.AnyType},
// Test that both encoding and decoding ignore unexported fields.
{struct{ a, X, b string }{"a", "XYZ", "b"}, struct{ d, X, e string }{X: "XYZ"}},
{
struct {
a bool
X string
b int64
}{true, "XYZ", 123},
struct {
a complex64
X string
b []byte
}{X: "XYZ"},
},
// Test for array encoding/decoding.
{[3]byte{1, 2, 3}, [3]byte{1, 2, 3}},
{[3]int64{1, 2, 3}, [3]int64{1, 2, 3}},
// Test for zero value struct/union field encoding/decoding.
{struct{ A int64 }{0}, struct{ A int64 }{}},
{struct{ T *vdl.Type }{nil}, struct{ T *vdl.Type }{vdl.AnyType}},
{struct{ M map[uint64]struct{} }{make(map[uint64]struct{})}, struct{ M map[uint64]struct{} }{}},
{struct{ M map[uint64]string }{make(map[uint64]string)}, struct{ M map[uint64]string }{}},
{struct{ N struct{ A int64 } }{struct{ A int64 }{0}}, struct{ N struct{ A int64 } }{}},
{struct{ N *types.NStruct }{&types.NStruct{A: false, B: "", C: 0}}, struct{ N *types.NStruct }{&types.NStruct{}}},
{struct{ N *types.NStruct }{nil}, struct{ N *types.NStruct }{}},
{types.NUnion(types.NUnionA{Value: false}), types.NUnion(types.NUnionA{})},
{types.RecA{}, types.RecA(nil)},
{types.RecStruct{}, types.RecStruct{}},
// Test for verifying correctness when encoding/decoding shared types concurrently.
{types.MStruct{}, types.MStruct{E: vdl.AnyType, F: vdl.AnyValue(nil)}},
{types.NStruct{}, types.NStruct{}},
{types.XyzStruct{}, types.XyzStruct{}},
{types.YzStruct{}, types.YzStruct{}},
{types.MBool(false), types.MBool(false)},
{types.NString(""), types.NString("")},
{vdl.ValueOf(uint16(5)), vdl.ValueOf(uint16(5))},
{vdl.ValueOf([]interface{}{uint16(5)}).Index(0), vdl.ValueOf(uint16(5))},
}
var (
typeenc *TypeEncoder
typedec *TypeDecoder
)
if withTypeEncoderDecoder {
r, w := newPipe()
typeenc = NewTypeEncoder(w)
typedec = NewTypeDecoder(r)
typedec.Start()
defer typedec.Stop()
}
var wg sync.WaitGroup
for n := 0; n < concurrency; n++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
for _, n := range rand.Perm(len(tests) * 10) {
test := tests[n%len(tests)]
name := fmt.Sprintf("[%d]:%+v,%+v", n, test.In, test.Want)
var (
encoder *Encoder
decoder *Decoder
buf bytes.Buffer
)
if withTypeEncoderDecoder {
encoder = NewEncoderWithTypeEncoder(&buf, typeenc)
decoder = NewDecoderWithTypeDecoder(&buf, typedec)
} else {
encoder = NewEncoder(&buf)
decoder = NewDecoder(&buf)
}
if err := encoder.Encode(test.In); err != nil {
t.Errorf("%s: Encode(%+v) failed: %v", name, test.In, err)
return
}
rv := reflect.New(reflect.TypeOf(test.Want))
if err := decoder.Decode(rv.Interface()); err != nil {
t.Errorf("%s: Decode failed: %v", name, err)
return
}
if got := rv.Elem().Interface(); !vdl.DeepEqual(got, test.Want) {
t.Errorf("%s: Decode mismatch\nGOT %T %+v\nWANT %T %+v", name, got, got, test.Want, test.Want)
return
}
}
}(n)
}
wg.Wait()
}
// waitingReader is a reader wrapper that waits until it is signalled before
// beginning to read.
type waitingReader struct {
lock sync.Mutex
cond *sync.Cond
activated bool
r io.Reader
}
func newWaitingReader(r io.Reader) *waitingReader {
wr := &waitingReader{
r: r,
}
wr.cond = sync.NewCond(&wr.lock)
return wr
}
func (wr *waitingReader) Read(p []byte) (int, error) {
wr.lock.Lock()
for !wr.activated {
wr.cond.Wait()
}
wr.lock.Unlock()
return wr.r.Read(p)
}
func (wr *waitingReader) Activate() {
wr.lock.Lock()
wr.activated = true
wr.cond.Broadcast()
wr.lock.Unlock()
}
type extractErrReader struct {
lock sync.Mutex
cond *sync.Cond
err error
r io.Reader
}
func newExtractErrReader(r io.Reader) *extractErrReader {
er := &extractErrReader{
r: r,
}
er.cond = sync.NewCond(&er.lock)
return er
}
func (er *extractErrReader) Read(p []byte) (int, error) {
n, err := er.r.Read(p)
if err != nil {
er.lock.Lock()
er.err = err
er.cond.Broadcast()
er.lock.Unlock()
}
return n, err
}
func (er *extractErrReader) WaitForError() error {
er.lock.Lock()
for er.err == nil {
er.cond.Wait()
}
err := er.err
er.lock.Unlock()
return err
}
// Test that no EOF is returned from Decode() if the type stream finished before the value stream.
func TestTypeStreamEndsFirst(t *testing.T) {
hexversion := "81"
hextype := "5133060025762e696f2f7632332f766f6d2f74657374646174612f74797065732e537472756374416e7901010003416e79010fe1e1533b060023762e696f2f7632332f766f6d2f74657374646174612f74797065732e4e53747275637401030001410101e10001420103e10001430109e1e1"
hexvalue := "52012a0103070000000001e1e1"
binversion := string(hex2Bin(t, hexversion))
bintype := string(hex2Bin(t, hextype))
binvalue := string(hex2Bin(t, hexvalue))
// Ensure EOF isn't returned if the type decode stream ends first
tr := newExtractErrReader(strings.NewReader(binversion + bintype))
typedec := NewTypeDecoder(tr)
typedec.Start()
wr := newWaitingReader(strings.NewReader(binversion + binvalue))
decoder := NewDecoderWithTypeDecoder(wr, typedec)
var v interface{}
go func() {
if tr.WaitForError() == nil {
t.Fatalf("expected EOF after reaching end of type stream, but didn't occur")
}
wr.Activate()
}()
if err := decoder.Decode(&v); err != nil {
t.Errorf("expected no error in decode, but got: %v", err)
}
}
// Return an error on all reads.
type errorReader struct {
}
func (er *errorReader) Read(p []byte) (int, error) {
return 0, fmt.Errorf("errorReader error\n")
}
// Test that non-EOF errors on the value stream are returned from Decode() calls.
func TestReceiveTypeStreamError(t *testing.T) {
hexversion := "80"
hexvalue := "5206002a000001e1"
binversion := string(hex2Bin(t, hexversion))
binvalue := string(hex2Bin(t, hexvalue))
// Ensure EOF isn't returned if the type decode stream ends first
typedec := NewTypeDecoder(&errorReader{})
typedec.Start()
decoder := NewDecoderWithTypeDecoder(strings.NewReader(binversion+binvalue), typedec)
var v interface{}
if err := decoder.Decode(&v); err == nil {
t.Errorf("expected error in decode, but got none")
}
}
// Test that using the type decoder incorrectly does not result
// in a deadlock.
func TestFuzzTypeDecodeDeadlock(t *testing.T) {
var v interface{}
d := NewDecoder(strings.NewReader("\x80\x30"))
// Before the fix, this line caused a deadlock and panic.
d.Decode(&v)
return
}
// Tests that an input go-fuzz found will no longer cause a
// panic over in package vdl.
func TestFuzzVdlPanic(t *testing.T) {
var v interface{}
d := NewDecoder(strings.NewReader("\x80S*\x00\x00$000000000000000000000000000000000000\x01*\xe1U(\x05\x00 00000000000000000000000000000000\x01*\x02+\xe1"))
// Before this fix this line caused a panic.
d.Decode(&v)
return
}
// In concurrent modes, one goroutine may try to read vom types before they are
// actually sent by other goroutine. We use a simple buffered pipe to provide
// blocking read since bytes.Buffer will return EOF in this case.
type pipe struct {
b bytes.Buffer
m sync.Mutex
c sync.Cond
cancelled bool
}
func newPipe() (io.ReadCloser, io.WriteCloser) {
p := &pipe{}
p.c.L = &p.m
return p, p
}
func (r *pipe) Read(p []byte) (n int, err error) {
r.m.Lock()
defer r.m.Unlock()
for r.b.Len() == 0 || r.cancelled {
r.c.Wait()
}
return r.b.Read(p)
}
func (p *pipe) Close() error {
p.m.Lock()
p.cancelled = true
p.c.Broadcast()
p.m.Unlock()
return nil
}
func (w *pipe) Write(p []byte) (n int, err error) {
w.m.Lock()
defer w.m.Unlock()
defer w.c.Signal()
return w.b.Write(p)
}
// Test that input found by go-fuzz cannot cause a stack overflow.
func TestFuzzDecodeOverflow(t *testing.T) {
var v interface{}
d := NewDecoder(strings.NewReader("\x81\x51\x04\x03\x01\x29\xe1"))
// Before the fix, this line caused a stack overflow. After the fix, we
// expect an error.
if err := d.Decode(&v); err == nil {
t.Fatal("unexpected success")
}
}
// Test that input discovered by go-fuzz does not result in a hang anymore.
func TestFuzzTypeDecodeHang(t *testing.T) {
var v interface{}
d := NewDecoder(strings.NewReader(
"\x80W&\x03\x00 v.io/v23/vom/t" +
"estdata/types.Rec4\x01)" +
"\xe1U&\x03\x00 v.io/v23/vom/t" +
"estdata/types.Rec3\x01," +
"\xe1S&\x00\x00 v.io/v23/v\x04\x00/t" +
"estdata/types.Rec2\x01*" +
"\xe1Q&\x00\x00 v.io/v23/vom/t" +
"estdataPtypesIRec1\x01*" +
"\xe1"))
// Before the fix, this line caused a hang. With the fix, it should
// give an error.
err := d.Decode(&v)
if err != io.EOF {
t.Fatal("unexpected success")
}
}
// Test that truncated input is handled gracefully.
func TestDecodeTruncatedInput(t *testing.T) {
type bar struct {
I []int64
}
type foo struct {
S string
I int32
F float64
X []byte
B bar
}
encoded, err := Encode(foo{"hello", 42, 3.14159265359, []byte{1, 2, 3, 4, 5, 6}, bar{[]int64{0}}})
if err != nil {
t.Fatalf("Encode failed: %v", err)
}
for x := 0; x < len(encoded)-1; x++ {
var f interface{}
if err := Decode(encoded[:x], &f); err == nil {
t.Errorf("Encode did not fail with x=%d", x)
}
}
}