fix(core): leave React's factory alone once the app declared its runtime - #19
Merged
Fiona2016 merged 1 commit intoAug 27, 2026
Merged
Conversation
Patching React.createElement is what crashes an app whose styling library owns the element factory. Measured on a release build of a nativewind app: the heap grows without bound until Hermes aborts during startup. Bisected against a build with only that patch removed - `memo` stays patched there and the app survives - so the factory patch is the whole of it. Two earlier attempts treated this as a matter of replacing the property more carefully: skip it when it is an accessor, and read it back through the same object the write lands on. Both are correct on their own terms and both are kept, but neither stops the crash. The problem is not how the property is replaced. It is that replacing it at all routes every createElement call in the process - React's internals included - through machinery the host wrote for its own calls. So the patch is now skipped once `jsxRuntimes` says where the app's elements actually come from. Under the automatic JSX transform such an app never calls React.createElement anyway, so nothing is instrumented that was not already: the declared runtime is what records the taps. An app that declares nothing still gets the old behaviour. Verified on a simulator release build: with the declared runtime instrumented and the factory left alone, the app starts, reports views, and records taps - `t_actions` rows of type `tap`, and `view_action_count` on the view they belong to. The pre-fix build is the negative control, and it aborts before any of that.
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.
Fixes the startup crash that #18 did not fix. Confirmed on a simulator release build, with the pre-fix build as the negative control.
What actually causes it
Patching
React.createElement. Nothing subtler.Bisected on a release build of a nativewind app:
hermes::vm::GCBase::oom→abort, SIGABRT on the JS threadcreateElement+memo)createElementskipped,memostill patchedSo
memois not involved, and neither is how the property gets replaced. Replacing it at all routes everycreateElementcall in the process — React's own internals included — through machinery the host wrote for its own calls. On a styling library that owns the element factory, that ends with the heap growing until Hermes aborts.The two changes in #18 are correct on their own terms and stay. They just treat a different problem.
The fix
Skip the
React.createElementpatch oncejsxRuntimestells us where the app's elements come from. Under the automatic JSX transform such an app never callsReact.createElementanyway — the declared runtime is what records the taps — so no instrumentation is lost. An app that declares nothing keeps the previous behaviour.Evidence
Negative control (0.1.4 as published, same app, same config including
jsxRuntimes):With the fix, the same app starts and reports:
Tests
Full suite green (885),
lerna run prepareacross all 9 projects, lint clean.