Skip to content

[Rust] Generate struct accessors without raw pointer operations (fixes enum arrays on big-endian) - #9472

Open
akhmittra wants to merge 1 commit into
google:masterfrom
akhmittra:rust-safe-struct-accessors
Open

akhmittra wants to merge 1 commit into
google:masterfrom
akhmittra:rust-safe-struct-accessors

Conversation

@akhmittra

Copy link
Copy Markdown

What

flatc --rust generated struct accessors using raw pointer operations
(ptr::copy_nonoverlapping + MaybeUninit::assume_init, ptr::copy with .add(),
slice::from_raw_parts, and &*(ptr as *const T) casts). This code is compiled into
every downstream crate that uses a schema with structs. It was in bounds only because
the C++ generator computes offsets and sizes correctly; nothing at the Rust level checked it.

This PR generates struct accessors as bounds-checked slices of the struct's [u8; N] storage:

Accessor Before After
scalar/enum getter copy_nonoverlapping + assume_init [u8; SIZE] + Scalar::from_ne_bytes
scalar/enum setter copy_nonoverlapping self.0[OFF..OFF+SIZE].copy_from_slice(&x_le.to_ne_bytes())
scalar array setter emplace_scalar_array chunks_exact_mut + per-element to_little_endian
enum/struct array setter ptr::copy(.., self.0.as_mut_ptr().add(off), size) chunks_exact_mut + copy_from_slice
nested struct getter &*(self.0[off..].as_ptr() as *const T) follow_cast_ref (bounds-checked)
Push slice::from_raw_parts(self as *const _ ..) dst.copy_from_slice(&self.0)

A wrong size is now a type error and a wrong offset a panic, never an out-of-bounds access.
There is no runtime API change. The generated code uses only core APIs available on MSRV 1.51,
so it also works with already-published runtime versions. It complements #9304 (runtime scalar
access) without overlapping it.

Bug fix: enum arrays on big-endian

Arrays of enums went through the raw ptr::copy path, writing the enum's host-endian
representation while the getter decodes little-endian. On big-endian targets, [E:N] with
enum E : short (or any enum wider than one byte) was written byte-swapped, and values read back
were wrong. Existing schemas only use one-byte enums in arrays, so CI didn't catch it.

With the new test schema on s390x (qemu-user), code from the current generator writes
[1, 2, 255, 254, 0, 1] instead of [2, 1, 254, 255, 1, 0] for [WideEnum::B, C, A], and the
Object API round trip returns [<UNKNOWN 513>, <UNKNOWN -257>, <UNKNOWN 256>] instead of
[B, C, A]. With this PR all 7 new tests pass on s390x.

Tests

  • New tests/rust_struct_accessors_test.fbs covers short and ulong enum arrays, struct arrays,
    bool arrays, nested structs, f32/f64 and padding.
  • New tests/rust_usage_test/tests/struct_accessors_test.rs (7 tests) checks the exact
    little-endian wire bytes against a hand-written layout, that each setter writes only its own
    bytes, round trips through the builder, verifier and Object API, and includes a guard against
    raw pointer ops reappearing in generated struct code.
  • x86_64: rust_usage_test 323/323 pass with and without --no-default-features. Alloc checks,
    serde test and cargo clippy are clean.
  • s390x (big-endian, qemu-user, -Zbuild-std): full rust_usage_test 322/322 pass
    (verify_struct_array_alignment is little-endian only). On current master the new tests fail
    5/7, as described above.
  • thumbv7m-none-eabi: rust_no_std_compilation_test builds, and the generated struct modules
    compile for it.
  • Benchmarks (cargo bench --bench benchmarks): traverse_canonical_buffer 58 → 54 ns/iter;
    builder benchmarks unchanged within noise.

All *_generated.rs changes come from scripts/generate_code.py with the patched flatc
(regenerating produces no further diff; regenerating with the unpatched flatc reproduces master).

🤖 Generated with Claude Code

@github-actions github-actions Bot added python c++ rust codegen Involving generating code from schema labels Nov 9, 2026
@google-cla

google-cla Bot commented Nov 9, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Struct accessors generated by `flatc --rust` used ptr::copy_nonoverlapping,
MaybeUninit::assume_init, ptr::copy with pointer arithmetic, slice::from_raw_parts
and raw pointer casts. They were in bounds only because the generator computes
offsets correctly; nothing at the Rust level enforced it.

Generate them as bounds-checked slices of the struct's [u8; N] storage instead:
scalars use {from,to}_ne_bytes on the type's EndianScalar::Scalar, array setters
convert element by element, Push copies self.0, and nested struct getters use the
bounds-checked follow_cast_ref. A wrong size is now a type error and a wrong offset
a panic, never an out-of-bounds access. No runtime API change.

This also fixes arrays of enums wider than one byte on big-endian hosts: they were
copied in host byte order instead of little-endian, so the wire bytes were swapped
and values read back through the Object API were wrong.

Adds tests/rust_struct_accessors_test.fbs and struct_accessors_test.rs, which check
exact wire bytes (so they catch endianness bugs on the MIPS CI job) and guard
against raw pointer operations reappearing in generated struct code.
@akhmittra
akhmittra force-pushed the rust-safe-struct-accessors branch from cce0cd7 to 4cbe943 Compare November 9, 2026 11:04

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ codegen Involving generating code from schema python rust

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant