Babel preset: Add Hermes V1 native runtime workarounds for hermes#1761#56816
Open
ramonclaudio wants to merge 1 commit into
Open
Babel preset: Add Hermes V1 native runtime workarounds for hermes#1761#56816ramonclaudio wants to merge 1 commit into
ramonclaudio wants to merge 1 commit into
Conversation
## Summary: Three Babel plugins added to `@react-native/babel-preset` that work around runtime codegen bugs in the bundled Hermes V1 (250829098.0.13, branch cut 2025-08-29) by rewriting source patterns before they reach the engine. Each plugin papers over a different bug that has been fixed on Hermes `static_h` but has not been backported to the V1 stable branch. - `fix-hermes-v1-async-arrow-non-simple-params`: facebook/hermes#1761 (fixed in static_h by 68bfb3a48b31, 2025-09-11). Async arrow functions with destructured, defaulted, or rest parameters silently resolve `await` with `undefined` while the function body continues executing in the background. The plugin rewrites the params to a simple identifier with inline destructuring so Hermes never sees the buggy shape. Gated on `isHermesProfile && preserveAsync` since `plugin-transform-async-to-generator` rewrites the pattern away when `preserveAsync` is false. - `fix-hermes-v1-class-in-finally`: Fixed in static_h by 1e94fbe0ebb4 (2026-02-12). Class declarations inside a `finally` block trip Hermes V1's variable caching path. The plugin wraps them in an IIFE so the class lives in its own scope. - `fix-hermes-v1-super-in-object-accessor`: Fixed in static_h by 18a963465944 (2025-11-04). Object-literal getters and setters using `super.x` trip the `genFunctionExpression` home object path. The plugin marks the accessor as computed with a string key. All three gate on `isHermesProfile` and bail out fast on the common case. The plugins are a direct port of work by @kitten (Phil Pluckthun, Expo team) in `babel-preset-expo` (expo/expo#45601, MIT licensed). All plugin design and implementation credit belongs to him. This PR carries his work over to `@react-native/babel-preset` so bare React Native consumers benefit from the same fix. ## Changelog: [GENERAL] [FIXED] - Work around three Hermes V1 source-level codegen bugs in `@react-native/babel-preset` (async-arrow non-simple params, class-in-finally, super-in-object-accessor) ## Test Plan: - 18 new inline-snapshot test cases across three new test files verify both the transformed output and the fast-bail behavior on irrelevant patterns. - `yarn jest packages/react-native-babel-preset` clean (65 tests, 24 snapshots). - Full repo `yarn test` clean (279 suites, 5656 tests passed, 1816 snapshots). - `yarn lint`, `yarn format-check`, `yarn flow-check`, `yarn test-typescript`, `yarn featureflags --verify-unchanged`, `yarn build-types --validate` all clean. - Existing `transform-snapshot-test` passes byte-identical: the kitchen-sink fixture has none of the buggy patterns so the new plugins do not change its output for any profile. - Verified end-to-end downstream: `babel-preset-expo@56.0.7` (which carries the same plugins) applied to vanilla `@convex-dev/better-auth@0.12.2` fixes the `useConvexAuth.isAuthenticated` race that the destructured-param async-arrow shape produces on Hermes V1. - Public repro: https://github.com/ramonclaudio/convex-better-auth-368-repro ## Caveats: `fix-hermes-v1-class-in-finally` rewrites block-scoped `class` declarations inside `finally` blocks to function-scoped `var` initializers. Observable only when code references the class by name from outside the `finally` (rare pattern). The same plugin shape has run in `babel-preset-expo@56.0.6+` and Expo SDK 56 preview for a week without reported regressions, but flagging in case Meta's internal test fleet surfaces an edge case the OSS test suite does not catch. ## References: - facebook/hermes#1761 (root-cause issue, fixed in static_h) - expo/expo#45601 (source of the ported plugins, by @kitten) - expo/expo#45592 (user-facing bug report on SDK 56 preview) Co-authored-by: Phil Pluckthun <phil@kitten.sh>
469ebb8 to
a5fff17
Compare
Author
|
Thanks to @kitten for the original plugin work in expo/expo#45601. The original design and implementation credit on these three transforms belongs to him. This PR just ports his work over to |
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:
Three Babel plugins for
@react-native/babel-presetthat work around runtime bugs in the bundled Hermes V1 (250829098.0.13, branch cut 2025-08-29). They rewrite the buggy code patterns at compile time so Hermes never sees them. Direct port of @kitten's plugins inbabel-preset-expo(expo/expo#45601, merged 2026-05-11); same fix, applied at the@react-native/babel-presetlayer so bare React Native consumers don't needbabel-preset-expoto escape the bugs. All plugin design and implementation credit is his.facebook/hermes#1761 was reported by
trossimel-scat Meta in August 2025, confirmed within the hour bytmikov, and fixed on Hermesstatic_htwo weeks later in68bfb3a48b31. The fix never got cherry-picked back to the250829098.0.0-stablebranch React Native pins. Two related codegen bugs sit in the same gap: class declarations insidefinallyblocks (1e94fbe0ebb4, 2026-02-12) andsuper.xlookups in object-literal getters and setters (18a963465944, 2025-11-04). The most recent V1 stable bump (0.13, #56728, 2026-05-08) carried only the Promise rejection-tracker backport, not these three.fix-hermes-v1-async-arrow-non-simple-params: rewritesasync ({a}) => 42intoasync _p => { var {a} = _p; return 42; }. Without it,awaitresolves withundefinedwhile the body keeps running in the background. Gated onisHermesProfile && preserveAsyncsince@babel/plugin-transform-async-to-generatorstrips async otherwise. Without this plugin,unstable_preserveAsync(Babel preset: Add unstable_preserveAsync to experiment with disabling async transforms for SH (#56254) #56254) is silently broken on Hermes V1 for any user code with non-simple async-arrow params.fix-hermes-v1-class-in-finally: wraps class declarations insidefinallyblocks in an IIFE so the class lives in its own scope. The Hermes bug surfaces on call graphs that populate the IR variable cache between normal and exception code paths, which is recompilation-pattern dependent and hard to construct in a minimal repro. @kitten verified the workaround against Expo's internaltest-suite.fix-hermes-v1-super-in-object-accessor: rewritesget name()intoget ["name"]()so the accessor key is computed. Identifier-keyed accessors segfault Hermes V1 during IR generation.All three gate on
isHermesProfileand bail out fast when the trigger patterns aren't present. The workaround unblocks 0.84.x and 0.85.x consumers today and becomes a no-op once a future V1 build picks up the upstream fixes.Empirical validation:
Built a minimal bare React Native 0.85.3 repro that ports the bug-trigger patterns from @kitten's
apps/test-suitework, plus a race detector, engine-identity assertions, and feature coverage for harder class shapes (static blocks, async/generator methods, private fields, getters, super-in-extends). 55 tests at ramonclaudio/hermes-1761-repro.Per-plugin A/B isolation on iPhone 17 Pro / iOS 26.5, Hermes V1
250829098.0.13:@react-native/babel-preset@0.85.3SIGSEGVinhermes::irgen::ESTreeIRGen::genMemberExpression)super-in-object-accessordisabledasync-arrow-non-simple-paramsdisabledawaiter-resumed:undefinedwhile the body returned 40class-in-finallydisabledDownstream confirmation:
babel-preset-expo@56.0.7(which carries the same plugins) applied to vanilla@convex-dev/better-auth@0.12.2fixes theuseConvexAuth.isAuthenticatedrace on Hermes V1. Original user-facing repro at ramonclaudio/convex-better-auth-368-repro.Caveats:
fix-hermes-v1-class-in-finallyrewrites block-scopedclassdeclarations insidefinallyblocks to function-scopedvarinitializers. The change is observable only when code references the class identifier outside thefinally, e.g., a direct reference after thefinallyexits or a same-named outervarthe originalclasswould have shadowed. Both are uncommon, and the four scope-delta cases I could construct are pinned as passing tests in the linked repro. The same plugin shape has been live inbabel-preset-expo@56.0.6+since 2026-05-11. Flagging in case Meta's internal test fleet surfaces an edge case the OSS test suite does not.Changelog:
[GENERAL] [FIXED] - Work around three Hermes V1 source-level codegen bugs in
@react-native/babel-preset(async-arrow non-simple params, class-in-finally, super-in-object-accessor)Test Plan:
Test repro at ramonclaudio/hermes-1761-repro. 55/55 PASS on iPhone 17 Pro / iOS 26.5 in both Debug and Release builds. Per-plugin A/B matrix above shows each plugin's role.
Locally on
packages/react-native-babel-preset:yarn jest packages/react-native-babel-presetclean (65 tests, 24 snapshots).yarn testclean (279 suites, 5656 tests, 1816 snapshots).yarn lint,yarn format-check,yarn flow-check,yarn test-typescript,yarn featureflags --verify-unchanged,yarn build-types --validateall clean.transform-snapshot-testis unchanged: the kitchen-sink fixture has none of the bug patterns so the new plugins don't alter its output.kitchen-sink-input.jsin a follow-up if reviewers want snapshot coverage.References: