123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 |
- /*
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
- #import "RCTModuleMethod.h"
- #import <objc/message.h>
- #import "RCTAssert.h"
- #import "RCTBridge+Private.h"
- #import "RCTBridge.h"
- #import "RCTConvert.h"
- #import "RCTCxxConvert.h"
- #import "RCTLog.h"
- #import "RCTManagedPointer.h"
- #import "RCTParserUtils.h"
- #import "RCTProfile.h"
- #import "RCTUtils.h"
- typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id);
- /**
- * Get the converter function for the specified type
- */
- static SEL selectorForType(NSString *type)
- {
- const char *input = type.UTF8String;
- return NSSelectorFromString([RCTParseType(&input) stringByAppendingString:@":"]);
- }
- @implementation RCTMethodArgument
- - (instancetype)initWithType:(NSString *)type nullability:(RCTNullability)nullability unused:(BOOL)unused
- {
- if (self = [super init]) {
- _type = [type copy];
- _nullability = nullability;
- _unused = unused;
- }
- return self;
- }
- @end
- @implementation RCTModuleMethod {
- Class _moduleClass;
- const RCTMethodInfo *_methodInfo;
- NSString *_JSMethodName;
- SEL _selector;
- NSInvocation *_invocation;
- NSArray<RCTArgumentBlock> *_argumentBlocks;
- NSMutableArray *_retainedObjects;
- }
- static void RCTLogArgumentError(RCTModuleMethod *method, NSUInteger index, id valueOrType, const char *issue)
- {
- RCTLogError(
- @"Argument %tu (%@) of %@.%s %s",
- index,
- valueOrType,
- RCTBridgeModuleNameForClass(method->_moduleClass),
- method.JSMethodName,
- issue);
- }
- RCT_NOT_IMPLEMENTED(-(instancetype)init)
- RCT_EXTERN_C_BEGIN
- // returns YES if the selector ends in a colon (indicating that there is at
- // least one argument, and maybe more selector parts) or NO if it doesn't.
- static BOOL RCTParseSelectorPart(const char **input, NSMutableString *selector)
- {
- NSString *selectorPart;
- if (RCTParseSelectorIdentifier(input, &selectorPart)) {
- [selector appendString:selectorPart];
- }
- RCTSkipWhitespace(input);
- if (RCTReadChar(input, ':')) {
- [selector appendString:@":"];
- RCTSkipWhitespace(input);
- return YES;
- }
- return NO;
- }
- static BOOL RCTParseUnused(const char **input)
- {
- return RCTReadString(input, "__attribute__((unused))") || RCTReadString(input, "__attribute__((__unused__))") ||
- RCTReadString(input, "__unused");
- }
- static RCTNullability RCTParseNullability(const char **input)
- {
- if (RCTReadString(input, "nullable")) {
- return RCTNullable;
- } else if (RCTReadString(input, "nonnull")) {
- return RCTNonnullable;
- }
- return RCTNullabilityUnspecified;
- }
- static RCTNullability RCTParseNullabilityPostfix(const char **input)
- {
- if (RCTReadString(input, "_Nullable") || RCTReadString(input, "__nullable")) {
- return RCTNullable;
- } else if (RCTReadString(input, "_Nonnull") || RCTReadString(input, "__nonnull")) {
- return RCTNonnullable;
- }
- return RCTNullabilityUnspecified;
- }
- // returns YES if execution is safe to proceed (enqueue callback invocation), NO if callback has already been invoked
- #if RCT_DEBUG
- static BOOL checkCallbackMultipleInvocations(BOOL *didInvoke)
- {
- if (*didInvoke) {
- RCTFatal(RCTErrorWithMessage(
- @"Illegal callback invocation from native module. This callback type only permits a single invocation from native code."));
- return NO;
- } else {
- *didInvoke = YES;
- return YES;
- }
- }
- #endif
- NSString *RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **arguments)
- {
- RCTSkipWhitespace(&input);
- NSMutableArray *args;
- NSMutableString *selector = [NSMutableString new];
- while (RCTParseSelectorPart(&input, selector)) {
- if (!args) {
- args = [NSMutableArray new];
- }
- // Parse type
- if (RCTReadChar(&input, '(')) {
- RCTSkipWhitespace(&input);
- // 5 cases that both nullable and __unused exist
- // 1: foo:(nullable __unused id)foo 2: foo:(nullable id __unused)foo
- // 3: foo:(__unused id _Nullable)foo 4: foo:(id __unused _Nullable)foo
- // 5: foo:(id _Nullable __unused)foo
- RCTNullability nullability = RCTParseNullability(&input);
- RCTSkipWhitespace(&input);
- BOOL unused = RCTParseUnused(&input);
- RCTSkipWhitespace(&input);
- NSString *type = RCTParseType(&input);
- RCTSkipWhitespace(&input);
- if (nullability == RCTNullabilityUnspecified) {
- nullability = RCTParseNullabilityPostfix(&input);
- RCTSkipWhitespace(&input);
- if (!unused) {
- unused = RCTParseUnused(&input);
- RCTSkipWhitespace(&input);
- if (unused && nullability == RCTNullabilityUnspecified) {
- nullability = RCTParseNullabilityPostfix(&input);
- RCTSkipWhitespace(&input);
- }
- }
- } else if (!unused) {
- unused = RCTParseUnused(&input);
- RCTSkipWhitespace(&input);
- }
- [args addObject:[[RCTMethodArgument alloc] initWithType:type nullability:nullability unused:unused]];
- RCTSkipWhitespace(&input);
- RCTReadChar(&input, ')');
- RCTSkipWhitespace(&input);
- } else {
- // Type defaults to id if unspecified
- [args addObject:[[RCTMethodArgument alloc] initWithType:@"id" nullability:RCTNullable unused:NO]];
- }
- // Argument name
- RCTParseArgumentIdentifier(&input, NULL);
- RCTSkipWhitespace(&input);
- }
- *arguments = [args copy];
- return selector;
- }
- RCT_EXTERN_C_END
- - (instancetype)initWithExportedMethod:(const RCTMethodInfo *)exportedMethod moduleClass:(Class)moduleClass
- {
- if (self = [super init]) {
- _moduleClass = moduleClass;
- _methodInfo = exportedMethod;
- }
- return self;
- }
- - (void)processMethodSignature
- {
- NSArray<RCTMethodArgument *> *arguments;
- _selector = NSSelectorFromString(RCTParseMethodSignature(_methodInfo->objcName, &arguments));
- RCTAssert(_selector, @"%s is not a valid selector", _methodInfo->objcName);
- // Create method invocation
- NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
- RCTAssert(methodSignature, @"%s is not a recognized Objective-C method.", sel_getName(_selector));
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
- invocation.selector = _selector;
- _invocation = invocation;
- NSMutableArray *retainedObjects = [NSMutableArray array];
- _retainedObjects = retainedObjects;
- // Process arguments
- NSUInteger numberOfArguments = methodSignature.numberOfArguments;
- NSMutableArray<RCTArgumentBlock> *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
- #if RCT_DEBUG
- __weak RCTModuleMethod *weakSelf = self;
- #endif
- #define RCT_RETAINED_ARG_BLOCK(_logic) \
- [argumentBlocks addObject:^(__unused __weak RCTBridge * bridge, NSUInteger index, id json) { \
- _logic [invocation setArgument:&value atIndex:(index) + 2]; \
- if (value) { \
- [retainedObjects addObject:value]; \
- } \
- return YES; \
- }]
- #define __PRIMITIVE_CASE(_type, _nullable) \
- { \
- isNullableType = _nullable; \
- _type (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend; \
- [argumentBlocks addObject:^(__unused RCTBridge * bridge, NSUInteger index, id json) { \
- _type value = convert([RCTConvert class], selector, json); \
- [invocation setArgument:&value atIndex:(index) + 2]; \
- return YES; \
- }]; \
- break; \
- }
- #define PRIMITIVE_CASE(_type) __PRIMITIVE_CASE(_type, NO)
- #define NULLABLE_PRIMITIVE_CASE(_type) __PRIMITIVE_CASE(_type, YES)
- // Explicitly copy the block
- #define __COPY_BLOCK(block...) \
- id value = [block copy]; \
- if (value) { \
- [retainedObjects addObject:value]; \
- }
- #if RCT_DEBUG
- #define BLOCK_CASE(_block_args, _block) \
- RCT_RETAINED_ARG_BLOCK(if (json && ![json isKindOfClass:[NSNumber class]]) { \
- RCTLogArgumentError(weakSelf, index, json, "should be a function"); \
- return NO; \
- } __block BOOL didInvoke = NO; \
- __COPY_BLOCK(^_block_args { \
- if (checkCallbackMultipleInvocations(&didInvoke)) \
- _block \
- });)
- #else
- #define BLOCK_CASE(_block_args, _block) \
- RCT_RETAINED_ARG_BLOCK(__COPY_BLOCK(^_block_args{ \
- _block});)
- #endif
- for (NSUInteger i = 2; i < numberOfArguments; i++) {
- const char *objcType = [methodSignature getArgumentTypeAtIndex:i];
- BOOL isNullableType = NO;
- RCTMethodArgument *argument = arguments[i - 2];
- NSString *typeName = argument.type;
- SEL selector = selectorForType(typeName);
- if ([RCTConvert respondsToSelector:selector]) {
- switch (objcType[0]) {
- // Primitives
- case _C_CHR:
- PRIMITIVE_CASE(char)
- case _C_UCHR:
- PRIMITIVE_CASE(unsigned char)
- case _C_SHT:
- PRIMITIVE_CASE(short)
- case _C_USHT:
- PRIMITIVE_CASE(unsigned short)
- case _C_INT:
- PRIMITIVE_CASE(int)
- case _C_UINT:
- PRIMITIVE_CASE(unsigned int)
- case _C_LNG:
- PRIMITIVE_CASE(long)
- case _C_ULNG:
- PRIMITIVE_CASE(unsigned long)
- case _C_LNG_LNG:
- PRIMITIVE_CASE(long long)
- case _C_ULNG_LNG:
- PRIMITIVE_CASE(unsigned long long)
- case _C_FLT:
- PRIMITIVE_CASE(float)
- case _C_DBL:
- PRIMITIVE_CASE(double)
- case _C_BOOL:
- PRIMITIVE_CASE(BOOL)
- case _C_SEL:
- NULLABLE_PRIMITIVE_CASE(SEL)
- case _C_CHARPTR:
- NULLABLE_PRIMITIVE_CASE(const char *)
- case _C_PTR:
- NULLABLE_PRIMITIVE_CASE(void *)
- case _C_ID: {
- isNullableType = YES;
- id (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
- RCT_RETAINED_ARG_BLOCK(id value = convert([RCTConvert class], selector, json););
- break;
- }
- case _C_STRUCT_B: {
- NSMethodSignature *typeSignature = [RCTConvert methodSignatureForSelector:selector];
- NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
- typeInvocation.selector = selector;
- typeInvocation.target = [RCTConvert class];
- [argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) {
- void *returnValue = malloc(typeSignature.methodReturnLength);
- if (!returnValue) {
- // CWE - 391 : Unchecked error condition
- // https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html
- // https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c
- abort();
- }
- [typeInvocation setArgument:&json atIndex:2];
- [typeInvocation invoke];
- [typeInvocation getReturnValue:returnValue];
- [invocation setArgument:returnValue atIndex:index + 2];
- free(returnValue);
- return YES;
- }];
- break;
- }
- default: {
- static const char *blockType = @encode(__typeof__(^{
- }));
- if (!strcmp(objcType, blockType)) {
- BLOCK_CASE((NSArray * args), { [bridge enqueueCallback:json args:args]; });
- } else {
- RCTLogError(@"Unsupported argument type '%@' in method %@.", typeName, [self methodName]);
- }
- }
- }
- } else if ([typeName isEqualToString:@"RCTResponseSenderBlock"]) {
- BLOCK_CASE((NSArray * args), { [bridge enqueueCallback:json args:args]; });
- } else if ([typeName isEqualToString:@"RCTResponseErrorBlock"]) {
- BLOCK_CASE((NSError * error), { [bridge enqueueCallback:json args:@[ RCTJSErrorFromNSError(error) ]]; });
- } else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) {
- RCTAssert(
- i == numberOfArguments - 2,
- @"The RCTPromiseResolveBlock must be the second to last parameter in %@",
- [self methodName]);
- BLOCK_CASE((id result), { [bridge enqueueCallback:json args:result ? @[ result ] : @[]]; });
- } else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) {
- RCTAssert(
- i == numberOfArguments - 1, @"The RCTPromiseRejectBlock must be the last parameter in %@", [self methodName]);
- BLOCK_CASE((NSString * code, NSString * message, NSError * error), {
- NSDictionary *errorJSON = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
- [bridge enqueueCallback:json args:@[ errorJSON ]];
- });
- } else if ([typeName hasPrefix:@"JS::"]) {
- NSString *selectorNameForCxxType =
- [[typeName stringByReplacingOccurrencesOfString:@"::" withString:@"_"] stringByAppendingString:@":"];
- selector = NSSelectorFromString(selectorNameForCxxType);
- [argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) {
- RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
- RCTManagedPointer *box = convert([RCTCxxConvert class], selector, json);
- void *pointer = box.voidPointer;
- [invocation setArgument:&pointer atIndex:index + 2];
- [retainedObjects addObject:box];
- return YES;
- }];
- } else {
- // Unknown argument type
- RCTLogError(
- @"Unknown argument type '%@' in method %@. Extend RCTConvert to support this type.",
- typeName,
- [self methodName]);
- }
- #if RCT_DEBUG
- RCTNullability nullability = argument.nullability;
- if (!isNullableType) {
- if (nullability == RCTNullable) {
- RCTLogArgumentError(
- weakSelf,
- i - 2,
- typeName,
- "is marked as "
- "nullable, but is not a nullable type.");
- }
- nullability = RCTNonnullable;
- }
- /**
- * Special case - Numbers are not nullable in Android, so we
- * don't support this for now. In future we may allow it.
- */
- if ([typeName isEqualToString:@"NSNumber"]) {
- BOOL unspecified = (nullability == RCTNullabilityUnspecified);
- if (!argument.unused && (nullability == RCTNullable || unspecified)) {
- RCTLogArgumentError(
- weakSelf,
- i - 2,
- typeName,
- [unspecified ? @"has unspecified nullability" : @"is marked as nullable"
- stringByAppendingString:@" but React requires that all NSNumber "
- "arguments are explicitly marked as `nonnull` to ensure "
- "compatibility with Android."]
- .UTF8String);
- }
- nullability = RCTNonnullable;
- }
- if (nullability == RCTNonnullable) {
- RCTArgumentBlock oldBlock = argumentBlocks[i - 2];
- argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSUInteger index, id json) {
- if (json != nil) {
- if (!oldBlock(bridge, index, json)) {
- return NO;
- }
- if (isNullableType) {
- // Check converted value wasn't null either, as method probably
- // won't gracefully handle a nil value for a nonull argument
- void *value;
- [invocation getArgument:&value atIndex:index + 2];
- if (value == NULL) {
- return NO;
- }
- }
- return YES;
- }
- RCTLogArgumentError(weakSelf, index, typeName, "must not be null");
- return NO;
- };
- }
- #endif
- }
- #if RCT_DEBUG
- const char *objcType = _invocation.methodSignature.methodReturnType;
- if (_methodInfo->isSync && objcType[0] != _C_ID) {
- RCTLogError(
- @"Return type of %@.%s should be (id) as the method is \"sync\"",
- RCTBridgeModuleNameForClass(_moduleClass),
- self.JSMethodName);
- }
- #endif
- _argumentBlocks = argumentBlocks;
- }
- - (SEL)selector
- {
- if (_selector == NULL) {
- RCT_PROFILE_BEGIN_EVENT(
- RCTProfileTagAlways,
- @"",
- (@{@"module" : NSStringFromClass(_moduleClass), @"method" : @(_methodInfo->objcName)}));
- [self processMethodSignature];
- RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
- }
- return _selector;
- }
- - (const char *)JSMethodName
- {
- NSString *methodName = _JSMethodName;
- if (!methodName) {
- const char *jsName = _methodInfo->jsName;
- if (jsName && strlen(jsName) > 0) {
- methodName = @(jsName);
- } else {
- methodName = @(_methodInfo->objcName);
- NSRange colonRange = [methodName rangeOfString:@":"];
- if (colonRange.location != NSNotFound) {
- methodName = [methodName substringToIndex:colonRange.location];
- }
- methodName = [methodName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
- RCTAssert(
- methodName.length,
- @"%s is not a valid JS function name, please"
- " supply an alternative using RCT_REMAP_METHOD()",
- _methodInfo->objcName);
- }
- _JSMethodName = methodName;
- }
- return methodName.UTF8String;
- }
- - (RCTFunctionType)functionType
- {
- if (strstr(_methodInfo->objcName, "RCTPromise") != NULL) {
- RCTAssert(!_methodInfo->isSync, @"Promises cannot be used in sync functions");
- return RCTFunctionTypePromise;
- } else if (_methodInfo->isSync) {
- return RCTFunctionTypeSync;
- } else {
- return RCTFunctionTypeNormal;
- }
- }
- - (id)invokeWithBridge:(RCTBridge *)bridge module:(id)module arguments:(NSArray *)arguments
- {
- if (_argumentBlocks == nil) {
- [self processMethodSignature];
- }
- #if RCT_DEBUG
- // Sanity check
- RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
- %@ on a module of class %@", [self methodName], [module class]);
- // Safety check
- if (arguments.count != _argumentBlocks.count) {
- NSInteger actualCount = arguments.count;
- NSInteger expectedCount = _argumentBlocks.count;
- // Subtract the implicit Promise resolver and rejecter functions for implementations of async functions
- if (self.functionType == RCTFunctionTypePromise) {
- actualCount -= 2;
- expectedCount -= 2;
- }
- RCTLogError(
- @"%@.%s was called with %lld arguments but expects %lld arguments. "
- @"If you haven\'t changed this method yourself, this usually means that "
- @"your versions of the native code and JavaScript code are out of sync. "
- @"Updating both should make this error go away.",
- RCTBridgeModuleNameForClass(_moduleClass),
- self.JSMethodName,
- (long long)actualCount,
- (long long)expectedCount);
- return nil;
- }
- #endif
- // Set arguments
- NSUInteger index = 0;
- for (id json in arguments) {
- RCTArgumentBlock block = _argumentBlocks[index];
- if (!block(bridge, index, RCTNilIfNull(json))) {
- // Invalid argument, abort
- RCTLogArgumentError(self, index, json, "could not be processed. Aborting method call.");
- return nil;
- }
- index++;
- }
- // Invoke method
- #ifdef RCT_MAIN_THREAD_WATCH_DOG_THRESHOLD
- if (RCTIsMainQueue()) {
- CFTimeInterval start = CACurrentMediaTime();
- [_invocation invokeWithTarget:module];
- CFTimeInterval duration = CACurrentMediaTime() - start;
- if (duration > RCT_MAIN_THREAD_WATCH_DOG_THRESHOLD) {
- RCTLogWarn(
- @"Main Thread Watchdog: Invocation of %@ blocked the main thread for %dms. "
- "Consider using background-threaded modules and asynchronous calls "
- "to spend less time on the main thread and keep the app's UI responsive.",
- [self methodName],
- (int)(duration * 1000));
- }
- } else {
- [_invocation invokeWithTarget:module];
- }
- #else
- [_invocation invokeWithTarget:module];
- #endif
- [_retainedObjects removeAllObjects];
- if (_methodInfo->isSync) {
- void *returnValue;
- [_invocation getReturnValue:&returnValue];
- return (__bridge id)returnValue;
- }
- return nil;
- }
- - (NSString *)methodName
- {
- if (!_selector) {
- [self processMethodSignature];
- }
- return [NSString stringWithFormat:@"-[%@ %s]", _moduleClass, sel_getName(_selector)];
- }
- - (NSString *)description
- {
- return [NSString stringWithFormat:@"<%@: %p; exports %@ as %s(); type: %s>",
- [self class],
- self,
- [self methodName],
- self.JSMethodName,
- RCTFunctionDescriptorFromType(self.functionType)];
- }
- @end
|