[Dart] Struct builders do not prepare full inline struct alignment/size before writing fields
- Dominant language
- C++
- Stars
- 26.5k
- Forks
- 3.7k
- PR merge metrics
- No merged PRs in 30d
Description
## 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
```fbs
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:
```dart
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:
```dart
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:
```dart
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:
```dart
int finish(int time, int ident) {
fbBuilder.prepStruct(8, 16);
fbBuilder.pad(6);
fbBuilder.putUint16(ident);
fbBuilder.putUint64(time);
return fbBuilder.offset;
}
```
Possible fix:
1. Add or expose a Dart builder API equivalent to Prep(alignment, size).
2. Update `src/idl_gen_dart.cpp` so generated struct builders call it before writing struct fields.
Contributor guide
Assessment
This issue has not been assessed yet.