Commit 2dfff15a authored by rw's avatar rw

Improve Builder user interface.

+ Add state to the Builder object to track if we are inside a table,
  and if we are finished building the buffer.
+ Use this data to check that a buffer is being built correctly.
+ Panic if a buffer is not being built correctly.
+ Test that the panics happen as expected.

Based on d236dea1.
parent 9dc5d378
...@@ -6,14 +6,17 @@ package flatbuffers ...@@ -6,14 +6,17 @@ package flatbuffers
// A Builder constructs byte buffers in a last-first manner for simplicity and // A Builder constructs byte buffers in a last-first manner for simplicity and
// performance. // performance.
type Builder struct { type Builder struct {
// `Bytes` gives raw access to the buffer. Most users will want to use
// FinishedBytes() instead.
Bytes []byte Bytes []byte
minalign int minalign int
vtable []UOffsetT vtable []UOffsetT
objectEnd UOffsetT objectEnd UOffsetT
insideObject bool vtables []UOffsetT
vtables []UOffsetT head UOffsetT
head UOffsetT nested bool
finished bool
} }
// NewBuilder initializes a Builder of size `initial_size`. // NewBuilder initializes a Builder of size `initial_size`.
...@@ -33,7 +36,7 @@ func NewBuilder(initialSize int) *Builder { ...@@ -33,7 +36,7 @@ func NewBuilder(initialSize int) *Builder {
} }
// Reset truncates the underlying Builder buffer, facilitating alloc-free // Reset truncates the underlying Builder buffer, facilitating alloc-free
// reuse of a Builder. // reuse of a Builder. It also resets bookkeeping data.
func (b *Builder) Reset() { func (b *Builder) Reset() {
if b.Bytes != nil { if b.Bytes != nil {
b.Bytes = b.Bytes[:cap(b.Bytes)] b.Bytes = b.Bytes[:cap(b.Bytes)]
...@@ -49,12 +52,22 @@ func (b *Builder) Reset() { ...@@ -49,12 +52,22 @@ func (b *Builder) Reset() {
b.head = UOffsetT(len(b.Bytes)) b.head = UOffsetT(len(b.Bytes))
b.minalign = 1 b.minalign = 1
b.nested = false
b.finished = false
}
// FinishedBytes returns a pointer to the written data in the byte buffer.
// Panics if the builder is not in a finished state (which is caused by calling
// `Finish()`).
func (b *Builder) FinishedBytes() []byte {
b.assertFinished()
return b.Bytes[b.Head():]
} }
// 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.assertNotNested()
b.insideObject = true b.nested = 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 {
...@@ -129,7 +142,7 @@ func (b *Builder) WriteVtable() (n UOffsetT) { ...@@ -129,7 +142,7 @@ func (b *Builder) WriteVtable() (n UOffsetT) {
var off UOffsetT var off UOffsetT
if b.vtable[i] != 0 { if b.vtable[i] != 0 {
// Forward reference to field; // Forward reference to field;
// use 32bit number to ensure no overflow: // use 32bit number to assert no overflow:
off = objectOffset - b.vtable[i] off = objectOffset - b.vtable[i]
} }
...@@ -173,11 +186,9 @@ func (b *Builder) WriteVtable() (n UOffsetT) { ...@@ -173,11 +186,9 @@ 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.insideObject { b.assertNested()
panic("not in object")
}
n := b.WriteVtable() n := b.WriteVtable()
b.insideObject = false b.nested = false
return n return n
} }
...@@ -271,7 +282,8 @@ func (b *Builder) PrependUOffsetT(off UOffsetT) { ...@@ -271,7 +282,8 @@ func (b *Builder) PrependUOffsetT(off UOffsetT) {
// <UOffsetT: number of elements in this vector> // <UOffsetT: number of elements in this vector>
// <T: data>+, where T is the type of elements of this vector. // <T: data>+, where T is the type of elements of this vector.
func (b *Builder) StartVector(elemSize, numElems, alignment int) UOffsetT { func (b *Builder) StartVector(elemSize, numElems, alignment int) UOffsetT {
b.notNested() b.assertNotNested()
b.nested = true
b.Prep(SizeUint32, elemSize*numElems) b.Prep(SizeUint32, elemSize*numElems)
b.Prep(alignment, elemSize*numElems) // Just in case alignment > int. b.Prep(alignment, elemSize*numElems) // Just in case alignment > int.
return b.Offset() return b.Offset()
...@@ -279,14 +291,19 @@ func (b *Builder) StartVector(elemSize, numElems, alignment int) UOffsetT { ...@@ -279,14 +291,19 @@ func (b *Builder) StartVector(elemSize, numElems, alignment int) UOffsetT {
// EndVector writes data necessary to finish vector construction. // EndVector writes data necessary to finish vector construction.
func (b *Builder) EndVector(vectorNumElems int) UOffsetT { func (b *Builder) EndVector(vectorNumElems int) UOffsetT {
b.assertNested()
// we already made space for this, so write without PrependUint32 // we already made space for this, so write without PrependUint32
b.PlaceUOffsetT(UOffsetT(vectorNumElems)) b.PlaceUOffsetT(UOffsetT(vectorNumElems))
b.nested = false
return b.Offset() return b.Offset()
} }
// 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.assertNotNested()
b.nested = true
b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte) b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte)
b.PlaceByte(0) b.PlaceByte(0)
...@@ -301,7 +318,8 @@ func (b *Builder) CreateString(s string) UOffsetT { ...@@ -301,7 +318,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.assertNotNested()
b.nested = true
b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte) b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte)
b.PlaceByte(0) b.PlaceByte(0)
...@@ -316,6 +334,9 @@ func (b *Builder) CreateByteString(s []byte) UOffsetT { ...@@ -316,6 +334,9 @@ func (b *Builder) CreateByteString(s []byte) UOffsetT {
// CreateByteVector writes a ubyte vector // CreateByteVector writes a ubyte vector
func (b *Builder) CreateByteVector(v []byte) UOffsetT { func (b *Builder) CreateByteVector(v []byte) UOffsetT {
b.assertNotNested()
b.nested = true
b.Prep(int(SizeUOffsetT), len(v)*SizeByte) b.Prep(int(SizeUOffsetT), len(v)*SizeByte)
l := UOffsetT(len(v)) l := UOffsetT(len(v))
...@@ -326,20 +347,38 @@ func (b *Builder) CreateByteVector(v []byte) UOffsetT { ...@@ -326,20 +347,38 @@ func (b *Builder) CreateByteVector(v []byte) UOffsetT {
return b.EndVector(len(v)) return b.EndVector(len(v))
} }
func (b *Builder) notNested() { func (b *Builder) assertNested() {
// Check that no other objects are being built while making this // If you get this assert, you're in an object while trying to write
// object. If not, panic: // data that belongs outside of an object.
if b.insideObject { // To fix this, write non-inline data (like vectors) before creating
panic("non-inline data write inside of object") // objects.
if !b.nested {
panic("Incorrect creation order: must be inside object.")
} }
} }
func (b *Builder) nested(obj UOffsetT) { func (b *Builder) assertNotNested() {
// Structs are always stored inline, so need to be created right // If you hit this, you're trying to construct a Table/Vector/String
// where they are used. You'll get this panic if you created it // during the construction of its parent table (between the MyTableBuilder
// elsewhere: // and builder.Finish()).
if obj != b.Offset() { // Move the creation of these sub-objects to above the MyTableBuilder to
panic("inline data write outside of object") // not get this assert.
// Ignoring this assert may appear to work in simple cases, but the reason
// it is here is that storing objects in-line may cause vtable offsets
// to not fit anymore. It also leads to vtable duplication.
if b.nested {
panic("Incorrect creation order: object must not be nested.")
}
}
func (b *Builder) assertFinished() {
// If you get this assert, you're attempting to get access a buffer
// which hasn't been finished yet. Be sure to call builder.Finish()
// with your root table.
// If you really need to access an unfinished buffer, use the Bytes
// buffer directly.
if !b.finished {
panic("Incorrect use of FinishedBytes(): must call 'Finish' first.")
} }
} }
...@@ -483,7 +522,10 @@ func (b *Builder) PrependUOffsetTSlot(o int, x, d UOffsetT) { ...@@ -483,7 +522,10 @@ func (b *Builder) PrependUOffsetTSlot(o int, x, d UOffsetT) {
// In generated code, `d` is always 0. // In generated code, `d` is always 0.
func (b *Builder) PrependStructSlot(voffset int, x, d UOffsetT) { func (b *Builder) PrependStructSlot(voffset int, x, d UOffsetT) {
if x != d { if x != d {
b.nested(x) b.assertNested()
if x != b.Offset() {
panic("inline data write outside of object")
}
b.Slot(voffset) b.Slot(voffset)
} }
} }
...@@ -495,8 +537,10 @@ func (b *Builder) Slot(slotnum int) { ...@@ -495,8 +537,10 @@ func (b *Builder) Slot(slotnum int) {
// Finish finalizes a buffer, pointing to the given `rootTable`. // Finish finalizes a buffer, pointing to the given `rootTable`.
func (b *Builder) Finish(rootTable UOffsetT) { func (b *Builder) Finish(rootTable UOffsetT) {
b.assertNotNested()
b.Prep(b.minalign, SizeUOffsetT) b.Prep(b.minalign, SizeUOffsetT)
b.PrependUOffsetT(rootTable) b.PrependUOffsetT(rootTable)
b.finished = true
} }
// vtableEqual compares an unwritten vtable to a written vtable. // vtableEqual compares an unwritten vtable to a written vtable.
......
...@@ -70,10 +70,10 @@ func TestAll(t *testing.T) { ...@@ -70,10 +70,10 @@ func TestAll(t *testing.T) {
// Verify that panics are raised during exceptional conditions: // Verify that panics are raised during exceptional conditions:
CheckNotInObjectError(t.Fatalf) CheckNotInObjectError(t.Fatalf)
CheckObjectIsNestedError(t.Fatalf)
CheckStringIsNestedError(t.Fatalf) CheckStringIsNestedError(t.Fatalf)
CheckByteStringIsNestedError(t.Fatalf) CheckByteStringIsNestedError(t.Fatalf)
CheckStructIsNotInlineError(t.Fatalf) CheckStructIsNotInlineError(t.Fatalf)
CheckFinishedBytesError(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:
...@@ -1113,20 +1113,6 @@ func CheckNotInObjectError(fail func(string, ...interface{})) { ...@@ -1113,20 +1113,6 @@ func CheckNotInObjectError(fail func(string, ...interface{})) {
b.EndObject() 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 // CheckStringIsNestedError verifies that a string can not be created inside
// another object. // another object.
func CheckStringIsNestedError(fail func(string, ...interface{})) { func CheckStringIsNestedError(fail func(string, ...interface{})) {
...@@ -1169,6 +1155,20 @@ func CheckStructIsNotInlineError(fail func(string, ...interface{})) { ...@@ -1169,6 +1155,20 @@ func CheckStructIsNotInlineError(fail func(string, ...interface{})) {
b.PrependStructSlot(0, 1, 0) b.PrependStructSlot(0, 1, 0)
} }
// CheckFinishedBytesError verifies that `FinishedBytes` panics if the table
// is not finished.
func CheckFinishedBytesError(fail func(string, ...interface{})) {
b := flatbuffers.NewBuilder(0)
defer func() {
r := recover()
if r == nil {
fail("expected panic in CheckFinishedBytesError")
}
}()
b.FinishedBytes()
}
// 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{})) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment