From 69a5ad5d8cd661c625f9ba0612852f067117f00e Mon Sep 17 00:00:00 2001 From: Vladimir Iliev Date: Mon, 2 Jun 2025 04:50:40 -0700 Subject: [PATCH] Fix emitting event from turbo module crashes on 32bit android (#51695) Summary: After testing the latest RC and nighly builds, crash appeared when emitting events from turbo modules on 32bit Android devices. The crash is always reproducible only on 32bit devices on signed production builds. Fore more details and the crash log, check the [related issue](https://github.com/facebook/react-native/issues/51628#issue-3094045077). From what I found, the variadic functions like CallVoidMethod are unsafe on 32bit due to not type checking the passed arguments at compile time. As far as I understand the 64bit cpus and ABIs are more forgiving with alignment and calling conventions. On 32bit the ABIs are strict as arguments are passed on the stack and if there is type/size/alignment issue it reads the wrong memory, which causes the SIGEGV crashes. ## Changelog: [ANDROID] [FIXED] - emitting event from turbo module crashes on 32bit android Pull Request resolved: https://github.com/facebook/react-native/pull/51695 Test Plan: 1. Pull the [reproduction demo](https://github.com/vladimirivanoviliev/rn079eventcrash), install the dependencies (v `0.80` is on PR) 2. Run codegen on android 3. Build signed apk. To create it you will need to create new demo key-store. 4. To install the build apk in 32bit mode you can use `adb -s YOURDEVICE install --abi armeabi-v7a android/app/release/app-release.apk` 5. Run the app, create key, save it. Than update the key and save it again. The app crashes when try to emit event from the turbo module. 6. Patch the related `JavaTurboModule.cpp` file with the changes from this PR and enable build from source. 7. Rebuild and reinstall the apk and test again - the issue is now fixed ## Additional notes: I have tested the app on android using the `rn-tester` demo app, everything works as expected. I also patched our production app and tested more complex scenarios and they works as expected. I have run the tests and linter and they passed. One thing that I didn't able to setup and run is the iOS `rn-tester` app, due to Hermes engine error `Command PhaseScriptExecution failed with a nonzero exit code`. I haven't found any information how to fix it. I have followed [this guide](https://github.com/facebook/react-native/blob/main/packages/rn-tester/README.md) and installed node modules using yarn and started the `yarn prepare-ios`. I also haven't found any information with what node version and ruby version the react native package is build on CI so I use the same versions locally. If you provide me with updated instructions for those I can contribute by updating the related guides and including `.npmrc`, `.ruby-version` files. Reviewed By: cortinico Differential Revision: D75782377 Pulled By: javache fbshipit-source-id: b94998be6dd51e90ad4137b1d2e38a6850bc3cb2 --- .../android/ReactCommon/JavaTurboModule.cpp | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp index dd2b70f84f4..99381c6ca5b 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp @@ -982,15 +982,19 @@ void JavaTurboModule::configureEventEmitterCallback() { FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); } - jvalue arg; - arg.l = - JCxxCallbackImpl::newObjectCxxArgs([&](folly::dynamic args) { - auto eventName = args.at(0).asString(); - auto& eventEmitter = static_cast&>( - *eventEmitterMap_[eventName].get()); - eventEmitter.emit(args.size() > 1 ? std::move(args).at(1) : nullptr); - }).release(); - env->CallVoidMethod(instance_.get(), cachedMethodId, arg); + auto callback = JCxxCallbackImpl::newObjectCxxArgs([&](folly::dynamic args) { + auto eventName = args.at(0).asString(); + auto& eventEmitter = static_cast&>( + *eventEmitterMap_[eventName].get()); + eventEmitter.emit(args.size() > 1 ? std::move(args).at(1) : nullptr); + }); + + jvalue args[1]; + args[0].l = callback.release(); + + // CallVoidMethod is replaced with CallVoidMethodA as it's unsafe on 32bit and + // causes crashes https://github.com/facebook/react-native/issues/51628 + env->CallVoidMethodA(instance_.get(), cachedMethodId, args); FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); }