react-native icon indicating copy to clipboard operation
react-native copied to clipboard

feat(tm-ios): Reject Promise if Turbo Module method throws an Error

Open krystofwoldrich opened this issue 2 years ago • 5 comments

Summary:

Android change here

This PR builds upon the previous work done in https://github.com/facebook/react-native/pull/36925, which introduced native stack traces to the JSError for synchronous functions.

The current modifications concentrate on functions that return Promises. Prior to this PR, errors within Promise-returning functions would be thrown at the platform layer crashing the app without a link to the JS stack.

After the implementation of this PR, errors thrown within Promise-returning functions are now captured and transformed into rejected Promises. These rejected Promises contain a JS Error object that contains both the JS stack trace and the cause, along with the platform stack trace.

Additionally, this PR ensures that rejections from native functions are now linked to the JS stack trace, providing a more comprehensive view of the rejection flow.

Changelog:

[GENERAL][ADDED] - Turbo Modules Promise-returning functions reject with JS and platform stack traces information

Test Plan:

iOS
function_promise_ios

Example of intentionally rejected promise on iOS:

{
  "name": "Error",
  "message": "Exception in HostFunction: intentional promise rejection",
  "stack": "@[native code]\nrejectPromise@http://localhost:8081/js/examples/TurboModule/SampleTurboModuleExample.bundle:42:70\nonPress@http://localhost:8081/js/examples/TurboModule/SampleTurboModuleExample.bundle:242:71\n_performTransitionSideEffects@http://localhost:8081/js/RNTesterApp.ios.bundle:52614:22\n_receiveSignal@http://localhost:8081/js/RNTesterApp.ios.bundle:52570:45\nonResponderRelease@http://localhost:8081/js/RNTesterApp.ios.bundle:52433:34\ninvokeGuardedCallbackProd@http://localhost:8081/js/RNTesterApp.ios.bundle:3068:21\ninvokeGuardedCallback@http://localhost:8081/js/RNTesterApp.ios.bundle:3154:42\ninvokeGuardedCallbackAndCatchFirstError@http://localhost:8081/js/RNTesterApp.ios.bundle:3157:36\nexecuteDispatch@http://localhost:8081/js/RNTesterApp.ios.bundle:3221:48\nexecuteDispatchesInOrder@http://localhost:8081/js/RNTesterApp.ios.bundle:3238:26\nexecuteDispatchesAndRelease@http://localhost:8081/js/RNTesterApp.ios.bundle:4962:35\nforEach@[native code]\nforEachAccumulated@http://localhost:8081/js/RNTesterApp.ios.bundle:3680:22\nrunEventsInBatch@http://localhost:8081/js/RNTesterApp.ios.bundle:4980:27\nrunExtractedPluginEventsInBatch@http://localhost:8081/js/RNTesterApp.ios.bundle:5002:25\n@http://localhost:8081/js/RNTesterApp.ios.bundle:5020:42\nbatchedUpdates$1@http://localhost:8081/js/RNTesterApp.ios.bundle:14856:20\nbatchedUpdates@http://localhost:8081/js/RNTesterApp.ios.bundle:4951:36\ndispatchEvent@http://localhost:8081/js/RNTesterApp.ios.bundle:5013:23",
  "cause": {
    "code": "code_1",
    "stackSymbols": [
      "0   RNTester                            0x00000001031f9958 RCTJSErrorFromCodeMessageAndNSError + 112",
      "1   RNTester                            0x00000001034cecc8 ___ZZN8facebook5react15ObjCTurboModule13createPromiseERNS_3jsi7RuntimeENSt3__112basic_stringIcNS5_11char_traitsIcEENS5_9allocatorIcEEEEU13block_pointerFvU13block_pointerFvP11objc_objectEU13block_pointerFvP8NSStringSH_P7NSErrorEU13block_pointerFvP11NSExceptionEEENK3$_0clES4_RKNS2_5ValueEPSU_m_block_invoke.66 + 96",
      "2   RNTester                            0x000000010374e774 -[RCTSampleTurboModule getValueWithPromise:resolve:reject:] + 232",
      "3   CoreFoundation                      0x000000018043d6c0 __invoking___ + 144",
      "4   CoreFoundation                      0x000000018043aa44 -[NSInvocation invoke] + 276",
      "5   CoreFoundation                      0x000000018043acdc -[NSInvocation invokeWithTarget:] + 60",
      "6   RNTester                            0x00000001034c4de4 ___ZN8facebook5react15ObjCTurboModule23performMethodInvocationERNS_3jsi7RuntimeEbPKcP12NSInvocationP14NSMutableArrayU13block_pointerFvP11NSExceptionE_block_invoke + 240",
      "7   RNTester                            0x00000001034d5120 _ZZN8facebook5react15ObjCTurboModule23performMethodInvocationERNS_3jsi7RuntimeEbPKcP12NSInvocationP14NSMutableArrayU13block_pointerFvP11NSExceptionEENK3$_1clEv + 32",
      "8   RNTester                            0x00000001034d50f4 _ZNSt3__18__invokeB6v15006IRZN8facebook5react15ObjCTurboModule23performMethodInvocationERNS1_3jsi7RuntimeEbPKcP12NSInvocationP14NSMutableArrayU13block_pointerFvP11NSExceptionEE3$_1JEEEDTclclsr3stdE7declvalIT_EEspclsr3stdE7declvalIT0_EEEEOSJ_DpOSK_ + 24",
      "9   RNTester                            0x00000001034d50ac _ZNSt3__128__invoke_void_return_wrapperIvLb1EE6__callIJRZN8facebook5react15ObjCTurboModule23performMethodInvocationERNS3_3jsi7RuntimeEbPKcP12NSInvocationP14NSMutableArrayU13block_pointerFvP11NSExceptionEE3$_1EEEvDpOT_ + 24",
      "10  RNTester                            0x00000001034d5088 _ZNSt3__110__function12__alloc_funcIZN8facebook5react15ObjCTurboModule23performMethodInvocationERNS2_3jsi7RuntimeEbPKcP12NSInvocationP14NSMutableArrayU13block_pointerFvP11NSExceptionEE3$_1NS_9allocatorISI_EEFvvEEclB6v15006Ev + 28",
      "11  RNTester                            0x00000001034d3ce0 _ZNSt3__110__function6__funcIZN8facebook5react15ObjCTurboModule23performMethodInvocationERNS2_3jsi7RuntimeEbPKcP12NSInvocationP14NSMutableArrayU13block_pointerFvP11NSExceptionEE3$_1NS_9allocatorISI_EEFvvEEclEv + 28",
      "12  RNTester                            0x000000010308d65c _ZNKSt3__110__function12__value_funcIFvvEEclB6v15006Ev + 68",
      "13  RNTester                            0x000000010308d60c _ZNKSt3__18functionIFvvEEclEv + 24",
      "14  RNTester                            0x00000001034dbd24 ___ZN12_GLOBAL__N_128MethodQueueNativeCallInvoker11invokeAsyncEONSt3__18functionIFvvEEE_block_invoke + 44",
      "15  libdispatch.dylib                   0x0000000106820528 _dispatch_call_block_and_release + 24",
      "16  libdispatch.dylib                   0x0000000106821d50 _dispatch_client_callout + 16",
      "17  libdispatch.dylib                   0x0000000106832808 _dispatch_main_queue_drain + 1316",
      "18  libdispatch.dylib                   0x00000001068322d4 _dispatch_main_queue_callback_4CF + 40",
      "19  CoreFoundation                      0x000000018039a784 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12",
      "20  CoreFoundation                      0x0000000180394de4 __CFRunLoopRun + 1912",
      "21  CoreFoundation                      0x0000000180394254 CFRunLoopRunSpecific + 584",
      "22  GraphicsServices                    0x0000000188eb7c9c GSEventRunModal + 160",
      "23  UIKitCore                           0x0000000112b32ff0 -[UIApplication _run] + 868",
      "24  UIKitCore                           0x0000000112b36f3c UIApplicationMain + 124",
      "25  RNTester                            0x0000000102e7029c main + 96",
      "26  dyld                                0x0000000106721514 start_sim + 20",
      "27  ???                                 0x00000001068fde50 0x0 + 4405059152",
      "28  ???                                 0x7539800000000000 0x0 + 8446923313598431232"
    ],
    "message": "intentional promise rejection",
    "domain": "RCTSampleTurboModule",
    "userInfo": {}
  }
}

