Skip to content

[0.81] [iOS] Backport TurboModule NSException safe-handling fix for iOS 26#56694

Open
pangu25 wants to merge 1 commit intofacebook:0.81-stablefrom
pangu25:backport/ios26-turbomodule-nsexception-fix
Open

[0.81] [iOS] Backport TurboModule NSException safe-handling fix for iOS 26#56694
pangu25 wants to merge 1 commit intofacebook:0.81-stablefrom
pangu25:backport/ios26-turbomodule-nsexception-fix

Conversation

@pangu25
Copy link
Copy Markdown

@pangu25 pangu25 commented May 6, 2026

Summary

Backport of #56265 (commit 4083a6ff92b0ffd4f71166848f3d7ce36172fac8,
"Fix Hermes crash from TurboModule void method NSException handling")
from main to 0.81-stable.

iOS 26 hardened TurboModule cold-start error handling. On the
unpatched 0.81 branch, ~66% of iPhones (per Apple's Feb 2026
adoption stats) crash at app launch with EXC_BAD_ACCESS /
SIGABRT inside performVoidMethodInvocation when any async void
TurboModule method throws an NSException. The unsafe path calls
convertNSExceptionToJSError from the native method call invoker
thread; since jsi::Runtime is not thread-safe, this corrupts
Hermes' heap.

The fix swaps the throw convertNSExceptionToJSError(...) for a
plain @throw exception;, mirroring how the sibling sync function
performMethodInvocation was already fixed in #50193 (D71619229)
back in March 2025. Since void methods are always async, there is
no JS caller to receive a converted JSError anyway — the original
JS call has already returned.

The cherry-pick from main applied cleanly (3-line single-file
change, no conflicts). This brings the fix to 0.81-stable so the
huge install base on RN 0.81.x (Expo SDK 54 and the ecosystem
pinned to it) can ship a fix without an SDK upgrade.

Linked issues

Test plan

Pick Request

This is a backport of a merged crash fix to a stable branch.
Marking as a pick request per RN backport conventions.

…acebook#56265)

Summary:
Pull Request resolved: facebook#56265

When an async void TurboModule method throws an NSException,
performVoidMethodInvocation calls convertNSExceptionToJSError which
accesses the Hermes JSI runtime from the native method call invoker
thread. Since jsi::Runtime is not thread-safe, this causes heap
corruption and EXC_BAD_ACCESS crashes across various hermes::vm::*
functions.

The sibling function performMethodInvocation was already fixed in
D71619229 to re-throw the ObjC exception instead of converting to
JSError when the call is async. This applies the same fix to
performVoidMethodInvocation, which is always async.

Related to SEV S641230 (4,550+ Hermes crashes in AMA iOS from OTA
bundle 921191722). A JS change behind a QE/MC gate is triggering an
NSException in a void TurboModule method for non-employee users, and
this bug turns that into widespread memory corruption. This fix
prevents the crash, but the triggering diff and throwing TurboModule
still need to be identified separately.

Matches upstream GitHub issue: https://github.com/facebook/hermes/issues/1957Commits affecting the React Native open source repository must have a changelog
entry in the commit summary. Every React Native release has almost 1000 commits,
and manually categorizing these commits is very time consuming.

 ---

Changelog:
[iOS][Fixed] - Fix Hermes crash when async void TurboModule method throws NSException by re-throwing instead of converting to JSError on wrong thread

Reviewed By: javache

Differential Revision: D98660782

fbshipit-source-id: bdedc769f17d9aec4156c45d0286c6c31ca006e4
(cherry picked from commit 4083a6f)
@meta-cla
Copy link
Copy Markdown

meta-cla Bot commented May 6, 2026

Hi @pangu25!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Caution

Missing Changelog

Please add a Changelog to your PR description. See Changelog format

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Warning

JavaScript API change detected

This PR commits an update to ReactNativeApi.d.ts, indicating a change to React Native's public JavaScript API.

  • Please include a clear changelog message.
  • This change will be subject to additional review.

This change was flagged as: BREAKING

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label May 6, 2026
@meta-cla
Copy link
Copy Markdown

meta-cla Bot commented May 6, 2026

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

FRI2PLAN pushed a commit to FRI2PLAN/fri2plan-native that referenced this pull request May 6, 2026
…idMethodInvocation)

Root cause: On iOS 26, async void TurboModule methods that throw NSExceptions
call convertNSExceptionToJSError which accesses jsi::Runtime from the wrong
thread (not thread-safe), causing heap corruption and SIGABRT.

Fix: Re-throw the ObjC exception instead of converting to JSError.
This matches the fix in performMethodInvocation (already fixed in PR #50193).

Applied via eas-build-pre-install.sh hook (runs before pod install on EAS).

References:
- facebook/react-native#56694
- facebook/react-native#54859
- facebook/react-native#54859 (comment)
FRI2PLAN pushed a commit to FRI2PLAN/fri2plan-native that referenced this pull request May 6, 2026
The eas-build-pre-install.sh patch was not applied because EAS reused
the cached compiled binary (same UUID a240950e-9a9c-3b58-ab2f-5d220f141047
in builds 14, 16, and 17).

This Config Plugin patches RCTTurboModule.mm during the Expo prebuild phase
(withDangerousMod), which runs BEFORE CocoaPods and BEFORE compilation.
This forces EAS to recompile the native code with the fix applied.

Fix: In performVoidMethodInvocation @catch block, replace:
  throw convertNSExceptionToJSError(runtime, exception, ...);
with:
  @throw exception;

This prevents jsi::Runtime from being accessed from the wrong thread
(not thread-safe), which caused heap corruption and SIGABRT on iOS 26.

References:
- facebook/react-native#56694
- facebook/react-native#54859
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Pick Request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants