From 6d41f3c456093b6dfb830bbb8f064d432bdfc5fa Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Mon, 10 Feb 2025 10:24:56 +0000 Subject: [PATCH] [RN][iOS] Properly handle null values in TM interop layer --- .../ios/ReactCommon/RCTInteropTurboModule.mm | 8 +++---- .../platform/ios/ReactCommon/RCTTurboModule.h | 1 + .../ios/ReactCommon/RCTTurboModule.mm | 24 ++++++++++++------- packages/rn-tester/Podfile.lock | 14 +++++------ 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm index ad267de81138df..680ca63e1e5a81 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm @@ -337,7 +337,7 @@ T RCTConvertTo(SEL selector, id json) SEL selector = selectorForType(argumentType); if ([RCTConvert respondsToSelector:selector]) { - id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_); + id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES); if (objCArgType == @encode(char)) { char arg = RCTConvertTo(selector, objCArg); @@ -491,7 +491,7 @@ T RCTConvertTo(SEL selector, id json) } RCTResponseSenderBlock arg = - (RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_); + (RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES); if (arg) { [retainedObjectsForInvocation addObject:arg]; } @@ -506,7 +506,7 @@ T RCTConvertTo(SEL selector, id json) } RCTResponseSenderBlock senderBlock = - (RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_); + (RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES); RCTResponseErrorBlock arg = ^(NSError *error) { senderBlock(@[ RCTJSErrorFromNSError(error) ]); }; @@ -536,7 +536,7 @@ T RCTConvertTo(SEL selector, id json) runtime, errorPrefix + "JavaScript argument must be a plain object. Got " + getType(runtime, jsiArg)); } - id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_); + id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES); RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend; RCTManagedPointer *box = convert([RCTCxxConvert class], selector, arg); diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h index f54e1751fb9465..7899037ee0b4fe 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h @@ -32,6 +32,7 @@ using EventEmitterCallback = std::function; namespace TurboModuleConvertUtils { jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value); id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr jsInvoker); +id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr jsInvoker, BOOL useNSNull); } // namespace TurboModuleConvertUtils template <> 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 812723f646ea1b..64271136714c56 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 @@ -112,20 +112,20 @@ static int32_t getUniqueId() } static NSArray * -convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value, std::shared_ptr jsInvoker) +convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value, std::shared_ptr jsInvoker, BOOL useNSNull) { size_t size = value.size(runtime); NSMutableArray *result = [NSMutableArray new]; for (size_t i = 0; i < size; i++) { // Insert kCFNull when it's `undefined` value to preserve the indices. - id convertedObject = convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker); + id convertedObject = convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker, useNSNull); [result addObject:convertedObject ? convertedObject : (id)kCFNull]; } return [result copy]; } static NSDictionary * -convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value, std::shared_ptr jsInvoker) +convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value, std::shared_ptr jsInvoker, BOOL useNSNull) { jsi::Array propertyNames = value.getPropertyNames(runtime); size_t size = propertyNames.size(runtime); @@ -133,7 +133,7 @@ static int32_t getUniqueId() for (size_t i = 0; i < size; i++) { jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime); NSString *k = convertJSIStringToNSString(runtime, name); - id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker); + id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker, useNSNull); if (v) { result[k] = v; } @@ -159,11 +159,14 @@ static int32_t getUniqueId() }; } -id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr jsInvoker) +id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr jsInvoker, BOOL useNSNull) { - if (value.isUndefined() || value.isNull()) { + if (value.isUndefined() || (value.isNull() && !useNSNull)) { return nil; } + if (value.isNull() && useNSNull) { + return [NSNull null]; + } if (value.isBool()) { return @(value.getBool()); } @@ -176,17 +179,22 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s if (value.isObject()) { jsi::Object o = value.getObject(runtime); if (o.isArray(runtime)) { - return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker); + return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker, useNSNull); } if (o.isFunction(runtime)) { return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker); } - return convertJSIObjectToNSDictionary(runtime, o, jsInvoker); + return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull); } throw std::runtime_error("Unsupported jsi::Value kind"); } +id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr jsInvoker) +{ + return convertJSIValueToObjCObject(runtime, value, jsInvoker, NO); +} + static jsi::Value createJSRuntimeError(jsi::Runtime &runtime, const std::string &message) { return runtime.global().getPropertyAsFunction(runtime, "Error").call(runtime, message); diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index be9fe4071d8061..17a5d6ce435ee8 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -1862,15 +1862,15 @@ EXTERNAL SOURCES: :path: "../react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb + boost: 4cb898d0bf20404aab1850c656dcea009429d6c1 + DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 2bc03a5cf64e29c611bbc5d7eb9d9f7431f37ee6 fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd - glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8 - hermes-engine: 1f783c3d53940aed0d2c84586f0b7a85ab7827ef - MyNativeView: cb6948821e50f1285b4f795b13787635fd8569b7 - NativeCxxModuleExample: 9d7a93fcea9944a38f58a736fee60718177731d6 + glog: 69ef571f3de08433d766d614c73a9838a06bf7eb + hermes-engine: d152d4b5b123cb9abbd21183ed7783e067314c76 + MyNativeView: ba8324184f90f76b5051af6716faa458d4d63b51 + NativeCxxModuleExample: ad2c1a2c0def6c6a1bf5f1257e6bc372b806aea5 OCMock: 589f2c84dacb1f5aaf6e4cec1f292551fe748e74 OSSLibraryExample: 837b4500e07ab8041d539a3ad871586ebcc9b43b RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 @@ -1940,4 +1940,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 8591f96a513620a2a83a0b9a125ad3fa32ea1369 -COCOAPODS: 1.15.2 +COCOAPODS: 1.14.3