From 197b8d21ab06e0143ad8efd3d25b9ff699c703cd Mon Sep 17 00:00:00 2001 From: Fabrizio Cucci Date: Sun, 29 Mar 2026 02:49:21 -0700 Subject: [PATCH] Fix Hermes crash from TurboModule void method NSException handling Summary: 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 --- .../core/platform/ios/ReactCommon/RCTTurboModule.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm index cc45dfc44a80..a1111f9c8565 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm @@ -460,7 +460,9 @@ TraceSection s( @try { [inv invokeWithTarget:strongModule]; } @catch (NSException *exception) { - throw convertNSExceptionToJSError(runtime, exception, std::string{moduleName}, methodNameStr); + // Void methods are always async, re-throw instead of converting to + // JSError, same as the async branch in performMethodInvocation. + @throw exception; } @finally { [retainedObjectsForInvocation removeAllObjects]; }