1 #include "MethodMetadata.h" 2 #include "JSIInteropModuleRegistry.h" 3 #include "JavaScriptValue.h" 4 #include "JavaScriptObject.h" 5 #include "JavaScriptTypedArray.h" 6 #include "JavaReferencesCache.h" 7 #include "Exceptions.h" 8 #include "JavaCallback.h" 9 10 #include <utility> 11 #include <functional> 12 13 #include <react/jni/ReadableNativeMap.h> 14 #include <react/jni/ReadableNativeArray.h> 15 #include <react/jni/WritableNativeArray.h> 16 #include <react/jni/WritableNativeMap.h> 17 #include "JSReferencesCache.h" 18 19 namespace jni = facebook::jni; 20 namespace jsi = facebook::jsi; 21 namespace react = facebook::react; 22 23 namespace expo { 24 25 class JSI_EXPORT ObjectDeallocator : public jsi::HostObject { 26 public: 27 typedef std::function<void()> ObjectDeallocatorType; 28 29 ObjectDeallocator(ObjectDeallocatorType deallocator) : deallocator(deallocator) {}; 30 31 virtual ~ObjectDeallocator() { 32 deallocator(); 33 } 34 35 const ObjectDeallocatorType deallocator; 36 37 }; // class ObjectDeallocator 38 39 // Modified version of the RN implementation 40 // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57 41 jni::local_ref<JavaCallback::JavaPart> createJavaCallbackFromJSIFunction( 42 jsi::Function &&function, 43 std::weak_ptr<react::LongLivedObjectCollection> longLivedObjectCollection, 44 jsi::Runtime &rt, 45 JSIInteropModuleRegistry *moduleRegistry, 46 bool isRejectCallback = false 47 ) { 48 std::shared_ptr<react::CallInvoker> jsInvoker = moduleRegistry->runtimeHolder->jsInvoker; 49 auto strongLongLiveObjectCollection = longLivedObjectCollection.lock(); 50 if (!strongLongLiveObjectCollection) { 51 throw std::runtime_error("The LongLivedObjectCollection for MethodMetadata is not alive."); 52 } 53 auto weakWrapper = react::CallbackWrapper::createWeak(strongLongLiveObjectCollection, 54 std::move(function), rt, 55 std::move(jsInvoker)); 56 57 // This needs to be a shared_ptr because: 58 // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is 59 // not. 60 // 2. It cannot be weak_ptr since we need this object to live on. 61 // 3. It cannot be a value, because that would be deleted as soon as this 62 // function returns. 63 auto callbackWrapperOwner = 64 std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper); 65 66 std::function<void(folly::dynamic)> fn = 67 [ 68 weakWrapper, 69 callbackWrapperOwner = std::move(callbackWrapperOwner), 70 wrapperWasCalled = false, 71 isRejectCallback 72 ]( 73 folly::dynamic responses) mutable { 74 if (wrapperWasCalled) { 75 throw std::runtime_error( 76 "callback 2 arg cannot be called more than once"); 77 } 78 79 auto strongWrapper = weakWrapper.lock(); 80 if (!strongWrapper) { 81 return; 82 } 83 84 strongWrapper->jsInvoker().invokeAsync( 85 [ 86 weakWrapper, 87 callbackWrapperOwner = std::move(callbackWrapperOwner), 88 responses = std::move(responses), 89 isRejectCallback 90 ]() mutable { 91 auto strongWrapper2 = weakWrapper.lock(); 92 if (!strongWrapper2) { 93 return; 94 } 95 96 jsi::Value arg = jsi::valueFromDynamic(strongWrapper2->runtime(), responses); 97 if (!isRejectCallback) { 98 strongWrapper2->callback().call( 99 strongWrapper2->runtime(), 100 (const jsi::Value *) &arg, 101 (size_t) 1 102 ); 103 } else { 104 auto &rt = strongWrapper2->runtime(); 105 auto jsErrorObject = arg.getObject(rt); 106 auto errorCode = jsErrorObject.getProperty(rt, "code").asString(rt); 107 auto message = jsErrorObject.getProperty(rt, "message").asString(rt); 108 109 auto codedError = makeCodedError( 110 rt, 111 std::move(errorCode), 112 std::move(message) 113 ); 114 115 strongWrapper2->callback().call( 116 strongWrapper2->runtime(), 117 (const jsi::Value *) &codedError, 118 (size_t) 1 119 ); 120 } 121 122 callbackWrapperOwner.reset(); 123 }); 124 125 wrapperWasCalled = true; 126 }; 127 128 return JavaCallback::newObjectCxxArgs(std::move(fn)); 129 } 130 131 jobjectArray MethodMetadata::convertJSIArgsToJNI( 132 JSIInteropModuleRegistry *moduleRegistry, 133 JNIEnv *env, 134 jsi::Runtime &rt, 135 const jsi::Value &thisValue, 136 const jsi::Value *args, 137 size_t count 138 ) { 139 // This function takes the owner, so the args number is higher because we have access to the thisValue. 140 if (takesOwner) { 141 count++; 142 } 143 144 auto argumentArray = env->NewObjectArray( 145 count, 146 JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz, 147 nullptr 148 ); 149 150 std::vector<jobject> result(count); 151 152 const auto getCurrentArg = [&thisValue, args, takesOwner = takesOwner]( 153 size_t index 154 ) -> const jsi::Value & { 155 if (!takesOwner) { 156 return args[index]; 157 } else { 158 if (index != 0) { 159 return args[index - 1]; 160 } 161 return thisValue; 162 } 163 }; 164 165 for (size_t argIndex = 0; argIndex < count; argIndex++) { 166 const jsi::Value &arg = getCurrentArg(argIndex); 167 auto &type = argTypes[argIndex]; 168 if (arg.isNull() || arg.isUndefined()) { 169 // If value is null or undefined, we just passes a null 170 // Kotlin code will check if expected type is nullable. 171 result[argIndex] = nullptr; 172 } else { 173 if (type->converter->canConvert(rt, arg)) { 174 auto converterValue = type->converter->convert(rt, env, moduleRegistry, arg); 175 env->SetObjectArrayElement(argumentArray, argIndex, converterValue); 176 env->DeleteLocalRef(converterValue); 177 } else { 178 auto stringRepresentation = arg.toString(rt).utf8(rt); 179 throwNewJavaException( 180 UnexpectedException::create( 181 "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get() 182 ); 183 } 184 } 185 } 186 187 return argumentArray; 188 } 189 190 MethodMetadata::MethodMetadata( 191 std::weak_ptr<react::LongLivedObjectCollection> longLivedObjectCollection, 192 std::string name, 193 bool takesOwner, 194 int args, 195 bool isAsync, 196 jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 197 jni::global_ref<jobject> &&jBodyReference 198 ) : name(std::move(name)), 199 takesOwner(takesOwner), 200 args(args), 201 isAsync(isAsync), 202 jBodyReference(std::move(jBodyReference)), 203 longLivedObjectCollection_(std::move(longLivedObjectCollection)) { 204 argTypes.reserve(args); 205 for (size_t i = 0; i < args; i++) { 206 auto expectedType = expectedArgTypes->getElement(i); 207 argTypes.push_back( 208 std::make_unique<AnyType>(std::move(expectedType)) 209 ); 210 } 211 } 212 213 MethodMetadata::MethodMetadata( 214 std::weak_ptr<react::LongLivedObjectCollection> longLivedObjectCollection, 215 std::string name, 216 bool takesOwner, 217 int args, 218 bool isAsync, 219 std::vector<std::unique_ptr<AnyType>> &&expectedArgTypes, 220 jni::global_ref<jobject> &&jBodyReference 221 ) : name(std::move(name)), 222 takesOwner(takesOwner), 223 args(args), 224 isAsync(isAsync), 225 argTypes(std::move(expectedArgTypes)), 226 jBodyReference(std::move(jBodyReference)), 227 longLivedObjectCollection_(std::move(longLivedObjectCollection)) { 228 } 229 230 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction( 231 jsi::Runtime &runtime, 232 JSIInteropModuleRegistry *moduleRegistry 233 ) { 234 if (body == nullptr) { 235 if (isAsync) { 236 body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry)); 237 } else { 238 body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry)); 239 } 240 } 241 242 return body; 243 } 244 245 jsi::Function MethodMetadata::toSyncFunction( 246 jsi::Runtime &runtime, 247 JSIInteropModuleRegistry *moduleRegistry 248 ) { 249 return jsi::Function::createFromHostFunction( 250 runtime, 251 moduleRegistry->jsRegistry->getPropNameID(runtime, name), 252 args, 253 [this, moduleRegistry]( 254 jsi::Runtime &rt, 255 const jsi::Value &thisValue, 256 const jsi::Value *args, 257 size_t count 258 ) -> jsi::Value { 259 try { 260 return this->callSync( 261 rt, 262 moduleRegistry, 263 thisValue, 264 args, 265 count 266 ); 267 } catch (jni::JniException &jniException) { 268 rethrowAsCodedError(rt, jniException); 269 } 270 }); 271 } 272 273 jsi::Value MethodMetadata::callSync( 274 jsi::Runtime &rt, 275 JSIInteropModuleRegistry *moduleRegistry, 276 const jsi::Value &thisValue, 277 const jsi::Value *args, 278 size_t count 279 ) { 280 if (this->jBodyReference == nullptr) { 281 return jsi::Value::undefined(); 282 } 283 284 JNIEnv *env = jni::Environment::current(); 285 286 /** 287 * This will push a new JNI stack frame for the LocalReferences in this 288 * function call. When the stack frame for this lambda is popped, 289 * all LocalReferences are deleted. 290 */ 291 jni::JniLocalScope scope(env, (int) count); 292 293 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count); 294 295 // Cast in this place is safe, cause we know that this function is promise-less. 296 auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference); 297 auto result = syncFunction->invoke( 298 convertedArgs 299 ); 300 301 env->DeleteLocalRef(convertedArgs); 302 if (result == nullptr) { 303 return jsi::Value::undefined(); 304 } 305 auto unpackedResult = result.get(); 306 auto cache = JavaReferencesCache::instance(); 307 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Double").clazz)) { 308 return {jni::static_ref_cast<jni::JDouble>(result)->value()}; 309 } 310 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Integer").clazz)) { 311 return {jni::static_ref_cast<jni::JInteger>(result)->value()}; 312 } 313 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Long").clazz)) { 314 return {(double) jni::static_ref_cast<jni::JLong>(result)->value()}; 315 } 316 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/String").clazz)) { 317 return jsi::String::createFromUtf8( 318 rt, 319 jni::static_ref_cast<jni::JString>(result)->toStdString() 320 ); 321 } 322 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Boolean").clazz)) { 323 return {(bool) jni::static_ref_cast<jni::JBoolean>(result)->value()}; 324 } 325 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Float").clazz)) { 326 return {(double) jni::static_ref_cast<jni::JFloat>(result)->value()}; 327 } 328 if (env->IsInstanceOf( 329 unpackedResult, 330 cache->getJClass("com/facebook/react/bridge/WritableNativeArray").clazz 331 )) { 332 auto dynamic = jni::static_ref_cast<react::WritableNativeArray::javaobject>(result) 333 ->cthis() 334 ->consume(); 335 return jsi::valueFromDynamic(rt, dynamic); 336 } 337 if (env->IsInstanceOf( 338 unpackedResult, 339 cache->getJClass("com/facebook/react/bridge/WritableNativeMap").clazz 340 )) { 341 auto dynamic = jni::static_ref_cast<react::WritableNativeMap::javaobject>(result) 342 ->cthis() 343 ->consume(); 344 return jsi::valueFromDynamic(rt, dynamic); 345 } 346 if (env->IsInstanceOf(unpackedResult, JavaScriptModuleObject::javaClassStatic().get())) { 347 auto anonymousObject = jni::static_ref_cast<JavaScriptModuleObject::javaobject>(result) 348 ->cthis(); 349 anonymousObject->jsiInteropModuleRegistry = moduleRegistry; 350 auto jsiObject = anonymousObject->getJSIObject(rt); 351 352 jni::global_ref<jobject> globalRef = jni::make_global(result); 353 std::shared_ptr<expo::ObjectDeallocator> deallocator = std::make_shared<ObjectDeallocator>( 354 [globalRef = globalRef]() mutable { 355 globalRef.reset(); 356 }); 357 358 auto descriptor = JavaScriptObject::preparePropertyDescriptor(rt, 0); 359 descriptor.setProperty(rt, "value", jsi::Object::createFromHostObject(rt, deallocator)); 360 JavaScriptObject::defineProperty(rt, jsiObject.get(), "__expo_object_deallocator__", 361 std::move(descriptor)); 362 363 return jsi::Value(rt, *jsiObject); 364 } 365 366 return jsi::Value::undefined(); 367 } 368 369 jsi::Function MethodMetadata::toAsyncFunction( 370 jsi::Runtime &runtime, 371 JSIInteropModuleRegistry *moduleRegistry 372 ) { 373 return jsi::Function::createFromHostFunction( 374 runtime, 375 moduleRegistry->jsRegistry->getPropNameID(runtime, name), 376 args, 377 [this, moduleRegistry]( 378 jsi::Runtime &rt, 379 const jsi::Value &thisValue, 380 const jsi::Value *args, 381 size_t count 382 ) -> jsi::Value { 383 JNIEnv *env = jni::Environment::current(); 384 385 /** 386 * This will push a new JNI stack frame for the LocalReferences in this 387 * function call. When the stack frame for this lambda is popped, 388 * all LocalReferences are deleted. 389 */ 390 jni::JniLocalScope scope(env, (int) count); 391 392 auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>( 393 JSReferencesCache::JSKeys::PROMISE 394 ); 395 396 try { 397 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count); 398 auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs); 399 env->DeleteLocalRef(convertedArgs); 400 401 // Creates a JSI promise 402 jsi::Value promise = Promise.callAsConstructor( 403 rt, 404 createPromiseBody(rt, moduleRegistry, globalConvertedArgs) 405 ); 406 return promise; 407 } catch (jni::JniException &jniException) { 408 jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable(); 409 if (!unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) { 410 unboxedThrowable = UnexpectedException::create(jniException.what()); 411 } 412 413 auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable); 414 auto code = codedException->getCode(); 415 auto message = codedException->getLocalizedMessage().value_or(""); 416 417 jsi::Value promise = Promise.callAsConstructor( 418 rt, 419 jsi::Function::createFromHostFunction( 420 rt, 421 moduleRegistry->jsRegistry->getPropNameID(rt, "promiseFn"), 422 2, 423 [code, message]( 424 jsi::Runtime &rt, 425 const jsi::Value &thisVal, 426 const jsi::Value *promiseConstructorArgs, 427 size_t promiseConstructorArgCount 428 ) { 429 if (promiseConstructorArgCount != 2) { 430 throw std::invalid_argument("Promise fn arg count must be 2"); 431 } 432 433 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 434 rejectJSIFn.call( 435 rt, 436 makeCodedError( 437 rt, 438 jsi::String::createFromUtf8(rt, code), 439 jsi::String::createFromUtf8(rt, message) 440 ) 441 ); 442 return jsi::Value::undefined(); 443 } 444 ) 445 ); 446 447 return promise; 448 } 449 } 450 ); 451 } 452 453 jsi::Function MethodMetadata::createPromiseBody( 454 jsi::Runtime &runtime, 455 JSIInteropModuleRegistry *moduleRegistry, 456 jobjectArray globalArgs 457 ) { 458 return jsi::Function::createFromHostFunction( 459 runtime, 460 moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"), 461 2, 462 [this, globalArgs, moduleRegistry]( 463 jsi::Runtime &rt, 464 const jsi::Value &thisVal, 465 const jsi::Value *promiseConstructorArgs, 466 size_t promiseConstructorArgCount 467 ) { 468 if (promiseConstructorArgCount != 2) { 469 throw std::invalid_argument("Promise fn arg count must be 2"); 470 } 471 472 jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt); 473 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 474 475 jobject resolve = createJavaCallbackFromJSIFunction( 476 std::move(resolveJSIFn), 477 longLivedObjectCollection_, 478 rt, 479 moduleRegistry 480 ).release(); 481 482 jobject reject = createJavaCallbackFromJSIFunction( 483 std::move(rejectJSIFn), 484 longLivedObjectCollection_, 485 rt, 486 moduleRegistry, 487 true 488 ).release(); 489 490 JNIEnv *env = jni::Environment::current(); 491 492 auto &jPromise = JavaReferencesCache::instance()->getJClass( 493 "expo/modules/kotlin/jni/PromiseImpl"); 494 jmethodID jPromiseConstructor = jPromise.getMethod( 495 "<init>", 496 "(Lexpo/modules/kotlin/jni/JavaCallback;Lexpo/modules/kotlin/jni/JavaCallback;)V" 497 ); 498 499 // Creates a promise object 500 jobject promise = env->NewObject( 501 jPromise.clazz, 502 jPromiseConstructor, 503 resolve, 504 reject 505 ); 506 507 // Cast in this place is safe, cause we know that this function expects promise. 508 auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference); 509 asyncFunction->invoke( 510 globalArgs, 511 promise 512 ); 513 514 // We have to remove the local reference to the promise object. 515 // It doesn't mean that the promise will be deallocated, but rather that we move 516 // the ownership to the `JNIAsyncFunctionBody`. 517 env->DeleteLocalRef(promise); 518 env->DeleteGlobalRef(globalArgs); 519 520 return jsi::Value::undefined(); 521 } 522 ); 523 } 524 } // namespace expo 525