Summary
Dart generated builders for structs appear not to prepare the struct's full alignment and size before writing fields. This can incorrectly serialize inline structs with 8-byte alignment and trailing padding when the builder is not already aligned for that struct.
I can reproduce this with:
- flatc version
25.9.23
- Dart package
flat_buffers: 25.9.23
This looks related to #6688, which also reported broken Dart struct serialization and suspected missing struct preparation/alignment. However, #6688 was closed by #8290, which appears to have fixed a separate putFloat64 write bug tracked in #8070. This repro uses only ulong and ushort, so it does not depend on float64 encoding.
Minimal schema
namespace Repro;
struct Header {
time: ulong;
ident: ushort;
}
table Message {
prefix: ushort;
header: Header;
}
root_type Message;
Expected Header layout:
- max alignment: 8
- size: 16
- time at offset 0
- ident at offset 8
- 6 bytes trailing padding
The prefix field is included to make the builder offset non-8-aligned before the inline struct is written.
Minimal repro steps:
flatc --dart repro.fbs
Then run:
import 'repro_generated.dart';
void main() {
final bytes = MessageObjectBuilder(
prefix: 0x55aa,
header: HeaderObjectBuilder(
time: 123,
ident: 0x1234,
),
).toBytes();
final decoded = Message.fromBytes(bytes);
if (decoded.header?.time != 123) {
throw StateError('Expected time 123, got ${decoded.header?.time}');
}
if (decoded.header?.ident != 0x1234) {
throw StateError(
'Expected ident 0x1234, got 0x${decoded.header?.ident.toRadixString(16)}',
);
}
}
Observed generated code
The generated reader expects the correct struct layout:
int get time => const fb.Uint64Reader().read(_bc, _bcOffset + 0);
int get ident => const fb.Uint16Reader().read(_bc, _bcOffset + 8);
@override
int get size => 16;
But the generated builder writes the fields without first preparing the struct as one 8-byte-aligned, 16-byte inline object:
int finish(int time, int ident) {
fbBuilder.pad(6);
fbBuilder.putUint16(ident);
fbBuilder.putUint64(time);
return fbBuilder.offset;
}
Because the builder writes backwards, this happens to work when the builder is already aligned correctly before the struct is written. However, when another field has already advanced the builder offset, putUint64(time) may perform its own 8-byte alignment after ident and the trailing padding have already been written. That inserts padding in the wrong place relative to the struct layout.
The result is that the generated reader and generated builder disagree about where the struct fields are located.
Expected behavior
Generated Dart struct builders should add the pre-struct alignment padding based on the struct’s full alignment and byte size before writing the struct fields, equivalent to other runtimes’ Prep(alignment, size) behavior.
For this schema, the generated builder should be shaped conceptually like this:
int finish(int time, int ident) {
fbBuilder.prepStruct(8, 16);
fbBuilder.pad(6);
fbBuilder.putUint16(ident);
fbBuilder.putUint64(time);
return fbBuilder.offset;
}
Possible fix:
- Add or expose a Dart builder API equivalent to Prep(alignment, size).
- Update
src/idl_gen_dart.cpp so generated struct builders call it before writing struct fields.
Summary
Dart generated builders for structs appear not to prepare the struct's full alignment and size before writing fields. This can incorrectly serialize inline structs with 8-byte alignment and trailing padding when the builder is not already aligned for that struct.
I can reproduce this with:
25.9.23flat_buffers: 25.9.23This looks related to #6688, which also reported broken Dart struct serialization and suspected missing struct preparation/alignment. However, #6688 was closed by #8290, which appears to have fixed a separate
putFloat64write bug tracked in #8070. This repro uses onlyulongandushort, so it does not depend on float64 encoding.Minimal schema
Expected Header layout:
The prefix field is included to make the builder offset non-8-aligned before the inline struct is written.
Minimal repro steps:
flatc --dart repro.fbsThen run:
Observed generated code
The generated reader expects the correct struct layout:
But the generated builder writes the fields without first preparing the struct as one 8-byte-aligned, 16-byte inline object:
Because the builder writes backwards, this happens to work when the builder is already aligned correctly before the struct is written. However, when another field has already advanced the builder offset, putUint64(time) may perform its own 8-byte alignment after ident and the trailing padding have already been written. That inserts padding in the wrong place relative to the struct layout.
The result is that the generated reader and generated builder disagree about where the struct fields are located.
Expected behavior
Generated Dart struct builders should add the pre-struct alignment padding based on the struct’s full alignment and byte size before writing the struct fields, equivalent to other runtimes’ Prep(alignment, size) behavior.
For this schema, the generated builder should be shaped conceptually like this:
Possible fix:
src/idl_gen_dart.cppso generated struct builders call it before writing struct fields.