Skip to content

rust: fix 64-bit scalar alignment on 32-bit targets in Push::alignment - #9465

Merged
dbaileychess merged 1 commit into
google:masterfrom
artyompal:fix-rust-scalar-push-alignment
Nov 6, 2026
Merged

dbaileychess merged 1 commit into
google:masterfrom
artyompal:fix-rust-scalar-push-alignment

Conversation

@artyompal

@artyompal artyompal commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

In the FlatBuffers binary wire format specification, every scalar field is aligned to its own byte size (1, 2, 4, or 8 bytes), regardless of the host CPU architecture. Both the flatc schema compiler (when computing table max_align and struct field offsets) and the C++ FlatBufferBuilder::Align / PushElement implementation align 64-bit scalars (u64, i64, f64) to 8 bytes on all architectures.

However, in the Rust flatbuffers crate (rust/flatbuffers/src/push.rs), the Push trait provides a default implementation:

fn alignment() -> PushAlignment {
    PushAlignment::new(align_of::<Self::Output>())
}

And impl_push_for_endian_scalar! did not override Push::alignment(), inheriting core::mem::align_of::<T>() for all primitive scalar types (bool, u8, i8, u16, i16, u32, i32, u64, i64, f32, f64).

On 64-bit targets (x86_64, aarch64) and 32-bit ARM (armv7), align_of::<u64>() == 8, so align_of::<T>() happens to equal size_of::<T>(). On 32-bit x86 (i686, System V i386 ABI), however, the host ABI alignment of 64-bit scalars (u64, i64, f64) is 4 bytes (align_of::<u64>() == 4), while their FlatBuffer wire-format size and required wire alignment is 8 bytes (size_of::<u64>() == 8).

As a consequence, on 32-bit x86 (i686):

  1. FlatBufferBuilder::push::<u64 / i64 / f64> only aligns the builder's cursor and tracks min_align to 4 bytes instead of 8 bytes when writing 64-bit table fields or vectors of 64-bit scalars.
  2. When a table containing a 64-bit scalar field (uint64, int64, double) is serialized on i686, the field can be placed at a buffer offset that is 4-byte aligned but not 8-byte aligned.
  3. When the resulting FlatBuffer is verified by C++ flatbuffers::Verifier (which checks VerifyField<uint64_t> with sizeof(uint64_t) == 8 alignment when FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE / alignment checks are active) or transferred to a 64-bit reader, verification fails or unaligned 64-bit reads occur.

Fix impl_push_for_endian_scalar! to explicitly implement Push::alignment() using size_of::<$ty>() so that all endian scalar types always use their wire-format byte size as their FlatBuffer alignment across all target architectures.

…sh::alignment

In the FlatBuffers binary wire format specification, every scalar field is
aligned to its own byte size (`1`, `2`, `4`, or `8` bytes), regardless of the
host CPU architecture. Both the `flatc` schema compiler (when computing table
`max_align` and struct field offsets) and the C++ `FlatBufferBuilder::Align` /
`PushElement` implementation align 64-bit scalars (`u64`, `i64`, `f64`) to 8
bytes on all architectures.

However, in the Rust `flatbuffers` crate (`rust/flatbuffers/src/push.rs`), the
`Push` trait provides a default implementation:

```rust
fn alignment() -> PushAlignment {
    PushAlignment::new(align_of::<Self::Output>())
}
```

And `impl_push_for_endian_scalar!` did not override `Push::alignment()`,
inheriting `core::mem::align_of::<T>()` for all primitive scalar types (`bool`,
`u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `f32`, `f64`).

On 64-bit targets (`x86_64`, `aarch64`) and 32-bit ARM (`armv7`),
`align_of::<u64>() == 8`, so `align_of::<T>()` happens to equal `size_of::<T>()`.
On 32-bit x86 (`i686`, System V i386 ABI), however, the host ABI alignment of
64-bit scalars (`u64`, `i64`, `f64`) is **4 bytes** (`align_of::<u64>() == 4`),
while their FlatBuffer wire-format size and required wire alignment is **8
bytes** (`size_of::<u64>() == 8`).

As a consequence, on 32-bit x86 (`i686`):
1. `FlatBufferBuilder::push::<u64 / i64 / f64>` only aligns the builder's cursor
   and tracks `min_align` to 4 bytes instead of 8 bytes when writing 64-bit
   table fields or vectors of 64-bit scalars.
2. When a table containing a 64-bit scalar field (`uint64`, `int64`, `double`)
   is serialized on `i686`, the field can be placed at a buffer offset that is
   4-byte aligned but not 8-byte aligned.
3. When the resulting FlatBuffer is verified by C++ `flatbuffers::Verifier`
   (which checks `VerifyField<uint64_t>` with `sizeof(uint64_t) == 8` alignment
   when `FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE` / alignment checks are active)
   or transferred to a 64-bit reader, verification fails or unaligned 64-bit
   reads occur.

Fix `impl_push_for_endian_scalar!` to explicitly implement `Push::alignment()`
using `size_of::<$ty>()` so that all endian scalar types always use their
wire-format byte size as their FlatBuffer alignment across all target
architectures.
@artyompal artyompal changed the title rust: fix 64-bit scalar wire-format alignment on 32-bit targets in Pu… rust: fix 64-bit scalar alignment on 32-bit targets in Push::alignment Nov 5, 2026
@github-actions github-actions Bot added the rust label Nov 5, 2026
@dbaileychess
dbaileychess merged commit 5eb190f into google:master Nov 6, 2026
51 of 55 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants