Skip to content

[Rust] Improve Object API codegen: idiomatic types, Eq/Hash derives, String shadowing fix#8931

Open
pittengermdp wants to merge 2682 commits intogoogle:masterfrom
pittengermdp:rust-object-api-improvements
Open

[Rust] Improve Object API codegen: idiomatic types, Eq/Hash derives, String shadowing fix#8931
pittengermdp wants to merge 2682 commits intogoogle:masterfrom
pittengermdp:rust-object-api-improvements

Conversation

@pittengermdp
Copy link
Copy Markdown

Summary

  • Use idiomatic short type names (String, Vec, Box) instead of fully-qualified alloc:: paths in generated Rust Object API code, with use alloc::* imports added for no_std compatibility
  • Derive Eq and Hash on Object API types (tables, structs, unions, bitflags) when no floating-point fields are present in the type hierarchy
  • Fix String type shadowing: when a schema defines a table named String, generated code now emits std::string::String for stdlib string fields (wires up existing unused NamespaceShadowsString/ObjectStringType helpers)
  • Add #[allow(unused_imports)] to suppress warnings on generated imports
  • Fix serde Deserialize impl for enums to use fully-qualified std::string::String

Test plan

  • C++ tests pass (flattests)
  • Rust tests pass (usage, no_std, serde, reflection, outdir, clippy)
  • Go tests pass (TestAll, TestTextParsing, benchmarks)
  • Python tests pass (multi-file, one-file, flexbuffers)

dbaileychess and others added 30 commits January 9, 2025 20:52
* Mention uint8 as an alias to ubyte

as it's referenced in the note below

* Remove an unfinished sentence.
* #Rust Create a crate for reflection

* #Rust Add a crate for reflection tests and helper to access schema

* #Rust Get root table of a buffer and access field with schema

* #Rust Add 'Struct' struct and corresponding getter

* #Rust Add functions of getting any table/struct field value as integer/float/string

* #Rust Add setters for scalar fields

* #Rust Add setter for string fields

* #Rust Add getter for Table/Vector fields

* #Rust Add buffer verification

* Add a 'SafeBuffer' struct which provides safe methods for reflection

It verifies buffer against schema during construction and provides all the unsafe getters in lib.rs in a safe way

---------

Co-authored-by: Derek Bailey <derekbailey@google.com>
Transitive headers like array.h have not been available in the runtime_cc target causing the build to fail. Adding all public headers to make sure transitive headers of flatbuffers.h are available.
This makes sure it doesn't break users when they choose a different repo_name.
@github-actions github-actions bot added kotlin CI Continuous Integration nim labels Mar 18, 2026
…API, union-of-unions

Phase 0 — Rust reflection verifier hardening:
- Replace vec![false; buffer.len()] DoS vector with HashSet<usize> capped at max_tables
- Make verify_with_options return HashMap instead of taking &mut ref
- Add Array and Vector64 base type support to reflection verifier

Phase 1a — Go and TypeScript verifier runtimes:
- go/verifier.go: full verifier with CheckTable, CheckString, CheckVector, depth/table limits
- ts/verifier.ts: full verifier with DataView-based reads, TextDecoder UTF-8 validation
- idl_gen_go.cpp: generate VerifyRootAs*/verify* per table with cross-namespace support
- idl_gen_ts.cpp: generate verify*/verifyRootAs* per table with cross-file imports

Phase 1b — Object API improvements:
- Go: omitempty JSON struct tags on *T types
- TypeScript: discriminated union types, pack() required field validation, clone(), equals()
- Rust: fix serde derives on struct *T types, add union discriminant debug_assert in pack()

Phase 2 — Reflection runtimes:
- go/reflect.go: LoadReflectionSchema, ObjectByName, FieldByName, GetField{String,Int,Float}
- ts/reflection.ts: standalone module with loadSchema, getField* functions

Phase 3 — Conformance and testing:
- tests/conformance/: cross-language corpus with Rust/Go/TS runners and compare.py
- tests/codegen_golden/: golden-file codegen tests with check_golden.sh
- Rust fuzz target for reflection verifier
- Go fuzz target and benchmarks for verifier

Union-of-unions:
- idl_parser.cpp: allow union members that are other unions via compile-time flattening
- Inner union members expanded into outer union discriminant numbering
- tests/union_of_union_test/: Go round-trip tests for flattened unions

Version bump to 25.12.20 across all generated headers.
Comprehensive API documentation for downstream consumers:
- VTS (Rust): rustdoc on SafeBuffer, verify_with_options, all pub functions in lib.rs
- LiftCloud (Go): godoc on Verifier, ReflectionSchema, all GetField* accessors
- liftNet_frontend (TypeScript): TSDoc on Verifier class, ReflectionSchema, all exports

Includes usage examples, error handling patterns, parameter descriptions,
and cross-references between related types. No code logic changes.
Previously --bfbs-gen-embed only worked for C++. Now all three LN2 languages
can embed binary schemas directly in generated code for runtime reflection
without external file I/O.

Rust: generates {name}_bfbs_generated.rs with SCREAMING_SNAKE_CASE const
  pub const MONSTER_BFBS: &[u8] = &[...];
  pub fn monster_bfbs() -> &'static [u8] { MONSTER_BFBS }

Go: generates {Type}BinarySchema.go with exported var and accessor
  var MonsterBfbsData = []byte{...}
  func MonsterBinarySchemaBytes() []byte { return MonsterBfbsData }

TypeScript: generates {name}-bfbs.ts with exported Uint8Array
  export const bfbsData: Uint8Array = new Uint8Array([...]);

All three use BufferToHexText from the serialized parser builder data.
Existing C++ --bfbs-gen-embed codegen is unchanged.
…eatures

Phase 1: Union Match (Go), optional scalars (Go), self-describing envelope
Phase 2: Selective unpack, reflection Pack/Unpack from JSON, buffer diffing
Phase 3: Chunked builder allocator (Rust ARM, gated on profiling)

Council: Systems Architect, Go Lead, Rust Lead, TS Lead, Security Engineer
Key decisions: envelope pattern (not wire format), SHA-256, transport-enforced
Runtime reflection needed for CDO version detection, generic Pack/Unpack,
structured logging, legacy format detection, and protocol negotiation.
Schema leakage mitigated by access control, not stripping.
Rust: 8 doctests in reflection crate
- FlatbufferError: fully runnable, tests Display + pattern matching
- SafeBuffer::new, get_root, get_field_integer, get_field_string,
  get_field_table, get_field_float: compile-checked no_run examples
  showing LN2-style field access patterns

Go: 7 Example functions in go/example_test.go
- NewVerifier, Verifier_CheckTable, VerificationError, CheckAlignment,
  PushDepth: self-contained with hand-crafted buffers, output-verified
- LoadReflectionSchema, GetFieldString: use monster_test.bfbs and
  monsterdata_go_wire.mon test fixtures, output-verified

All examples serve as both documentation and regression tests.
Feature 1.1 — Union Match (Go):
  Generated Match() method on Go union *T types with typed callbacks.
  One callback per variant, type-safe dispatch, nil-safe.
  Files: src/idl_gen_go.cpp (GenUnionMatch)

Feature 1.2 — Optional Scalars (Go):
  Already implemented upstream — verified *int32/*float64 pointer accessors
  exist for fields marked optional in the schema.

Feature 1.3 — Self-Describing Envelope:
  go/envelope.go: EnvelopeWrap/Unwrap with SHA-256 schema fingerprint
  ts/envelope.ts: Same API using Web Crypto SubtleCrypto
  Format: "LN2E" magic + version + algo + 32-byte hash + payload

Feature 2.1 — Selective Unpack:
  Go: UnpackFields(...string) on generated table types (idl_gen_go.cpp)
  TS: unpackFields(...fields: string[]) on generated classes (idl_gen_ts.cpp)
  Only materializes named fields, skips rest.

Feature 2.2 — Reflection Pack/Unpack from JSON:
  go/reflect_json.go: ReflectUnpack (FlatBuffer → map[string]any)
                      ReflectPack (JSON → FlatBuffer)
  ts/reflection-json.ts: reflectUnpack/reflectPack
  Recursive schema walk handling all base types including unions.

Feature 2.3 — Buffer Diffing:
  go/reflect_diff.go: DiffBuffers returns []FieldDelta with path/kind/values
  ts/reflection-diff.ts: diffBuffers returns FieldDelta[]
  Recursive field comparison including nested tables and vectors.

Feature 3.1 — Chunked Builder (Rust proof-of-concept):
  rust/reflection/src/chunked_builder.rs: segmented allocator for ARM
  8 unit tests passing. Documented as PoC pending ARM profiling.

Version bumped to 25.12.22 across all generated headers.
…omprehensive tests

Reflection API now covers 100% of reflection.fbs tables and fields:

New types (Go + TS):
- ReflectionEnum: name, is_union, values, underlying_type, attributes, docs
- ReflectionEnumVal: name, value, union_type, attributes, docs
- ReflectionKeyValue: key, value

New methods on existing types:
- Schema: Enums(), EnumByName(), EnumValueName(), FileIdent(), FileExt()
- Object: MinAlign(), Attributes(), Documentation(), DeclarationFile()
- Field: ID(), Deprecated(), Key(), Optional(), Padding(), Offset64(),
         Attributes(), Documentation()
- Type: FixedLength(), BaseSize(), ElementSize()

Test suite (1,815 lines across 5 files):
- reflect_unit_test.go: every public method on every type
- reflect_integration_test.go: end-to-end schema + data workflows
- reflect_bench_test.go: 8 benchmarks for hot-path operations
- reflect_fuzz_test.go: 3 fuzz targets (schema loading, field reads)
- reflect_property_test.go: 9 schema invariant checks
SafeBuffer schema accessors:
- file_ident(), file_ext(), enums(), enum_by_name(), enum_value_name()
- objects(), object_by_name()

SafeTable new methods:
- get_field_bool() — boolean field access
- get_field_union() -> Option<(u8, SafeTable)> — union discriminant + table
- get_any_field() -> FieldValue — dynamic typed access for logging/inspection
- object() — returns the schema Object for this table
- fields() — returns all field definitions

FieldValue enum:
- Bool, Integer, Float, String, Table, Struct, VecInteger, VecFloat,
  VecString, Union, Absent variants
- Display impl for structured logging output

SafeStruct: get_field_bool()

Tests: 145 unit tests + 23 doc tests passing (up from 113 + 8)
Rustdoc: 677 lines of documentation with examples
Go and TypeScript verifiers now match Rust's strictness for unions:

Runtime verifier additions:
- Go: CheckUnionConsistency, CheckVectorOfStrings, ReadFieldByte
- TS: checkUnionConsistency, readFieldUint8, readUint8

Code generator changes (idl_gen_go.cpp, idl_gen_ts.cpp):
- Union fields emit consistency check (type+value both present or absent)
- Union fields emit per-variant dispatch (switch on type discriminant,
  call variant's verify function recursively)
- Go vector-of-strings now verifies each string element via
  CheckVectorOfStrings instead of just checking vector bounds
- Unknown union discriminants pass silently for forward compatibility

Previously Go treated union values as opaque byte vectors via
CheckVector(pos, 1) and TypeScript only checked offset accessibility.
Both defined InconsistentUnion error constants but never checked for them.

Regenerated all TS test golden files to reflect new verifier output.
Remove js/ and mjs/ from .gitignore so that GitHub-based npm installs
(github:user/repo#branch) get the compiled files directly. The
prepublishOnly script only runs for npm publish, not for GitHub refs,
so the compiled output must be in the repo.
…2.23

- Bump FLATBUFFERS_VERSION_REVISION 22 → 23; regenerate all C++ headers
  to match the new compatibility assertion.

- Fix Go verifier: CheckString, CheckVector, CheckVectorOfTables, and
  CheckVectorOfStrings were calling CheckUOffsetT internally, causing
  double-dereference when the caller already passed the target position
  returned by CheckOffsetField.  Each function now accepts the target
  position directly (no internal UOffsetT indirection).
  CheckVectorOfStrings now follows each element's UOffsetT itself.

- Fix PushDepth: depth was incremented before the limit check, leaving
  the counter elevated after a failed push.  The check now runs before
  the increment so that a failed push does not corrupt the depth state.

- Fix reflect_test.go: loadTestData was loading the JSON golden file
  (monsterdata_test.golden) as binary FlatBuffer data, causing a panic
  on the root-offset read.  Switch to monsterdata_go_wire.mon.

- Add tests/codegen_golden/{go,ts} generated output files.

All C++ (flattests), Go (go test ./go/...), and verifier tests pass.
- clone(): vector-of-structs uses .map(e => e.clone()) instead of null ternary
- clone(): scalar vectors use spread copy without null check
- equals(): vectors compared directly without null guards
- equals(): float fields use Object.is() for NaN-safe comparison
- equals(): vector-of-structs iterated without null wrapping
- Rust codegen: bfbs accessor is #[must_use] const fn
- Rust codegen: doc comments wrap schema symbols in backticks
- TS codegen: cross-namespace verify imports use proper aliasing
- C++ codegen: version assert uses >= for REVISION
- Version bump to 25.12.25
- Added TS tests for clone/equals vectors and cross-ns verify imports
The generated verify function passed the offset field position directly
to child verify functions, but FlatBuffer offset fields store a relative
int32 offset to the target table. The verifier must read and add that
offset to get the actual table position.

Also restores cross-namespace verify test to actually call the verifier
end-to-end instead of using a mock.
…ng method ref

The unpackFields() method generated t.field = this.field for union
fields, assigning the method reference instead of calling it. Now
uses GenUnionValTS to generate the proper accessor call (matching
what unpack/unpackTo already do). Fixes both scalar union and
vector-of-union cases.
…mprehensive TS tests

Codegen fixes:
- Verifier: union table variants now dereference offset before recursing
- Verifier: skip AddVerifyImport for fixed structs (no verify function)
- Runtime: checkVectorOfTables dereferences element offsets to table positions

Tests added (closing coverage gaps):
- testObjApiUnpackFields: selective unpacking of scalars, vectors, structs,
  nested tables, and union fields
- testObjApiNaNEquality: explicit Object.is() verification for NaN/Infinity
  default fields in equals()
- testObjApiVerify: verifier on valid buffer + corruption detection
- testMovieClone: clone with union and vector-of-union fields
- testMovieEquals: equals with union field mutation
- testMovieUnpackFields: unpackFields with vector-of-union containing
  table, struct, and string variants
- Replace for...of loops with indexed for loops (no-restricted-syntax)
- Replace continue statements with inverted if guards (no-continue)
- Replace hi << 32n with hi * 0x100000000n (no-bitwise)
- Replace i++ with i += 1 (no-plusplus)
- Add blank lines between class member declarations (lines-between-class-members)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c# c++ CI Continuous Integration codegen Involving generating code from schema dart documentation Documentation golang grpc java javascript json kotlin lobster lua nim php python rust swift typescript waiting-for-update This PR is waiting for a change from the author or contributors before it is ready for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.