Skip to content

[Dart] Struct builders do not prepare full inline struct alignment/size before writing fields #9099

@chittti

Description

@chittti

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:

  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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions