fix(runtime): reject unregistered box pointers in js_box_set/get (#4898)#4902
Merged
Merged
Conversation
react-reconciler@0.33 crashed at module init under Perry with a SIGBUS (exit 138) before any user statement ran. The faulting instruction is `str d0, [x0]` inside `js_box_set`, with x0 pointing at the read-only `"object_get_by_name_guard"` string constant in __TEXT.__cstring. Root cause: a miscompiled `js_box_set` call receives a box-pointer operand that is effectively undef/poison at the IR level (a mutable- capture box whose allocation was elided on the taken path). Under the typed-feedback (#854) `register_site` instrumentation — react-reconciler emits ~2500 of these in one giant minified function — the value left live in the callee-saved register that regalloc reuses for the undef operand is the `..._guard` diagnostic string. That constant is >=0x1000, untagged (top-16 zero), and 8-byte aligned, so it passed every check in `is_plausible_box_ptr` and the write landed in read-only memory. Fix: gate `js_box_set`/`js_box_get` on `is_registered_box_ptr`, which additionally requires the address to be in `BOX_REGISTRY` (every pointer `js_box_alloc` ever minted). Boxes are never freed, so the registry is monotonic per thread: membership has no false negatives for a real box and no stale-reuse hazard. A pointer that was never alloc'd as a box is provably not a box and is skipped — the same correctness-safe silent no-op the structural checks already performed for bad pointers (#924). Result: the standalone `import createReconciler from 'react-reconciler'` repro prints "reached" and exits 0, and `createReconciler({...})` now constructs a working reconciler (createContainer is a function) where main SIGBUSes. Closure mutable-capture semantics are unchanged. Adds two unit tests; full perry-runtime suite passes (one pre-existing, unrelated date-test failure also present on main).
This was referenced Jun 10, 2026
fix(cjs_wrap): flat-emit module.exports = <Class> that closes over a top-level binding (#4933)
#4947
Merged
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
Fixes #4898 —
react-reconciler@0.33crashed at module init under Perry with a SIGBUS (exit 138) before any user statement ran. After this change the standalone repro printsreachedand exits 0, andcreateReconciler({...})constructs a working reconciler (createContaineris a function) wheremainSIGBUSes.Root cause
Deep-triage (thanks @proggeramlug) pinpointed the faulting instruction as
str d0, [x0]insidejs_box_set, withx0= the address of the read-only"object_get_by_name_guard"string constant in__TEXT.__cstring.js_box_setcall receives a box-pointer operand that is effectivelyundef/poison at the IR level (a mutable-capture box whose allocation was elided on the taken path).register_siteinstrumentation — react-reconciler emits ~2500 of these in one giant minified function — the value left live in the callee-saved register that regalloc reuses for the undef operand is the..._guarddiagnostic string passed tojs_typed_feedback_register_site.>=0x1000, untagged (top-16 zero), and 8-byte aligned, so it passed every structural check inis_plausible_box_ptr, and the write landed in read-only memory.The IR itself never stores into the guard global (verified across all
--trace llvmmodules — every guard global is used only as aregister_sitearg); the bad pointer is an optimizer/regalloc artifact of the undef operand, which is why a defensive runtime check is the robust fix.Fix
Gate
js_box_set/js_box_geton a newis_registered_box_ptr, which additionally requires the address to be inBOX_REGISTRY(every pointerjs_box_allocever minted). Boxes are never freed, so the registry is monotonic per thread: membership has no false negatives for a real box and no stale-reuse hazard. A pointer that was never alloc'd as a box is provably not a box and is skipped — the same correctness-safe silent no-op the structural checks already performed for bad pointers (#924).Validation
import createReconciler from 'react-reconciler'; console.log('reached', ...)→reached function, exit 0 (was exit 138).createReconciler({...full host config...})→reconciler built: object createContainer: function, exit 0 (was exit 138).for-letloop captures) → unchanged, correct.box_set_skips_unregistered_plausible_pointer,box_set_get_roundtrips_for_real_box).perry-runtimesuite passes (one pre-existing, unrelateddate::tests::test_full_year_setters_revive_invalid_date_onlyfailure also present onmain).Notes
createReconcilerfactory call. The deeper codegen bug (emittingjs_box_setwith an undef box-pointer operand) is worth a separate follow-up, but the runtime gate is correctness-safe regardless of where the stray pointer originates.