diff --git a/dart/lib/flat_buffers.dart b/dart/lib/flat_buffers.dart index 7590bdba69..0844811125 100644 --- a/dart/lib/flat_buffers.dart +++ b/dart/lib/flat_buffers.dart @@ -775,6 +775,31 @@ class Builder { } } + /// Prepare for writing a struct with the given [alignment] and [size]. + /// + /// Struct fields are written one at a time, so this only writes the padding + /// needed before the whole inline struct. Field writes still advance the tail. + @pragma('vm:prefer-inline') + void prepStruct(int alignment, int size) { + assert(!_finished); + if (_maxAlign < alignment) { + _maxAlign = alignment; + } + final alignDelta = (-(_tail + size)) & (alignment - 1); + final oldCapacity = _buf.lengthInBytes; + if (_tail + alignDelta > oldCapacity) { + final desiredNewCapacity = (oldCapacity + alignDelta) * 2; + var deltaCapacity = desiredNewCapacity - oldCapacity; + deltaCapacity += (-deltaCapacity) & (_maxAlign - 1); + final newCapacity = oldCapacity + deltaCapacity; + _buf = _allocator.resize(_buf, newCapacity, _tail, 0); + } + for (var i = _tail + 1; i <= _tail + alignDelta; i++) { + _setUint8AtTail(i, 0); + } + _tail += alignDelta; + } + /// Prepare for writing the given `count` of scalars of the given `size`. /// Additionally allocate the specified `additionalBytes`. Update the current /// tail pointer to point at the allocated space. diff --git a/dart/test/bool_structs.fbs b/dart/test/bool_structs.fbs index 47b26b5b1b..da9b738c00 100644 --- a/dart/test/bool_structs.fbs +++ b/dart/test/bool_structs.fbs @@ -8,3 +8,14 @@ struct foo_properties a : bool; b : bool; } + +// Test for #9099 +struct Header { + time: ulong; + ident: ushort; +} + +table Message { + prefix: ushort; + header: Header; +} diff --git a/dart/test/bool_structs_generated.dart b/dart/test/bool_structs_generated.dart index ec4732776f..c1aa85a304 100644 --- a/dart/test/bool_structs_generated.dart +++ b/dart/test/bool_structs_generated.dart @@ -2,9 +2,10 @@ // ignore_for_file: unused_import, unused_field, unused_element, unused_local_variable, constant_identifier_names import 'dart:typed_data' show Uint8List; - import 'package:flat_buffers/flat_buffers.dart' as fb; + + class Foo { Foo._(this._bc, this._bcOffset); factory Foo(List bytes) { @@ -17,15 +18,15 @@ class Foo { final fb.BufferContext _bc; final int _bcOffset; - FooProperties? get myFoo => - FooProperties.reader.vTableGetNullable(_bc, _bcOffset, 4); + FooProperties? get myFoo => FooProperties.reader.vTableGetNullable(_bc, _bcOffset, 4); @override String toString() { return 'Foo{myFoo: ${myFoo}}'; } - FooT unpack() => FooT(myFoo: myFoo?.unpack()); + FooT unpack() => FooT( + myFoo: myFoo?.unpack()); static int pack(fb.Builder fbBuilder, FooT? object) { if (object == null) return 0; @@ -36,7 +37,8 @@ class Foo { class FooT implements fb.Packable { FooPropertiesT? myFoo; - FooT({this.myFoo}); + FooT({ + this.myFoo}); @override int pack(fb.Builder fbBuilder) { @@ -57,7 +59,8 @@ class _FooReader extends fb.TableReader { const _FooReader(); @override - Foo createObject(fb.BufferContext bc, int offset) => Foo._(bc, offset); + Foo createObject(fb.BufferContext bc, int offset) => + Foo._(bc, offset); } class FooBuilder { @@ -82,7 +85,10 @@ class FooBuilder { class FooObjectBuilder extends fb.ObjectBuilder { final FooPropertiesObjectBuilder? _myFoo; - FooObjectBuilder({FooPropertiesObjectBuilder? myFoo}) : _myFoo = myFoo; + FooObjectBuilder({ + FooPropertiesObjectBuilder? myFoo, + }) + : _myFoo = myFoo; /// Finish building, and store into the [fbBuilder]. @override @@ -102,7 +108,6 @@ class FooObjectBuilder extends fb.ObjectBuilder { return fbBuilder.buffer; } } - class FooProperties { FooProperties._(this._bc, this._bcOffset); @@ -119,7 +124,9 @@ class FooProperties { return 'FooProperties{a: ${a}, b: ${b}}'; } - FooPropertiesT unpack() => FooPropertiesT(a: a, b: b); + FooPropertiesT unpack() => FooPropertiesT( + a: a, + b: b); static int pack(fb.Builder fbBuilder, FooPropertiesT? object) { if (object == null) return 0; @@ -131,10 +138,13 @@ class FooPropertiesT implements fb.Packable { bool a; bool b; - FooPropertiesT({required this.a, required this.b}); + FooPropertiesT({ + required this.a, + required this.b}); @override int pack(fb.Builder fbBuilder) { + fbBuilder.prepStruct(1, 2); fbBuilder.putBool(b); fbBuilder.putBool(a); return fbBuilder.offset; @@ -154,7 +164,7 @@ class _FooPropertiesReader extends fb.StructReader { @override FooProperties createObject(fb.BufferContext bc, int offset) => - FooProperties._(bc, offset); + FooProperties._(bc, offset); } class FooPropertiesBuilder { @@ -163,23 +173,29 @@ class FooPropertiesBuilder { final fb.Builder fbBuilder; int finish(bool a, bool b) { + fbBuilder.prepStruct(1, 2); fbBuilder.putBool(b); fbBuilder.putBool(a); return fbBuilder.offset; } + } class FooPropertiesObjectBuilder extends fb.ObjectBuilder { final bool _a; final bool _b; - FooPropertiesObjectBuilder({required bool a, required bool b}) - : _a = a, - _b = b; + FooPropertiesObjectBuilder({ + required bool a, + required bool b, + }) + : _a = a, + _b = b; /// Finish building, and store into the [fbBuilder]. @override int finish(fb.Builder fbBuilder) { + fbBuilder.prepStruct(1, 2); fbBuilder.putBool(_b); fbBuilder.putBool(_a); return fbBuilder.offset; @@ -193,3 +209,222 @@ class FooPropertiesObjectBuilder extends fb.ObjectBuilder { return fbBuilder.buffer; } } +class Header { + Header._(this._bc, this._bcOffset); + + static const fb.Reader
reader = _HeaderReader(); + + final fb.BufferContext _bc; + final int _bcOffset; + + int get time => const fb.Uint64Reader().read(_bc, _bcOffset + 0); + int get ident => const fb.Uint16Reader().read(_bc, _bcOffset + 8); + + @override + String toString() { + return 'Header{time: ${time}, ident: ${ident}}'; + } + + HeaderT unpack() => HeaderT( + time: time, + ident: ident); + + static int pack(fb.Builder fbBuilder, HeaderT? object) { + if (object == null) return 0; + return object.pack(fbBuilder); + } +} + +class HeaderT implements fb.Packable { + int time; + int ident; + + HeaderT({ + required this.time, + required this.ident}); + + @override + int pack(fb.Builder fbBuilder) { + fbBuilder.prepStruct(8, 16); + fbBuilder.pad(6); + fbBuilder.putUint16(ident); + fbBuilder.putUint64(time); + return fbBuilder.offset; + } + + @override + String toString() { + return 'HeaderT{time: ${time}, ident: ${ident}}'; + } +} + +class _HeaderReader extends fb.StructReader
{ + const _HeaderReader(); + + @override + int get size => 16; + + @override + Header createObject(fb.BufferContext bc, int offset) => + Header._(bc, offset); +} + +class HeaderBuilder { + HeaderBuilder(this.fbBuilder); + + final fb.Builder fbBuilder; + + int finish(int time, int ident) { + fbBuilder.prepStruct(8, 16); + fbBuilder.pad(6); + fbBuilder.putUint16(ident); + fbBuilder.putUint64(time); + return fbBuilder.offset; + } + +} + +class HeaderObjectBuilder extends fb.ObjectBuilder { + final int _time; + final int _ident; + + HeaderObjectBuilder({ + required int time, + required int ident, + }) + : _time = time, + _ident = ident; + + /// Finish building, and store into the [fbBuilder]. + @override + int finish(fb.Builder fbBuilder) { + fbBuilder.prepStruct(8, 16); + fbBuilder.pad(6); + fbBuilder.putUint16(_ident); + fbBuilder.putUint64(_time); + return fbBuilder.offset; + } + + /// Convenience method to serialize to byte list. + @override + Uint8List toBytes([String? fileIdentifier]) { + final fbBuilder = fb.Builder(deduplicateTables: false); + fbBuilder.finish(finish(fbBuilder), fileIdentifier); + return fbBuilder.buffer; + } +} +class Message { + Message._(this._bc, this._bcOffset); + factory Message(List bytes) { + final rootRef = fb.BufferContext.fromBytes(bytes); + return reader.read(rootRef, 0); + } + + static const fb.Reader reader = _MessageReader(); + + final fb.BufferContext _bc; + final int _bcOffset; + + int get prefix => const fb.Uint16Reader().vTableGet(_bc, _bcOffset, 4, 0); + Header? get header => Header.reader.vTableGetNullable(_bc, _bcOffset, 6); + + @override + String toString() { + return 'Message{prefix: ${prefix}, header: ${header}}'; + } + + MessageT unpack() => MessageT( + prefix: prefix, + header: header?.unpack()); + + static int pack(fb.Builder fbBuilder, MessageT? object) { + if (object == null) return 0; + return object.pack(fbBuilder); + } +} + +class MessageT implements fb.Packable { + int prefix; + HeaderT? header; + + MessageT({ + this.prefix = 0, + this.header}); + + @override + int pack(fb.Builder fbBuilder) { + fbBuilder.startTable(2); + fbBuilder.addUint16(0, prefix); + if (header != null) { + fbBuilder.addStruct(1, header!.pack(fbBuilder)); + } + return fbBuilder.endTable(); + } + + @override + String toString() { + return 'MessageT{prefix: ${prefix}, header: ${header}}'; + } +} + +class _MessageReader extends fb.TableReader { + const _MessageReader(); + + @override + Message createObject(fb.BufferContext bc, int offset) => + Message._(bc, offset); +} + +class MessageBuilder { + MessageBuilder(this.fbBuilder); + + final fb.Builder fbBuilder; + + void begin() { + fbBuilder.startTable(2); + } + + int addPrefix(int? prefix) { + fbBuilder.addUint16(0, prefix); + return fbBuilder.offset; + } + int addHeader(int offset) { + fbBuilder.addStruct(1, offset); + return fbBuilder.offset; + } + + int finish() { + return fbBuilder.endTable(); + } +} + +class MessageObjectBuilder extends fb.ObjectBuilder { + final int? _prefix; + final HeaderObjectBuilder? _header; + + MessageObjectBuilder({ + int? prefix, + HeaderObjectBuilder? header, + }) + : _prefix = prefix, + _header = header; + + /// Finish building, and store into the [fbBuilder]. + @override + int finish(fb.Builder fbBuilder) { + fbBuilder.startTable(2); + fbBuilder.addUint16(0, _prefix); + if (_header != null) { + fbBuilder.addStruct(1, _header!.finish(fbBuilder)); + } + return fbBuilder.endTable(); + } + + /// Convenience method to serialize to byte list. + @override + Uint8List toBytes([String? fileIdentifier]) { + final fbBuilder = fb.Builder(deduplicateTables: false); + fbBuilder.finish(finish(fbBuilder), fileIdentifier); + return fbBuilder.buffer; + } +} diff --git a/dart/test/flat_buffers_test.dart b/dart/test/flat_buffers_test.dart index c4a146f8db..354baeec50 100644 --- a/dart/test/flat_buffers_test.dart +++ b/dart/test/flat_buffers_test.dart @@ -219,6 +219,17 @@ class BuilderTest { expect(mon3.pos!.test1, 3.0); } + void test_objectBuilder_alignsInlineStruct() { + final bytes = example4.MessageObjectBuilder( + prefix: 0x55aa, + header: example4.HeaderObjectBuilder(time: 123, ident: 0x1234), + ).toBytes(); + final decoded = example4.Message(bytes); + + expect(decoded.header!.time, 123); + expect(decoded.header!.ident, 0x1234); + } + void test_error_addInt32_withoutStartTable([Builder? builder]) { builder ??= Builder(); expect(() { diff --git a/src/idl_gen_dart.cpp b/src/idl_gen_dart.cpp index 5b90708525..32bdccdfda 100644 --- a/src/idl_gen_dart.cpp +++ b/src/idl_gen_dart.cpp @@ -804,7 +804,7 @@ class DartGenerator : public BaseGenerator { } code += " @override\n"; code += " " + struct_type + - " createObject(fb.BufferContext bc, int offset) => \n " + + " createObject(fb.BufferContext bc, int offset) =>\n " + struct_type + "._(bc, offset);\n"; code += "}\n\n"; } @@ -852,6 +852,8 @@ class DartGenerator : public BaseGenerator { } } code += ") {\n"; + code += " fbBuilder.prepStruct(" + NumToString(struct_def.minalign) + + ", " + NumToString(struct_def.bytesize) + ");\n"; for (auto it = non_deprecated_fields.rbegin(); it != non_deprecated_fields.rend(); ++it) { @@ -1053,8 +1055,8 @@ class DartGenerator : public BaseGenerator { } if (struct_def.fixed) { - code += StructObjectBuilderBody(non_deprecated_fields, prependUnderscore, - pack); + code += StructObjectBuilderBody(struct_def, non_deprecated_fields, + prependUnderscore, pack); } else { code += TableObjectBuilderBody(struct_def, non_deprecated_fields, prependUnderscore, pack); @@ -1063,9 +1065,12 @@ class DartGenerator : public BaseGenerator { } std::string StructObjectBuilderBody( + const StructDef& struct_def, const std::vector>& non_deprecated_fields, bool prependUnderscore = true, bool pack = false) { std::string code; + code += " fbBuilder.prepStruct(" + NumToString(struct_def.minalign) + + ", " + NumToString(struct_def.bytesize) + ");\n"; for (auto it = non_deprecated_fields.rbegin(); it != non_deprecated_fields.rend(); ++it) {