From ab7cbd673c830525feabc361536653d7a0959a19 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 16 May 2025 06:20:22 -0700 Subject: [PATCH 1/2] Use methodSignatureForSelector instead of instanceMethodSignatureForSelector (#51366) Summary: We inherited this from the legacy native module infra, where we didn't have access to the module instance. Instead we can use the simpler `methodSignatureForSelector` which works correctly with OCMock (needed in D74815079). Changelog: [Internal] Reviewed By: sammy-SC Differential Revision: D74817191 --- .../core/platform/ios/ReactCommon/RCTTurboModule.mm | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 441a16f419f3..e8ad078da5f0 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 @@ -686,7 +686,7 @@ TraceSection s( NSMutableArray *retainedObjectsForInvocation) { const char *moduleName = name_.c_str(); - const id module = instance_; + const NSObject *module = instance_; if (isSync) { TurboModulePerfLogger::syncMethodCallArgConversionStart(moduleName, methodName); @@ -694,12 +694,10 @@ TraceSection s( TurboModulePerfLogger::asyncMethodCallArgConversionStart(moduleName, methodName); } - NSInvocation *inv = - [NSInvocation invocationWithMethodSignature:[[module class] instanceMethodSignatureForSelector:selector]]; + NSMethodSignature *methodSignature = [module methodSignatureForSelector:selector]; + NSInvocation *inv = [NSInvocation invocationWithMethodSignature:methodSignature]; [inv setSelector:selector]; - NSMethodSignature *methodSignature = [[module class] instanceMethodSignatureForSelector:selector]; - for (size_t i = 0; i < count; i++) { const jsi::Value &arg = args[i]; const std::string objCArgType = [methodSignature getArgumentTypeAtIndex:i + 2]; From ce9d0974947de4106e24b8457902c213c13ad9b8 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 16 May 2025 06:20:22 -0700 Subject: [PATCH 2/2] Add unit test for NSNull behaviour in RCTTurboModule (#51370) Summary: Changelog: [Internal] Reviewed By: NickGerleman Differential Revision: D74815079 --- .../core/iostests/RCTTurboModuleTests.mm | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 packages/react-native/ReactCommon/react/nativemodule/core/iostests/RCTTurboModuleTests.mm diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/iostests/RCTTurboModuleTests.mm b/packages/react-native/ReactCommon/react/nativemodule/core/iostests/RCTTurboModuleTests.mm new file mode 100644 index 000000000000..ec9b15ecc617 --- /dev/null +++ b/packages/react-native/ReactCommon/react/nativemodule/core/iostests/RCTTurboModuleTests.mm @@ -0,0 +1,98 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#import +#import + +#import + +using namespace facebook::react; + +@protocol RCTTestTurboModule + +- (void)testMethodWhichTakesObject:(id)object; + +@end + +class StubNativeMethodCallInvoker : public NativeMethodCallInvoker { + public: + void invokeAsync(const std::string &methodName, NativeMethodCallFunc &&func) noexcept override + { + func(); + } + void invokeSync(const std::string &methodName, NativeMethodCallFunc &&func) noexcept override + { + func(); + } +}; + +@interface RCTTurboModuleTests : XCTestCase +@end + +@implementation RCTTurboModuleTests { + std::unique_ptr module_; + id instance_; +} + +- (void)setUp +{ + [super setUp]; + instance_ = OCMProtocolMock(@protocol(RCTTestTurboModule)); + + ObjCTurboModule::InitParams params = { + .moduleName = "TestModule", + .instance = instance_, + .jsInvoker = nullptr, + .nativeMethodCallInvoker = std::make_shared(), + .isSyncModule = false, + .shouldVoidMethodsExecuteSync = true, + }; + module_ = std::make_unique(params); +} + +- (void)tearDown +{ + module_ = nullptr; + instance_ = nil; + + [super tearDown]; +} + +- (void)testInvokeTurboModuleWithNull +{ + auto hermesRuntime = facebook::hermes::makeHermesRuntime(); + facebook::jsi::Runtime *rt = hermesRuntime.get(); + + // Empty object + facebook::jsi::Value args[1] = {facebook::jsi::Object(*rt)}; + module_->invokeObjCMethod( + *rt, VoidKind, "testMethodWhichTakesObject", @selector(testMethodWhichTakesObject:), args, 1); + OCMVerify(OCMTimes(1), [instance_ testMethodWhichTakesObject:@{}]); + + // Object with one key + args[0].asObject(*rt).setProperty(*rt, "foo", "bar"); + module_->invokeObjCMethod( + *rt, VoidKind, "testMethodWhichTakesObject", @selector(testMethodWhichTakesObject:), args, 1); + OCMVerify(OCMTimes(1), [instance_ testMethodWhichTakesObject:@{@"foo" : @"bar"}]); + + // Object with key without value + args[0].asObject(*rt).setProperty(*rt, "foo", "facebook::jsi::Value::undefined()"); + module_->invokeObjCMethod( + *rt, VoidKind, "testMethodWhichTakesObject", @selector(testMethodWhichTakesObject:), args, 1); + // FIXME this should be called with @{@"foo": kCFNull} + OCMVerify(OCMTimes(1), [instance_ testMethodWhichTakesObject:@{}]); + + // Null + args[0] = facebook::jsi::Value::null(); + module_->invokeObjCMethod( + *rt, VoidKind, "testMethodWhichTakesObject", @selector(testMethodWhichTakesObject:), args, 1); + OCMVerify(OCMTimes(1), [instance_ testMethodWhichTakesObject:nil]); +} + +@end