// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include #include #include "dartjni.h" #if !defined(_WIN32) pthread_key_t tlsKey; pthread_mutex_t spawnLock = PTHREAD_MUTEX_INITIALIZER; #endif int8_t captureStackTraceOnRelease = 0; FFI_PLUGIN_EXPORT void setCaptureStackTraceOnRelease(int8_t value) { captureStackTraceOnRelease = value; } FFI_PLUGIN_EXPORT int8_t getCaptureStackTraceOnRelease() { return captureStackTraceOnRelease; } jclass FindClassUnchecked(const char* name) { attach_thread(); jclass cls; load_class_platform(&cls, name); if (!(*jniEnv)->ExceptionCheck(jniEnv)) { cls = to_global_ref(cls); } return cls; }; /// Load class through platform-specific mechanism. /// /// Currently uses application classloader on android, /// and JNIEnv->FindClass on other platforms. FFI_PLUGIN_EXPORT JniClassLookupResult FindClass(const char* name) { JniClassLookupResult result = {NULL, NULL}; result.value = FindClassUnchecked(name); result.exception = check_exception(); return result; } /// Stores class and method references for obtaining exception details typedef struct JniExceptionMethods { jclass objectClass, exceptionClass, printStreamClass; jclass byteArrayOutputStreamClass; jmethodID toStringMethod, printStackTraceMethod; jmethodID byteArrayOutputStreamCtor, printStreamCtor; } JniExceptionMethods; // Context and shared global state. Initialized once or if thread-local, initialized once in a thread. JniContext jni_context = { .jvm = NULL, .classLoader = NULL, .loadClassMethod = NULL, }; JniContext* jni = &jni_context; THREAD_LOCAL JNIEnv* jniEnv = NULL; JniExceptionMethods exceptionMethods; void init() { #if !defined(_WIN32) // Init TLS keys. pthread_key_create(&tlsKey, detach_thread); #endif // Init locks. init_lock(&jni_context.locks.classLoadingLock); // Init exception handling classes and methods. exceptionMethods.objectClass = FindClassUnchecked("java/lang/Object"); exceptionMethods.exceptionClass = FindClassUnchecked("java/lang/Exception"); exceptionMethods.printStreamClass = FindClassUnchecked("java/io/PrintStream"); exceptionMethods.byteArrayOutputStreamClass = FindClassUnchecked("java/io/ByteArrayOutputStream"); load_method(exceptionMethods.objectClass, &exceptionMethods.toStringMethod, "toString", "()Ljava/lang/String;"); load_method(exceptionMethods.exceptionClass, &exceptionMethods.printStackTraceMethod, "printStackTrace", "(Ljava/io/PrintStream;)V"); load_method(exceptionMethods.byteArrayOutputStreamClass, &exceptionMethods.byteArrayOutputStreamCtor, "", "()V"); load_method(exceptionMethods.printStreamClass, &exceptionMethods.printStreamCtor, "", "(Ljava/io/OutputStream;)V"); } void deinit() { #if !defined(_WIN32) // Delete TLS keys. pthread_key_delete(tlsKey); #endif // Destroy locks. destroy_lock(&jni_context.locks.classLoadingLock); // Delete references to exception handling classes. if (jniEnv != NULL) { (*jniEnv)->DeleteGlobalRef(jniEnv, exceptionMethods.objectClass); (*jniEnv)->DeleteGlobalRef(jniEnv, exceptionMethods.exceptionClass); (*jniEnv)->DeleteGlobalRef(jniEnv, exceptionMethods.printStreamClass); (*jniEnv)->DeleteGlobalRef(jniEnv, exceptionMethods.byteArrayOutputStreamClass); } } JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* jvm, void* reserved) { deinit(); } /// Get JVM associated with current process. /// Returns NULL if no JVM is running. FFI_PLUGIN_EXPORT JavaVM* GetJavaVM() { return jni_context.jvm; } // Android specifics FFI_PLUGIN_EXPORT jobject GetClassLoader() { attach_thread(); return (*jniEnv)->NewGlobalRef(jniEnv, jni_context.classLoader); } // JNI Initialization #if defined(__ANDROID__) JNIEXPORT void JNICALL Java_com_github_dart_1lang_jni_JniPlugin_setClassLoader(JNIEnv* env, jclass clazz, jobject classLoader) { jniEnv = env; (*env)->GetJavaVM(env, &jni_context.jvm); jni_context.classLoader = (*env)->NewGlobalRef(env, classLoader); jclass classLoaderClass = (*env)->GetObjectClass(env, classLoader); jni_context.loadClassMethod = (*env)->GetMethodID(env, classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); init(); } // Sometimes you may get linker error trying to link JNI_CreateJavaVM APIs // on Android NDK. So IFDEF is required. #else #ifdef _WIN32 SRWLOCK spawnLock = SRWLOCK_INIT; BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { switch (fdwReason) { case DLL_THREAD_DETACH: if (jniEnv != NULL) { detach_thread(jniEnv); } break; } return TRUE; } #endif FFI_PLUGIN_EXPORT JniErrorCode SpawnJvm(JavaVMInitArgs* initArgs) { if (jni_context.jvm != NULL) { return SINGLETON_EXISTS; } acquire_lock(&spawnLock); // Init may have happened in the meanwhile. if (jni_context.jvm != NULL) { release_lock(&spawnLock); return SINGLETON_EXISTS; } JavaVMOption jvmopt[1]; char class_path[] = "-Djava.class.path=."; jvmopt[0].optionString = class_path; JavaVMInitArgs vmArgs; if (!initArgs) { vmArgs.version = JNI_VERSION_1_6; vmArgs.nOptions = 1; vmArgs.options = jvmopt; vmArgs.ignoreUnrecognized = JNI_TRUE; initArgs = &vmArgs; } const long flag = JNI_CreateJavaVM(&jni_context.jvm, &jniEnv, initArgs); if (flag != JNI_OK) { return flag; } init(); release_lock(&spawnLock); return JNI_OK; } #endif FFI_PLUGIN_EXPORT JniExceptionDetails GetExceptionDetails(jthrowable exception) { JniExceptionDetails details; details.message = (*jniEnv)->CallObjectMethod( jniEnv, exception, exceptionMethods.toStringMethod); // No exception is thrown from toString. (*jniEnv)->ExceptionClear(jniEnv); jobject buffer = (*jniEnv)->NewObject(jniEnv, exceptionMethods.byteArrayOutputStreamClass, exceptionMethods.byteArrayOutputStreamCtor); jobject printStream = (*jniEnv)->NewObject(jniEnv, exceptionMethods.printStreamClass, exceptionMethods.printStreamCtor, buffer); (*jniEnv)->CallVoidMethod( jniEnv, exception, exceptionMethods.printStackTraceMethod, printStream); // No exception is thrown from printStackTrace. (*jniEnv)->ExceptionClear(jniEnv); details.stacktrace = (*jniEnv)->CallObjectMethod( jniEnv, buffer, exceptionMethods.toStringMethod); // No exception is thrown from toString. (*jniEnv)->ExceptionClear(jniEnv); details.message = to_global_ref(details.message); details.stacktrace = to_global_ref(details.stacktrace); return details; } // These will not be required after migrating to Dart-only bindings. FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr() { return jni; } FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv() { if (jni_context.jvm == NULL) { return NULL; } attach_thread(); return jniEnv; } FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data) { return Dart_InitializeApiDL(data); } FFI_PLUGIN_EXPORT int64_t GetMainPortId() { return Dart_GetMainPortId_DL(); } FFI_PLUGIN_EXPORT int64_t GetCurrentIsolateId() { return (int64_t)Dart_CurrentIsolate_DL(); } // com.github.dart_lang.jni.DartException jclass _c_DartException = NULL; jmethodID _m_DartException__ctor = NULL; FFI_PLUGIN_EXPORT JniResult DartException__ctor(jstring message, jthrowable cause) { attach_thread(); load_class_global_ref( &_c_DartException, "com/github/dart_lang/jni/PortProxyBuilder$DartException"); if (_c_DartException == NULL) return (JniResult){.value = {.j = 0}, .exception = check_exception()}; load_method(_c_DartException, &_m_DartException__ctor, "", "(Ljava/lang/String;Ljava/lang/Throwable;)V"); if (_m_DartException__ctor == NULL) return (JniResult){.value = {.j = 0}, .exception = check_exception()}; jobject _result = (*jniEnv)->NewObject( jniEnv, _c_DartException, _m_DartException__ctor, message, cause); jthrowable exception = check_exception(); if (exception == NULL) { _result = to_global_ref(_result); } return (JniResult){.value = {.l = _result}, .exception = check_exception()}; } void finalizeGlobal(void* isolate_callback_data, void* peer); void doNotFinalize(void* isolate_callback_data, void* peer); void finalizeBlockingResult(void* isolate_callback_data, void* peer); JNIEXPORT void JNICALL Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith(JNIEnv* env, jclass clazz, jlong port, jobject result) { attach_thread(); Dart_CObject c_post; c_post.type = Dart_CObject_kNativePointer; c_post.value.as_native_pointer.ptr = (intptr_t)((*env)->NewGlobalRef(env, result)); c_post.value.as_native_pointer.size = 0; c_post.value.as_native_pointer.callback = finalizeGlobal; if (!Dart_PostCObject_DL(port, &c_post)) { c_post.value.as_native_pointer.callback( NULL, (void*)c_post.value.as_native_pointer.ptr); } } // com.github.dart_lang.jni.PortContinuation jclass _c_PortContinuation = NULL; jmethodID _m_PortContinuation__ctor = NULL; FFI_PLUGIN_EXPORT JniResult PortContinuation__ctor(int64_t j) { attach_thread(); load_class_global_ref(&_c_PortContinuation, "com/github/dart_lang/jni/PortContinuation"); if (_c_PortContinuation == NULL) return (JniResult){.value = {.j = 0}, .exception = check_exception()}; load_method(_c_PortContinuation, &_m_PortContinuation__ctor, "", "(J)V"); if (_m_PortContinuation__ctor == NULL) return (JniResult){.value = {.j = 0}, .exception = check_exception()}; jobject _result = (*jniEnv)->NewObject(jniEnv, _c_PortContinuation, _m_PortContinuation__ctor, j); jthrowable exception = check_exception(); if (exception == NULL) { _result = to_global_ref(_result); } return (JniResult){.value = {.l = _result}, .exception = check_exception()}; } FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object) { acquire_lock(&result->lock); result->ready = 1; result->object = object; signal_cond(&result->cond); release_lock(&result->lock); } void doNotFinalize(void* isolate_callback_data, void* peer) {} void finalizeBlockingResult(void* isolate_callback_data, void* peer) { CallbackResult* result = (CallbackResult*)peer; if (result != NULL) { resultFor(result, NULL); } } void finalizeLocal(void* isolate_callback_data, void* peer) { attach_thread(); (*jniEnv)->DeleteLocalRef(jniEnv, peer); } void finalizeGlobal(void* isolate_callback_data, void* peer) { attach_thread(); (*jniEnv)->DeleteGlobalRef(jniEnv, peer); } void finalizeWeakGlobal(void* isolate_callback_data, void* peer) { attach_thread(); (*jniEnv)->DeleteWeakGlobalRef(jniEnv, peer); } void freeBoolean(void* isolate_callback_data, void* peer) { // To match the platform implementation of Dart's calloc. free_mem(peer); } FFI_PLUGIN_EXPORT Dart_FinalizableHandle newJObjectFinalizableHandle(Dart_Handle object, jobject reference, jobjectRefType refType) { switch (refType) { case JNIInvalidRefType: return Dart_NewFinalizableHandle_DL(object, reference, 0, doNotFinalize); case JNILocalRefType: return Dart_NewFinalizableHandle_DL(object, reference, 0, finalizeLocal); case JNIGlobalRefType: return Dart_NewFinalizableHandle_DL(object, reference, 0, finalizeGlobal); case JNIWeakGlobalRefType: return Dart_NewFinalizableHandle_DL(object, reference, 0, finalizeWeakGlobal); } return NULL; // Never happens. } FFI_PLUGIN_EXPORT Dart_FinalizableHandle newBooleanFinalizableHandle(Dart_Handle object, bool* reference) { return Dart_NewFinalizableHandle_DL(object, reference, 1, freeBoolean); } void freeStackTracePtr(void* isolate_callback_data, void* peer) { char** ptr = (char**)peer; if (*ptr != NULL) { free_mem(*ptr); } free_mem(peer); } FFI_PLUGIN_EXPORT Dart_FinalizableHandle newStackTraceFinalizableHandle(Dart_Handle object, char* reference) { return Dart_NewFinalizableHandle_DL(object, reference, 1, freeStackTracePtr); } FFI_PLUGIN_EXPORT void deleteFinalizableHandle(Dart_FinalizableHandle finalizableHandle, Dart_Handle object) { Dart_DeleteFinalizableHandle_DL(finalizableHandle, object); } jclass _c_Object = NULL; jclass _c_Long = NULL; jmethodID _m_Long_init = NULL; JNIEXPORT jobjectArray JNICALL Java_com_github_dart_1lang_jni_PortProxyBuilder__1invoke( JNIEnv* env, jclass clazz, jlong port, jlong isolateId, jlong mainPortId, jlong functionPtr, jobject proxy, jstring methodDescriptor, jobjectArray args, jboolean isBlocking) { CallbackResult* result = NULL; if (isBlocking) { result = (CallbackResult*)malloc(sizeof(CallbackResult)); } Dart_Isolate currentIsolate = Dart_CurrentIsolate_DL(); bool alreadyEnteredTargetIsolate = currentIsolate == (Dart_Isolate)isolateId; bool mayEnterIsolate = currentIsolate == NULL && Dart_GetCurrentThreadOwnsIsolate_DL != NULL && Dart_GetCurrentThreadOwnsIsolate_DL((Dart_Port)mainPortId); if (isBlocking && (alreadyEnteredTargetIsolate || mayEnterIsolate)) { // Current thread owns the isolate associated with mainPortId, or we're // already in it. Invoke the callback synchronously. if (mayEnterIsolate) { Dart_EnterIsolate_DL((Dart_Isolate)isolateId); } typedef jobject (*DartCallback)(uint64_t, jobject, jobject); result->object = ((DartCallback)functionPtr)( port, (*env)->NewGlobalRef(env, methodDescriptor), (*env)->NewGlobalRef(env, args)); if (mayEnterIsolate) { Dart_ExitIsolate_DL(); } } else { // Otherwise invoke the callback asynchronously (via a port). if (isBlocking) { init_lock(&result->lock); init_cond(&result->cond); acquire_lock(&result->lock); result->ready = 0; result->object = NULL; } Dart_CObject c_result; c_result.type = Dart_CObject_kNativePointer; c_result.value.as_native_pointer.ptr = (intptr_t)result; c_result.value.as_native_pointer.size = 0; c_result.value.as_native_pointer.callback = isBlocking ? finalizeBlockingResult : doNotFinalize; Dart_CObject c_method; c_method.type = Dart_CObject_kNativePointer; c_method.value.as_native_pointer.ptr = (intptr_t)((*env)->NewGlobalRef(env, methodDescriptor)); c_method.value.as_native_pointer.size = 0; c_method.value.as_native_pointer.callback = finalizeGlobal; Dart_CObject c_args; c_args.type = Dart_CObject_kNativePointer; c_args.value.as_native_pointer.ptr = (intptr_t)((*env)->NewGlobalRef(env, args)); c_args.value.as_native_pointer.size = 0; c_args.value.as_native_pointer.callback = finalizeGlobal; Dart_CObject* c_post_arr[] = {&c_result, &c_method, &c_args}; Dart_CObject c_post; c_post.type = Dart_CObject_kArray; c_post.value.as_array.values = c_post_arr; c_post.value.as_array.length = sizeof(c_post_arr) / sizeof(c_post_arr[0]); if (!Dart_PostCObject_DL(port, &c_post)) { if (isBlocking) { result->ready = 1; result->object = NULL; } (*env)->DeleteGlobalRef(env, (void*)c_method.value.as_native_pointer.ptr); (*env)->DeleteGlobalRef(env, (void*)c_args.value.as_native_pointer.ptr); } if (isBlocking) { while (!result->ready) { wait_for(&result->cond, &result->lock); } release_lock(&result->lock); destroy_lock(&result->lock); destroy_cond(&result->cond); } } if (!isBlocking) { // No result is created in this case, there is nothing to clean up either. return NULL; } // Returning an array of length 2. // [0]: The result pointer, used for cleaning up the global reference, and // freeing the memory since we passed the ownership to Java. // [1]: The returned object. attach_thread(); load_class_global_ref(&_c_Object, "java/lang/Object"); load_class_global_ref(&_c_Long, "java/lang/Long"); load_method(_c_Long, &_m_Long_init, "", "(J)V"); jobject first = (*env)->NewObject(env, _c_Long, _m_Long_init, (jlong)result); jobject second = result->object; jobjectArray arr = (*env)->NewObjectArray(env, 2, _c_Object, NULL); (*env)->SetObjectArrayElement(env, arr, 0, first); (*env)->SetObjectArrayElement(env, arr, 1, second); return arr; } JNIEXPORT void JNICALL Java_com_github_dart_1lang_jni_PortProxyBuilder__1cleanUp(JNIEnv* env, jclass clazz, jlong resultPtr) { CallbackResult* result = (CallbackResult*)resultPtr; (*env)->DeleteGlobalRef(env, result->object); free(result); } JNIEXPORT void JNICALL Java_com_github_dart_1lang_jni_PortCleaner_clean(JNIEnv* env, jclass clazz, jlong port) { Dart_CObject close_signal; close_signal.type = Dart_CObject_kNull; Dart_PostCObject_DL(port, &close_signal); } JNIEXPORT jobject JNICALL Java_com_github_dart_1lang_jni_JniUtils_fromReferenceAddress(JNIEnv* env, jclass clazz, jlong id) { attach_thread(); return (jobject)(id); }