| // 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 <stdarg.h> | |
| #include <stdint.h> | |
| #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, "<init>", "()V"); | |
| load_method(exceptionMethods.printStreamClass, | |
| &exceptionMethods.printStreamCtor, "<init>", | |
| "(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, "<init>", | |
| "(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, "<init>", | |
| "(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, "<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); | |
| } |