Implement auto-conversions from primitives to Objects

Summary:
If a NativeModule method requires an optional boolean argument, our codegen translates those optional booleans into `NSNumber*` instead of `BOOL`. The reason why is probably because this is the closest object analogue to `BOOL` in Objective C. The same boxing occurs with numbers. If the type of a number argument in JavaScript is optional, we'll map it to the `NSNumber*` Objective C type instead of `double`. Our existing TurboModules argument conversion code would not take this behaviour into account. Therefore, we'd try to insert a `BOOL` where the `NSInvocation` would expect a `NSNumber*`. This, in turn, would cause the app to crash. (Why would it crash at the point of NSInvocation retainArguments, I'm still not sure).

Our flow typechecking ensures that if the type of a method argument is a boolean, we pass in a boolean. Therefore, on the Native side, if we detect a boolean, we can check the type of the Native argument to see whether we should box the primitive. If the native argument type is an object, then we know it has to be an `NSNumber*` in both cases, so we simply wrap the `BOOL` or `double` in a `NSNumber*`.

Reviewed By: shergin

Differential Revision: D14679590

fbshipit-source-id: c394a878492aab8e98c71d74ec8740a94fc3a6c5
This commit is contained in:
Ramanpreet Nara
2019-03-31 02:46:30 -07:00
committed by Facebook Github Bot
parent 02562e51bd
commit 46944b7937

View File

@@ -474,29 +474,60 @@ NSInvocation *ObjCTurboModule::getMethodInvocation(
NSMutableArray *retainedObjectsForInvocation) {
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[module class] instanceMethodSignatureForSelector:selector]];
[inv setSelector:selector];
NSMethodSignature *methodSignature = [[module class] instanceMethodSignatureForSelector:selector];
for (size_t i = 0; i < count; i++) {
const jsi::Value *arg = &args[i];
const char *objCArgType = [methodSignature getArgumentTypeAtIndex:i + 2];
if (arg->isBool()) {
bool v = arg->getBool();
[inv setArgument:(void *)&v atIndex:i + 2];
} else if (arg->isNumber()) {
/**
* JS type checking ensures the Objective C argument here is either a BOOL or NSNumber*.
*/
if (objCArgType[0] == _C_ID) {
id objCArg = [NSNumber numberWithBool:v];
[inv setArgument:(void *)&objCArg atIndex:i + 2];
[retainedObjectsForInvocation addObject:objCArg];
} else {
[inv setArgument:(void *)&v atIndex:i + 2];
}
continue;
}
if (arg->isNumber()) {
double v = arg->getNumber();
[inv setArgument:(void *)&v atIndex:i + 2];
} else {
id v = convertJSIValueToObjCObject(runtime, *arg, jsInvoker);
NSString *methodNameObjc = @(methodName.c_str());
NSMethodSignature *methodSignature = [[module class] instanceMethodSignatureForSelector:selector];
const char *objcType = [methodSignature getArgumentTypeAtIndex:i];
if (objcType[0] == _C_ID) {
NSString* argumentType = getArgumentTypeName(methodNameObjc, i);
/**
* When argumentType is nil, it means that the method hasn't been wrapped with
* an RCT_EXPORT_METHOD macro. Therefore, we do not support converting the method
* arguments using RCTConvert.
*/
/**
* JS type checking ensures the Objective C argument here is either a double or NSNumber*.
*/
if (objCArgType[0] == _C_ID) {
id objCArg = [NSNumber numberWithDouble:v];
[inv setArgument:(void *)&objCArg atIndex:i + 2];
[retainedObjectsForInvocation addObject:objCArg];
} else {
[inv setArgument:(void *)&v atIndex:i + 2];
}
continue;
}
/**
* Convert arg to ObjC objects.
*/
id objCArg = convertJSIValueToObjCObject(runtime, *arg, jsInvoker);
if (objCArg) {
NSString *methodNameNSString = @(methodName.c_str());
/**
* Convert objects using RCTConvert.
*/
if (objCArgType[0] == _C_ID) {
NSString* argumentType = getArgumentTypeName(methodNameNSString, i);
if (argumentType != nil) {
NSString *rctConvertMethodName = [NSString stringWithFormat:@"%@:", argumentType];
SEL rctConvertSelector = NSSelectorFromString(rctConvertMethodName);
@@ -504,34 +535,50 @@ NSInvocation *ObjCTurboModule::getMethodInvocation(
if ([RCTConvert respondsToSelector: rctConvertSelector]) {
// Message dispatch logic from old infra
id (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
v = convert([RCTConvert class], rctConvertSelector, v);
id convertedObjCArg = convert([RCTConvert class], rctConvertSelector, objCArg);
/**
* TODO(ramanpreet):
* Investigate whether we can avoid inserting to retainedObjectsForInvocation.
* Otherwise, NSInvocation raises a BAD_ACCESS when we invoke the retainArguments method.
**/
[retainedObjectsForInvocation addObject:v];
[inv setArgument:(void *)&convertedObjCArg atIndex:i + 2];
if (convertedObjCArg) {
[retainedObjectsForInvocation addObject:convertedObjCArg];
}
continue;
}
}
}
if ([v isKindOfClass:[NSDictionary class]] && hasMethodArgConversionSelector(methodNameObjc, i)) {
SEL methodArgConversionSelector = getMethodArgConversionSelector(methodNameObjc, i);
/**
* Convert objects using RCTCxxConvert to structs.
*/
if ([objCArg isKindOfClass:[NSDictionary class]] && hasMethodArgConversionSelector(methodNameNSString, i)) {
SEL methodArgConversionSelector = getMethodArgConversionSelector(methodNameNSString, i);
// Message dispatch logic from old infra (link: https://git.io/fjf3U)
RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCTManagedPointer *box = convert([RCTCxxConvert class], methodArgConversionSelector, v);
RCTManagedPointer *box = convert([RCTCxxConvert class], methodArgConversionSelector, objCArg);
void *pointer = box.voidPointer;
[inv setArgument:&pointer atIndex:i + 2];
[retainedObjectsForInvocation addObject:box];
} else {
[inv setArgument:(void *)&v atIndex:i + 2];
continue;
}
}
/**
* Insert converted args unmodified.
*/
[inv setArgument:(void *)&objCArg atIndex:i + 2];
if (objCArg) {
[retainedObjectsForInvocation addObject:objCArg];
}
}
/**
* TODO(rsnara):
* If you remove this call, then synchronous calls that return NSDictionary's break.
* Investigate why.
*/
[inv retainArguments];
return inv;
}