From 5eb190f00005b8dd1aa4f7ab2db9f01a39e3c76d Mon Sep 17 00:00:00 2001 From: Artyom Palvelev <15948033+artyompal@users.noreply.github.com> Date: Fri, 6 Nov 2026 15:15:33 -0800 Subject: [PATCH] rust: fix 64-bit scalar wire-format alignment on 32-bit targets in Push::alignment (#9465) 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::()) } ``` And `impl_push_for_endian_scalar!` did not override `Push::alignment()`, inheriting `core::mem::align_of::()` 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::() == 8`, so `align_of::()` happens to equal `size_of::()`. 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::() == 4`), while their FlatBuffer wire-format size and required wire alignment is **8 bytes** (`size_of::() == 8`). As a consequence, on 32-bit x86 (`i686`): 1. `FlatBufferBuilder::push::` 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` 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. --- rust/flatbuffers/src/push.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/rust/flatbuffers/src/push.rs b/rust/flatbuffers/src/push.rs index 20bacd8e2..59d16bea6 100644 --- a/rust/flatbuffers/src/push.rs +++ b/rust/flatbuffers/src/push.rs @@ -83,6 +83,11 @@ macro_rules! impl_push_for_endian_scalar { unsafe fn push(&self, dst: &mut [u8], _written_len: usize) { emplace_scalar::<$ty>(dst, *self); } + + #[inline] + fn alignment() -> PushAlignment { + PushAlignment::new(size_of::<$ty>()) + } } }; } @@ -98,3 +103,23 @@ impl_push_for_endian_scalar!(u64); impl_push_for_endian_scalar!(i64); impl_push_for_endian_scalar!(f32); impl_push_for_endian_scalar!(f64); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn endian_scalar_push_alignment_matches_wire_size() { + assert_eq!(::alignment().value(), 1); + assert_eq!(::alignment().value(), 1); + assert_eq!(::alignment().value(), 1); + assert_eq!(::alignment().value(), 2); + assert_eq!(::alignment().value(), 2); + assert_eq!(::alignment().value(), 4); + assert_eq!(::alignment().value(), 4); + assert_eq!(::alignment().value(), 4); + assert_eq!(::alignment().value(), 8); + assert_eq!(::alignment().value(), 8); + assert_eq!(::alignment().value(), 8); + } +}