blob: 98317e430ef25aa694ec72159c03bf8bdadfc8fa [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_test
import (
"bytes"
"fmt"
"io"
"math/rand"
"reflect"
"runtime"
"strings"
"sync"
"testing"
"v.io/v23/vdl"
"v.io/v23/vom"
"v.io/v23/vom/testdata/types"
"v.io/v23/vom/vomtest"
// Import verror to ensure that interface tests result in *verror.E
_ "v.io/v23/verror"
)
var (
rtIface = reflect.TypeOf((*interface{})(nil)).Elem()
rtValue = reflect.TypeOf(vdl.Value{})
)
func TestDecoder(t *testing.T) {
// The decoder tests take a long time, so we run them concurrently.
var pending sync.WaitGroup
for _, test := range vomtest.AllPass() {
pending.Add(1)
go func(test vomtest.Entry) {
defer pending.Done()
testDecoder(t, "[go value]", test, rvPtrValue(test.Value))
testDecoder(t, "[go iface]", test, rvPtrIface(test.Value))
vv, err := vdl.ValueFromReflect(test.Value)
if err != nil {
t.Errorf("%s: ValueFromReflect failed: %v", test.Name(), err)
return
}
vvWant := reflect.ValueOf(vv)
testDecoder(t, "[new *vdl.Value]", test, vvWant)
testDecoderFunc(t, "[zero vdl.Value]", test, vvWant, func() reflect.Value {
return reflect.ValueOf(vdl.ZeroValue(vv.Type()))
})
}(test)
}
pending.Wait()
}
func rvPtrValue(rv reflect.Value) reflect.Value {
result := reflect.New(rv.Type())
result.Elem().Set(rv)
return result
}
func rvPtrIface(rv reflect.Value) reflect.Value {
result := reflect.New(rtIface)
result.Elem().Set(rv)
return result
}
func testDecoder(t *testing.T, pre string, test vomtest.Entry, rvWant reflect.Value) {
testDecoderFunc(t, pre, test, rvWant, func() reflect.Value {
return reflect.New(rvWant.Type().Elem())
})
// TODO(toddw): Add tests that start with a randomly-set value.
}
func testDecoderFunc(t *testing.T, pre string, test vomtest.Entry, rvWant reflect.Value, rvNew func() reflect.Value) {
readEOF := make([]byte, 1)
for _, mode := range vom.AllReadModes {
// Test vom.NewDecoder.
{
name := fmt.Sprintf("%s (%s) %s", pre, mode, test.Name())
rvGot := rvNew()
reader := mode.TestReader(bytes.NewReader(test.Bytes()))
dec := vom.NewDecoder(reader)
if err := dec.Decode(rvGot.Interface()); err != nil {
t.Errorf("%s: Decode failed: %v", name, err)
return
}
if !vdl.DeepEqualReflect(rvGot, rvWant) {
t.Errorf("%s\nGOT %v\nWANT %v", name, rvGot, rvWant)
return
}
if n, err := reader.Read(readEOF); n != 0 || err != io.EOF {
t.Errorf("%s: reader got (%d,%v), want (0,EOF)", name, n, err)
}
}
// Test vom.NewDecoderWithTypeDecoder
{
name := fmt.Sprintf("%s (%s with TypeDecoder) %s", pre, mode, test.Name())
rvGot := rvNew()
readerT := mode.TestReader(bytes.NewReader(test.TypeBytes()))
decT := vom.NewTypeDecoder(readerT)
decT.Start()
reader := mode.TestReader(bytes.NewReader(test.ValueBytes()))
dec := vom.NewDecoderWithTypeDecoder(reader, decT)
err := dec.Decode(rvGot.Interface())
decT.Stop()
if err != nil {
t.Errorf("%s: Decode failed: %v", name, err)
return
}
if !vdl.DeepEqualReflect(rvGot, rvWant) {
t.Errorf("%s\nGOT %v\nWANT %v", name, rvGot, rvWant)
return
}
if n, err := reader.Read(readEOF); n != 0 || err != io.EOF {
t.Errorf("%s: reader got (%d,%v), want (0,EOF)", name, n, err)
}
if n, err := readerT.Read(readEOF); n != 0 || err != io.EOF {
t.Errorf("%s: readerT got (%d,%v), want (0,EOF)", name, n, err)
}
}
}
// Test single-shot vom.Decode twice, to ensure we test the cache hit case.
for i := 0; i < 2; i++ {
name := fmt.Sprintf("%s (single-shot %d) %s", pre, i, test.Name())
rvGot := rvNew()
if err := vom.Decode(test.Bytes(), rvGot.Interface()); err != nil {
t.Errorf("%s: Decode failed: %v", name, err)
return
}
if !vdl.DeepEqualReflect(rvGot, rvWant) {
t.Errorf("%s\nGOT %v\nWANT %v", name, rvGot, rvWant)
return
}
}
}
// 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 *vom.TypeEncoder
typedec *vom.TypeDecoder
)
if withTypeEncoderDecoder {
r, w := newPipe()
typeenc = vom.NewTypeEncoder(w)
typedec = vom.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 *vom.Encoder
decoder *vom.Decoder
buf bytes.Buffer
)
if withTypeEncoderDecoder {
encoder = vom.NewEncoderWithTypeEncoder(&buf, typeenc)
decoder = vom.NewDecoderWithTypeDecoder(&buf, typedec)
} else {
encoder = vom.NewEncoder(&buf)
decoder = vom.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
}
func hex2Bin(t *testing.T, hex string) []byte {
var bin string
if _, err := fmt.Sscanf(hex, "%x", &bin); err != nil {
t.Fatalf("error converting %q to binary: %v", hex, err)
}
return []byte(bin)
}
// 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 := vom.NewTypeDecoder(tr)
typedec.Start()
wr := newWaitingReader(strings.NewReader(binversion + binvalue))
decoder := vom.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 := vom.NewTypeDecoder(&errorReader{})
typedec.Start()
decoder := vom.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 := vom.NewDecoder(strings.NewReader("\x81\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 := vom.NewDecoder(strings.NewReader("\x81S*\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 := vom.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 := vom.NewDecoder(strings.NewReader(
"\x81W&\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 == nil {
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 := vom.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 := vom.Decode(encoded[:x], &f); err == nil {
t.Errorf("Decode did not fail with x=%d", x)
}
}
}