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.
This commit is contained in:
rw 2015-06-24 11:53:44 -04:00
parent 1e6f8f5b8c
commit 4d305f5922
2 changed files with 95 additions and 9 deletions

View File

@ -8,11 +8,12 @@ package flatbuffers
type Builder struct { type Builder struct {
Bytes []byte Bytes []byte
minalign int minalign int
vtable []UOffsetT vtable []UOffsetT
objectEnd UOffsetT objectEnd UOffsetT
vtables []UOffsetT insideObject bool
head UOffsetT vtables []UOffsetT
head UOffsetT
} }
// NewBuilder initializes a Builder of size `initial_size`. // NewBuilder initializes a Builder of size `initial_size`.
@ -53,6 +54,8 @@ func (b *Builder) Reset() {
// StartObject initializes bookkeeping for writing a new object. // StartObject initializes bookkeeping for writing a new object.
func (b *Builder) StartObject(numfields int) { func (b *Builder) StartObject(numfields int) {
b.notNested() b.notNested()
b.insideObject = true
// use 32-bit offsets so that arithmetic doesn't overflow. // use 32-bit offsets so that arithmetic doesn't overflow.
if cap(b.vtable) < numfields || b.vtable == nil { if cap(b.vtable) < numfields || b.vtable == nil {
b.vtable = make([]UOffsetT, numfields) b.vtable = make([]UOffsetT, numfields)
@ -170,10 +173,12 @@ func (b *Builder) WriteVtable() (n UOffsetT) {
// EndObject writes data necessary to finish object construction. // EndObject writes data necessary to finish object construction.
func (b *Builder) EndObject() UOffsetT { func (b *Builder) EndObject() UOffsetT {
if b.vtable == nil { if !b.insideObject {
panic("not in object") 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 // 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. // CreateString writes a null-terminated string as a vector.
func (b *Builder) CreateString(s string) UOffsetT { func (b *Builder) CreateString(s string) UOffsetT {
b.notNested()
b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte) b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte)
b.PlaceByte(0) b.PlaceByte(0)
@ -294,6 +301,8 @@ func (b *Builder) CreateString(s string) UOffsetT {
// CreateByteString writes a byte slice as a string (null-terminated). // CreateByteString writes a byte slice as a string (null-terminated).
func (b *Builder) CreateByteString(s []byte) UOffsetT { func (b *Builder) CreateByteString(s []byte) UOffsetT {
b.notNested()
b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte) b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte)
b.PlaceByte(0) b.PlaceByte(0)
@ -320,7 +329,7 @@ func (b *Builder) CreateByteVector(v []byte) UOffsetT {
func (b *Builder) notNested() { func (b *Builder) notNested() {
// Check that no other objects are being built while making this // Check that no other objects are being built while making this
// object. If not, panic: // object. If not, panic:
if len(b.vtable) > 0 { if b.insideObject {
panic("non-inline data write inside of object") panic("non-inline data write inside of object")
} }
} }

View File

@ -68,6 +68,13 @@ func TestAll(t *testing.T) {
// expected bytes (does not use any schema): // expected bytes (does not use any schema):
CheckByteLayout(t.Fatalf) 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 // Verify that using the generated Go code builds a buffer without
// returning errors: // returning errors:
generated, off := CheckGeneratedBuild(t.Fatalf) 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 // We use escape codes here so that editors without unicode support
// aren't bothered: // aren't bothered:
uni_str := "\u65e5\u672c\u8a9e" 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 check([]byte{9, 0, 0, 0, 230, 151, 165, 230, 156, 172, 232, 170, 158, 0, // null-terminated, 2-byte pad
0, 0}) 0, 0})
@ -1096,6 +1103,76 @@ func CheckVtableDeduplication(fail func(string, ...interface{})) {
testTable(table2, 0, 77, 88, 99) 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 // CheckDocExample checks that the code given in FlatBuffers documentation
// is syntactically correct. // is syntactically correct.
func CheckDocExample(buf []byte, off flatbuffers.UOffsetT, fail func(string, ...interface{})) { func CheckDocExample(buf []byte, off flatbuffers.UOffsetT, fail func(string, ...interface{})) {