From 4d305f5922c52935006987ef27f29516d3fd1583 Mon Sep 17 00:00:00 2001 From: rw Date: Wed, 24 Jun 2015 11:53:44 -0400 Subject: [PATCH] Panic when nesting strings. Test panic scenarios. Also add a new `insideObject` boolean to the Builder to track whether an object is currently being constructed. This fixes a bug with objects that have zero fields. --- go/builder.go | 25 ++++++++++----- tests/go_test.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 9 deletions(-) diff --git a/go/builder.go b/go/builder.go index c7aa264c7..7533dddf2 100644 --- a/go/builder.go +++ b/go/builder.go @@ -8,11 +8,12 @@ package flatbuffers type Builder struct { Bytes []byte - minalign int - vtable []UOffsetT - objectEnd UOffsetT - vtables []UOffsetT - head UOffsetT + minalign int + vtable []UOffsetT + objectEnd UOffsetT + insideObject bool + vtables []UOffsetT + head UOffsetT } // NewBuilder initializes a Builder of size `initial_size`. @@ -53,6 +54,8 @@ func (b *Builder) Reset() { // StartObject initializes bookkeeping for writing a new object. func (b *Builder) StartObject(numfields int) { b.notNested() + b.insideObject = true + // use 32-bit offsets so that arithmetic doesn't overflow. if cap(b.vtable) < numfields || b.vtable == nil { b.vtable = make([]UOffsetT, numfields) @@ -170,10 +173,12 @@ func (b *Builder) WriteVtable() (n UOffsetT) { // EndObject writes data necessary to finish object construction. func (b *Builder) EndObject() UOffsetT { - if b.vtable == nil { + if !b.insideObject { panic("not in object") } - return b.WriteVtable() + n := b.WriteVtable() + b.insideObject = false + return n } // Doubles the size of the byteslice, and copies the old data towards the @@ -281,6 +286,8 @@ func (b *Builder) EndVector(vectorNumElems int) UOffsetT { // CreateString writes a null-terminated string as a vector. func (b *Builder) CreateString(s string) UOffsetT { + b.notNested() + b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte) b.PlaceByte(0) @@ -294,6 +301,8 @@ func (b *Builder) CreateString(s string) UOffsetT { // CreateByteString writes a byte slice as a string (null-terminated). func (b *Builder) CreateByteString(s []byte) UOffsetT { + b.notNested() + b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte) b.PlaceByte(0) @@ -320,7 +329,7 @@ func (b *Builder) CreateByteVector(v []byte) UOffsetT { func (b *Builder) notNested() { // Check that no other objects are being built while making this // object. If not, panic: - if len(b.vtable) > 0 { + if b.insideObject { panic("non-inline data write inside of object") } } diff --git a/tests/go_test.go b/tests/go_test.go index 070045a1d..18a6b8e2e 100644 --- a/tests/go_test.go +++ b/tests/go_test.go @@ -68,6 +68,13 @@ func TestAll(t *testing.T) { // expected bytes (does not use any schema): CheckByteLayout(t.Fatalf) + // Verify that panics are raised during exceptional conditions: + CheckNotInObjectError(t.Fatalf) + CheckObjectIsNestedError(t.Fatalf) + CheckStringIsNestedError(t.Fatalf) + CheckByteStringIsNestedError(t.Fatalf) + CheckStructIsNotInlineError(t.Fatalf) + // Verify that using the generated Go code builds a buffer without // returning errors: generated, off := CheckGeneratedBuild(t.Fatalf) @@ -540,7 +547,7 @@ func CheckByteLayout(fail func(string, ...interface{})) { // We use escape codes here so that editors without unicode support // aren't bothered: uni_str := "\u65e5\u672c\u8a9e" - b.CreateString(uni_str) + b.CreateString(uni_str) check([]byte{9, 0, 0, 0, 230, 151, 165, 230, 156, 172, 232, 170, 158, 0, // null-terminated, 2-byte pad 0, 0}) @@ -1096,6 +1103,76 @@ func CheckVtableDeduplication(fail func(string, ...interface{})) { testTable(table2, 0, 77, 88, 99) } +// CheckNotInObjectError verifies that `EndObject` fails if not inside an +// object. +func CheckNotInObjectError(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + + defer func() { + r := recover() + if r == nil { + fail("expected panic in CheckNotInObjectError") + } + }() + b.EndObject() +} + +// CheckObjectIsNestedError verifies that an object can not be created inside +// another object. +func CheckObjectIsNestedError(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + b.StartObject(0) + defer func() { + r := recover() + if r == nil { + fail("expected panic in CheckObjectIsNestedError") + } + }() + b.StartObject(0) +} + +// CheckStringIsNestedError verifies that a string can not be created inside +// another object. +func CheckStringIsNestedError(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + b.StartObject(0) + defer func() { + r := recover() + if r == nil { + fail("expected panic in CheckStringIsNestedError") + } + }() + b.CreateString("foo") +} + +// CheckByteStringIsNestedError verifies that a bytestring can not be created +// inside another object. +func CheckByteStringIsNestedError(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + b.StartObject(0) + defer func() { + r := recover() + if r == nil { + fail("expected panic in CheckByteStringIsNestedError") + } + }() + b.CreateByteString([]byte("foo")) +} + +// CheckStructIsNotInlineError verifies that writing a struct in a location +// away from where it is used will cause a panic. +func CheckStructIsNotInlineError(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + b.StartObject(0) + defer func() { + r := recover() + if r == nil { + fail("expected panic in CheckStructIsNotInlineError") + } + }() + b.PrependStructSlot(0, 1, 0) +} + // CheckDocExample checks that the code given in FlatBuffers documentation // is syntactically correct. func CheckDocExample(buf []byte, off flatbuffers.UOffsetT, fail func(string, ...interface{})) {