Repository navigation
rust: fix 64-bit scalar alignment on 32-bit targets in Push::alignment - #9465
Merged
dbaileychess merged 1 commit intoNov 6, 2026
Merged
Conversation
…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.
dbaileychess
approved these changes
Nov 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In the FlatBuffers binary wire format specification, every scalar field is aligned to its own byte size (
1,2,4, or8bytes), regardless of the host CPU architecture. Both theflatcschema compiler (when computing tablemax_alignand struct field offsets) and the C++FlatBufferBuilder::Align/PushElementimplementation align 64-bit scalars (u64,i64,f64) to 8 bytes on all architectures.However, in the Rust
flatbufferscrate (rust/flatbuffers/src/push.rs), thePushtrait provides a default implementation:And
impl_push_for_endian_scalar!did not overridePush::alignment(), inheritingcore::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, soalign_of::<T>()happens to equalsize_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):FlatBufferBuilder::push::<u64 / i64 / f64>only aligns the builder's cursor and tracksmin_alignto 4 bytes instead of 8 bytes when writing 64-bit table fields or vectors of 64-bit scalars.uint64,int64,double) is serialized oni686, the field can be placed at a buffer offset that is 4-byte aligned but not 8-byte aligned.flatbuffers::Verifier(which checksVerifyField<uint64_t>withsizeof(uint64_t) == 8alignment whenFLATBUFFERS_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 implementPush::alignment()usingsize_of::<$ty>()so that all endian scalar types always use their wire-format byte size as their FlatBuffer alignment across all target architectures.