How I logged out the Errors:

console.log('Error in JS:', JSON.stringify({
  name: e.name,
  message: e.message,
  stack: e.stack,
  ...e,
}, null, 2));

krystofwoldrich avatar Oct 10 '23 12:10 krystofwoldrich

In other places in the codebase we've wrapped this as

try {
  @try {
     ...
   } @catch {
      // handle obj-c exceptions
   }
} catch {
  // handle c++
}

https://github.com/facebook/react-native/pull/37484#discussion_r1348806181 @javache I can use that since it's battle-tested. I don't know if using only try has any side effects. I tested all the exceptions from the catch block and all were watched correctly.

krystofwoldrich avatar Oct 10 '23 13:10 krystofwoldrich

Platform Engine Arch Size (bytes) Diff
android hermes arm64-v8a 21,367,652 +171
android hermes armeabi-v7a n/a --
android hermes x86 n/a --
android hermes x86_64 n/a --
android jsc arm64-v8a 24,564,228 +7
android jsc armeabi-v7a n/a --
android jsc x86 n/a --
android jsc x86_64 n/a --

Base commit: 2eb7bcb8d9c0f239a13897e3a5d4397d81d3f627 Branch: main

analysis-bot avatar Oct 11 '23 10:10 analysis-bot

@javache Hi, is there anything blocking this PR?

krystofwoldrich avatar Nov 30 '23 14:11 krystofwoldrich

@javache Thank you 🙏, no worries, will update the PR.

krystofwoldrich avatar Dec 14 '23 09:12 krystofwoldrich

Hi @javache, can you check this PR?

krystofwoldrich avatar Mar 23 '24 19:03 krystofwoldrich

@javache Thank you very much, I've applied the suggestions.

krystofwoldrich avatar Jul 15 '24 04:07 krystofwoldrich

This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

react-native-bot avatar Aug 13 '25 05:08 react-native-bot

This PR was closed because it has been stalled for 7 days with no activity.

react-native-bot avatar Aug 20 '25 05:08 react-native-bot