fix(ffi): regenerate the drifted cbindgen header and gate it in CI - #277
Open
YuanYuYuan wants to merge 2 commits into
Open
fix(ffi): regenerate the drifted cbindgen header and gate it in CI#277YuanYuYuan wants to merge 2 commits into
YuanYuYuan wants to merge 2 commits into
Conversation
The committed header lacked the namespace_ field that CContextConfig has carried since it was added. cgo sizes its struct from this file, so Go allocated 80 bytes where the Rust side reads 88 and dereferenced cfg.namespace past the end of the allocation on every advanced-config ContextBuilder.Build(). Regenerated with the cbindgen the flake pins (0.29.3). Also restores 11 parameter-type constants, KEEP_ALL_CACHE_DEPTH, and corrected doc text. Refs #270
Nothing regenerated the header in CI, so its drift from the Rust structs was invisible. cbindgen was absent from every dev shell, and build.rs degrades its absence to a non-fatal cargo:warning=, so enabling the ffi feature alone would not have caught this. Adds rust-cbindgen to commonBuildInputs and a check-ffi-header check that deletes the header, regenerates it, and fails on any diff -- mirroring check-python-stubs, which gates the generated Python stubs the same way. The deletion is what makes a build that generates nothing fail instead of reporting on the committed copy; cbindgen's absence is checked explicitly so that path cannot pass vacuously either. Runs as its own nix-based job because go-tests has no nix environment and cbindgen must come from the pinned dev shell -- 0.29.4 emits a constant 0.29.3 does not, so an unpinned version would itself read as drift. Closes item 3 of #270
There was a problem hiding this comment.
Pull request overview
Regenerates the committed FFI header and adds CI drift detection.
Changes:
- Updates the C header with the missing context namespace field and constants.
- Adds cbindgen and a header freshness script.
- Adds a dedicated CI freshness job.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
scripts/test-pure-rust.nu |
Adds the regeneration check. |
flake.nix |
Adds cbindgen to build inputs. |
crates/hiroz-go/hiroz/hiroz_ffi.h |
Regenerates the C FFI surface. |
.github/workflows/ci.yml |
Runs the freshness check in CI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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.
Summary
crates/hiroz-go/hiroz/hiroz_ffi.his generated by cbindgen fromcrates/hiroz/src/ffi/and committed, because cgo needs it at build time. Nothing in CI regenerated it, so it drifted — and the drift was not confined to documentation.The committed header was missing the
namespace_field thatCContextConfighas carried since it was added:cgo sizes its struct from this header.
sizeof(hiroz_context_config_t)is 80 bytes as committed and 88 bytes as generated, socrates/hiroz-go/hiroz/context.go:133'svar cfg C.hiroz_context_config_tallocates 80 bytes, andcrates/hiroz/src/ffi/context.rs:84readscfg.namespaceat offset 80 — past the end of the allocation — then dereferences it as a C string when the garbage there is non-null. That path is reached by any Go caller that sets an advanced-config option.Key Changes
namespace_field above,KEEP_ALL_CACHE_DEPTH, 11 parameter-type constants, and corrected doc text forDEFAULT_HISTORY_DEPTHandhiroz_service_client_wait_for_service.rust-cbindgentocommonBuildInputs. It was in no dev shell, which is the root cause: CI could not have regenerated the header even if something had asked it to. Placed besidego, since that header is what cgo compiles against.check-ffi-headertoscripts/test-pure-rust.nu, mirroringcheck-python-stubs, which gates the generated Python stubs the same way. It deletes the header, forcesbuild.rsto rerun, rebuilds with--features ffi, and fails on anygit status --porcelainoutput.ffi-header-freshCI job mirroringpython-stubs-fresh.Two details are load-bearing rather than incidental:
Dentry.crates/hiroz/build.rs:55-61degrades a missing cbindgen to a non-fatalcargo:warning=, so without this the build would succeed, write nothing, and the check would report on whatever was already in the tree.It runs as its own nix-based job because
go-testshas no nix environment, and cbindgen must come from the pinned dev shell: 0.29.4 emits aCDR_HEADER_LEconstant that 0.29.3 does not, so an unpinned cbindgen would itself read as drift. The version used is printed in the log so that case is diagnosable rather than mysterious.What fails without this
The header on
mainis stale today. Running the new check against it fails:The size mismatch it corresponds to, compiled from each version of the header:
Verified in three directions, so the detector is known to detect rather than merely never to fire:
mainas-is)PATHNotes
Addresses item 3 of #270 and the two follow-ups recorded in its second comment. The rest of #270 — the 22
missing_safety_doccontracts, theserialize.rs:81cast, linting the module under-D warnings, and theRawPublisher::publish_bytesregression test — is untouched and still open.Independent of #273: that PR adds a build step to
go-tests, which this does not modify. Merge simulation is clean againstmain, #273, #250 and #255.The
nixfmt --checkdeviation inflake.nix(twoextraShellHook = ''''occurrences at lines 396 and 511) is pre-existing onmainand outside this diff; left alone.Breaking Changes
Yes, for C and Go consumers — they must rebuild.
sizeof(hiroz_context_config_t)changes from 80 to 88.Appending the field preserves the offsets of every pre-existing member, so source compiled against the new header is compatible. It does not preserve ABI: a caller still linked against the 80-byte definition passes an 80-byte allocation to a library that reads
namespace_at offset 80, which is the out-of-bounds read described above. The header comment inherited from the Rust source calls this "ABI compatibility"; that is too strong, and the accurate statement is offset compatibility plus a mandatory rebuild.Rebuilding is what corrects it, and cgo does so automatically for the in-tree Go bindings. Any out-of-tree C consumer holding a copy of the old header must regenerate it.
None to the Rust API.