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 // The `count < this->args` case is handled by the Kotlin part 145 if (count > this->args) { 146 throwNewJavaException( 147 InvalidArgsNumberException::create( 148 count, 149 this->args 150 ).get() 151 ); 152 } 153 154 auto argumentArray = env->NewObjectArray( 155 count, 156 JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz, 157 nullptr 158 ); 159 160 std::vector<jobject> result(count); 161 162 const auto getCurrentArg = [&thisValue, args, takesOwner = takesOwner]( 163 size_t index 164 ) -> const jsi::Value & { 165 if (!takesOwner) { 166 return args[index]; 167 } else { 168 if (index != 0) { 169 return args[index - 1]; 170 } 171 return thisValue; 172 } 173 }; 174 175 for (size_t argIndex = 0; argIndex < count; argIndex++) { 176 const jsi::Value &arg = getCurrentArg(argIndex); 177 auto &type = argTypes[argIndex]; 178 if (arg.isNull() || arg.isUndefined()) { 179 // If value is null or undefined, we just passes a null 180 // Kotlin code will check if expected type is nullable. 181 result[argIndex] = nullptr; 182 } else { 183 if (type->converter->canConvert(rt, arg)) { 184 auto converterValue = type->converter->convert(rt, env, moduleRegistry, arg); 185 env->SetObjectArrayElement(argumentArray, argIndex, converterValue); 186 env->DeleteLocalRef(converterValue); 187 } else { 188 auto stringRepresentation = arg.toString(rt).utf8(rt); 189 throwNewJavaException( 190 UnexpectedException::create( 191 "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get() 192 ); 193 } 194 } 195 } 196 197 return argumentArray; 198 } 199 200 MethodMetadata::MethodMetadata( 201 std::weak_ptr<react::LongLivedObjectCollection> longLivedObjectCollection, 202 std::string name, 203 bool takesOwner, 204 int args, 205 bool isAsync, 206 jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 207 jni::global_ref<jobject> &&jBodyReference 208 ) : name(std::move(name)), 209 takesOwner(takesOwner), 210 args(args), 211 isAsync(isAsync), 212 jBodyReference(std::move(jBodyReference)), 213 longLivedObjectCollection_(std::move(longLivedObjectCollection)) { 214 argTypes.reserve(args); 215 for (size_t i = 0; i < args; i++) { 216 auto expectedType = expectedArgTypes->getElement(i); 217 argTypes.push_back( 218 std::make_unique<AnyType>(std::move(expectedType)) 219 ); 220 } 221 } 222 223 MethodMetadata::MethodMetadata( 224 std::weak_ptr<react::LongLivedObjectCollection> longLivedObjectCollection, 225 std::string name, 226 bool takesOwner, 227 int args, 228 bool isAsync, 229 std::vector<std::unique_ptr<AnyType>> &&expectedArgTypes, 230 jni::global_ref<jobject> &&jBodyReference 231 ) : name(std::move(name)), 232 takesOwner(takesOwner), 233 args(args), 234 isAsync(isAsync), 235 argTypes(std::move(expectedArgTypes)), 236 jBodyReference(std::move(jBodyReference)), 237 longLivedObjectCollection_(std::move(longLivedObjectCollection)) { 238 } 239 240 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction( 241 jsi::Runtime &runtime, 242 JSIInteropModuleRegistry *moduleRegistry 243 ) { 244 if (body == nullptr) { 245 if (isAsync) { 246 body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry)); 247 } else { 248 body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry)); 249 } 250 } 251 252 return body; 253 } 254 255 jsi::Function MethodMetadata::toSyncFunction( 256 jsi::Runtime &runtime, 257 JSIInteropModuleRegistry *moduleRegistry 258 ) { 259 return jsi::Function::createFromHostFunction( 260 runtime, 261 moduleRegistry->jsRegistry->getPropNameID(runtime, name), 262 args, 263 [this, moduleRegistry]( 264 jsi::Runtime &rt, 265 const jsi::Value &thisValue, 266 const jsi::Value *args, 267 size_t count 268 ) -> jsi::Value { 269 try { 270 return this->callSync( 271 rt, 272 moduleRegistry, 273 thisValue, 274 args, 275 count 276 ); 277 } catch (jni::JniException &jniException) { 278 rethrowAsCodedError(rt, jniException); 279 } 280 }); 281 } 282 283 jni::local_ref<jobject> MethodMetadata::callJNISync( 284 JNIEnv *env, 285 jsi::Runtime &rt, 286 JSIInteropModuleRegistry *moduleRegistry, 287 const jsi::Value &thisValue, 288 const jsi::Value *args, 289 size_t count 290 ) { 291 if (this->jBodyReference == nullptr) { 292 return nullptr; 293 } 294 295 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count); 296 297 // Cast in this place is safe, cause we know that this function is promise-less. 298 auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference); 299 auto result = syncFunction->invoke( 300 convertedArgs 301 ); 302 303 env->DeleteLocalRef(convertedArgs); 304 return result; 305 } 306 307 jsi::Value MethodMetadata::callSync( 308 jsi::Runtime &rt, 309 JSIInteropModuleRegistry *moduleRegistry, 310 const jsi::Value &thisValue, 311 const jsi::Value *args, 312 size_t count 313 ) { 314 JNIEnv *env = jni::Environment::current(); 315 /** 316 * This will push a new JNI stack frame for the LocalReferences in this 317 * function call. When the stack frame for this lambda is popped, 318 * all LocalReferences are deleted. 319 */ 320 jni::JniLocalScope scope(env, (int) count); 321 322 auto result = this->callJNISync(env, rt, moduleRegistry, thisValue, args, count); 323 324 if (result == nullptr) { 325 return jsi::Value::undefined(); 326 } 327 auto unpackedResult = result.get(); 328 auto cache = JavaReferencesCache::instance(); 329 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Double").clazz)) { 330 return {jni::static_ref_cast<jni::JDouble>(result)->value()}; 331 } 332 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Integer").clazz)) { 333 return {jni::static_ref_cast<jni::JInteger>(result)->value()}; 334 } 335 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Long").clazz)) { 336 return {(double) jni::static_ref_cast<jni::JLong>(result)->value()}; 337 } 338 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/String").clazz)) { 339 return jsi::String::createFromUtf8( 340 rt, 341 jni::static_ref_cast<jni::JString>(result)->toStdString() 342 ); 343 } 344 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Boolean").clazz)) { 345 return {(bool) jni::static_ref_cast<jni::JBoolean>(result)->value()}; 346 } 347 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Float").clazz)) { 348 return {(double) jni::static_ref_cast<jni::JFloat>(result)->value()}; 349 } 350 if (env->IsInstanceOf( 351 unpackedResult, 352 cache->getJClass("com/facebook/react/bridge/WritableNativeArray").clazz 353 )) { 354 auto dynamic = jni::static_ref_cast<react::WritableNativeArray::javaobject>(result) 355 ->cthis() 356 ->consume(); 357 return jsi::valueFromDynamic(rt, dynamic); 358 } 359 if (env->IsInstanceOf( 360 unpackedResult, 361 cache->getJClass("com/facebook/react/bridge/WritableNativeMap").clazz 362 )) { 363 auto dynamic = jni::static_ref_cast<react::WritableNativeMap::javaobject>(result) 364 ->cthis() 365 ->consume(); 366 return jsi::valueFromDynamic(rt, dynamic); 367 } 368 if (env->IsInstanceOf(unpackedResult, JavaScriptModuleObject::javaClassStatic().get())) { 369 auto anonymousObject = jni::static_ref_cast<JavaScriptModuleObject::javaobject>(result) 370 ->cthis(); 371 anonymousObject->jsiInteropModuleRegistry = moduleRegistry; 372 auto jsiObject = anonymousObject->getJSIObject(rt); 373 374 jni::global_ref<jobject> globalRef = jni::make_global(result); 375 std::shared_ptr<expo::ObjectDeallocator> deallocator = std::make_shared<ObjectDeallocator>( 376 [globalRef = globalRef]() mutable { 377 globalRef.reset(); 378 }); 379 380 auto descriptor = JavaScriptObject::preparePropertyDescriptor(rt, 0); 381 descriptor.setProperty(rt, "value", jsi::Object::createFromHostObject(rt, deallocator)); 382 JavaScriptObject::defineProperty(rt, jsiObject.get(), "__expo_object_deallocator__", 383 std::move(descriptor)); 384 385 return jsi::Value(rt, *jsiObject); 386 } 387 388 return jsi::Value::undefined(); 389 } 390 391 jsi::Function MethodMetadata::toAsyncFunction( 392 jsi::Runtime &runtime, 393 JSIInteropModuleRegistry *moduleRegistry 394 ) { 395 return jsi::Function::createFromHostFunction( 396 runtime, 397 moduleRegistry->jsRegistry->getPropNameID(runtime, name), 398 args, 399 [this, moduleRegistry]( 400 jsi::Runtime &rt, 401 const jsi::Value &thisValue, 402 const jsi::Value *args, 403 size_t count 404 ) -> jsi::Value { 405 JNIEnv *env = jni::Environment::current(); 406 407 /** 408 * This will push a new JNI stack frame for the LocalReferences in this 409 * function call. When the stack frame for this lambda is popped, 410 * all LocalReferences are deleted. 411 */ 412 jni::JniLocalScope scope(env, (int) count); 413 414 auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>( 415 JSReferencesCache::JSKeys::PROMISE 416 ); 417 418 try { 419 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count); 420 auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs); 421 env->DeleteLocalRef(convertedArgs); 422 423 // Creates a JSI promise 424 jsi::Value promise = Promise.callAsConstructor( 425 rt, 426 createPromiseBody(rt, moduleRegistry, globalConvertedArgs) 427 ); 428 return promise; 429 } catch (jni::JniException &jniException) { 430 jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable(); 431 if (!unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) { 432 unboxedThrowable = UnexpectedException::create(jniException.what()); 433 } 434 435 auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable); 436 auto code = codedException->getCode(); 437 auto message = codedException->getLocalizedMessage().value_or(""); 438 439 jsi::Value promise = Promise.callAsConstructor( 440 rt, 441 jsi::Function::createFromHostFunction( 442 rt, 443 moduleRegistry->jsRegistry->getPropNameID(rt, "promiseFn"), 444 2, 445 [code, message]( 446 jsi::Runtime &rt, 447 const jsi::Value &thisVal, 448 const jsi::Value *promiseConstructorArgs, 449 size_t promiseConstructorArgCount 450 ) { 451 if (promiseConstructorArgCount != 2) { 452 throw std::invalid_argument("Promise fn arg count must be 2"); 453 } 454 455 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 456 rejectJSIFn.call( 457 rt, 458 makeCodedError( 459 rt, 460 jsi::String::createFromUtf8(rt, code), 461 jsi::String::createFromUtf8(rt, message) 462 ) 463 ); 464 return jsi::Value::undefined(); 465 } 466 ) 467 ); 468 469 return promise; 470 } 471 } 472 ); 473 } 474 475 jsi::Function MethodMetadata::createPromiseBody( 476 jsi::Runtime &runtime, 477 JSIInteropModuleRegistry *moduleRegistry, 478 jobjectArray globalArgs 479 ) { 480 return jsi::Function::createFromHostFunction( 481 runtime, 482 moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"), 483 2, 484 [this, globalArgs, moduleRegistry]( 485 jsi::Runtime &rt, 486 const jsi::Value &thisVal, 487 const jsi::Value *promiseConstructorArgs, 488 size_t promiseConstructorArgCount 489 ) { 490 if (promiseConstructorArgCount != 2) { 491 throw std::invalid_argument("Promise fn arg count must be 2"); 492 } 493 494 jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt); 495 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 496 497 jobject resolve = createJavaCallbackFromJSIFunction( 498 std::move(resolveJSIFn), 499 longLivedObjectCollection_, 500 rt, 501 moduleRegistry 502 ).release(); 503 504 jobject reject = createJavaCallbackFromJSIFunction( 505 std::move(rejectJSIFn), 506 longLivedObjectCollection_, 507 rt, 508 moduleRegistry, 509 true 510 ).release(); 511 512 JNIEnv *env = jni::Environment::current(); 513 514 auto &jPromise = JavaReferencesCache::instance()->getJClass( 515 "expo/modules/kotlin/jni/PromiseImpl"); 516 jmethodID jPromiseConstructor = jPromise.getMethod( 517 "<init>", 518 "(Lexpo/modules/kotlin/jni/JavaCallback;Lexpo/modules/kotlin/jni/JavaCallback;)V" 519 ); 520 521 // Creates a promise object 522 jobject promise = env->NewObject( 523 jPromise.clazz, 524 jPromiseConstructor, 525 resolve, 526 reject 527 ); 528 529 // Cast in this place is safe, cause we know that this function expects promise. 530 auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference); 531 asyncFunction->invoke( 532 globalArgs, 533 promise 534 ); 535 536 // We have to remove the local reference to the promise object. 537 // It doesn't mean that the promise will be deallocated, but rather that we move 538 // the ownership to the `JNIAsyncFunctionBody`. 539 env->DeleteLocalRef(promise); 540 env->DeleteGlobalRef(globalArgs); 541 542 return jsi::Value::undefined(); 543 } 544 ); 545 } 546 } // namespace expo 547