| // Copyright 2016 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // C support functions for bindings. This file is copied into the |
| // generated gomobile_bind package and compiled along with the |
| // generated binding files. |
| |
| #include <android/log.h> |
| #include <errno.h> |
| #include <jni.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <pthread.h> |
| #include "seq.h" |
| #include "_cgo_export.h" |
| |
| #define NULL_REFNUM 41 |
| |
| static JavaVM *jvm; |
| // jnienvs holds the per-thread JNIEnv* for Go threads where we called AttachCurrentThread. |
| // A pthread key destructor is supplied to call DetachCurrentThread on exit. This trick is |
| // documented in http://developer.android.com/training/articles/perf-jni.html under "Threads". |
| static pthread_key_t jnienvs; |
| |
| static jclass seq_class; |
| static jmethodID seq_getRef; |
| static jmethodID seq_decRef; |
| static jmethodID seq_incRef; |
| static jmethodID seq_incGoObjectRef; |
| static jmethodID seq_incRefnum; |
| |
| static jfieldID ref_objField; |
| |
| static jclass throwable_class; |
| |
| // env_destructor is registered as a thread data key destructor to |
| // clean up a Go thread that is attached to the JVM. |
| static void env_destructor(void *env) { |
| if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) { |
| LOG_INFO("failed to detach current thread"); |
| } |
| } |
| |
| static JNIEnv *go_seq_get_thread_env(void) { |
| JNIEnv *env; |
| jint ret = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_6); |
| if (ret != JNI_OK) { |
| if (ret != JNI_EDETACHED) { |
| LOG_FATAL("failed to get thread env"); |
| } |
| if ((*jvm)->AttachCurrentThread(jvm, &env, NULL) != JNI_OK) { |
| LOG_FATAL("failed to attach current thread"); |
| } |
| pthread_setspecific(jnienvs, env); |
| } |
| return env; |
| } |
| |
| void go_seq_maybe_throw_exception(JNIEnv *env, jobject exc) { |
| if (exc != NULL) { |
| (*env)->Throw(env, exc); |
| } |
| } |
| |
| jobject go_seq_get_exception(JNIEnv *env) { |
| jthrowable exc = (*env)->ExceptionOccurred(env); |
| if (!exc) { |
| return NULL; |
| } |
| (*env)->ExceptionClear(env); |
| return exc; |
| } |
| |
| jbyteArray go_seq_to_java_bytearray(JNIEnv *env, nbyteslice s, int copy) { |
| if (s.ptr == NULL) { |
| return NULL; |
| } |
| jbyteArray res = (*env)->NewByteArray(env, s.len); |
| if (res == NULL) { |
| LOG_FATAL("NewByteArray failed"); |
| } |
| (*env)->SetByteArrayRegion(env, res, 0, s.len, s.ptr); |
| if (copy) { |
| free(s.ptr); |
| } |
| return res; |
| } |
| |
| #define surr1 0xd800 |
| #define surr2 0xdc00 |
| #define surr3 0xe000 |
| |
| // Unicode replacement character |
| #define replacementChar 0xFFFD |
| |
| #define rune1Max ((1<<7) - 1) |
| #define rune2Max ((1<<11) - 1) |
| #define rune3Max ((1<<16) - 1) |
| // Maximum valid Unicode code point. |
| #define MaxRune 0x0010FFFF |
| |
| #define surrogateMin 0xD800 |
| #define surrogateMax 0xDFFF |
| // 0011 1111 |
| #define maskx 0x3F |
| // 1000 0000 |
| #define tx 0x80 |
| // 1100 0000 |
| #define t2 0xC0 |
| // 1110 0000 |
| #define t3 0xE0 |
| // 1111 0000 |
| #define t4 0xF0 |
| |
| // encode_rune writes into p (which must be large enough) the UTF-8 encoding |
| // of the rune. It returns the number of bytes written. |
| static int encode_rune(uint8_t *p, uint32_t r) { |
| if (r <= rune1Max) { |
| p[0] = (uint8_t)r; |
| return 1; |
| } else if (r <= rune2Max) { |
| p[0] = t2 | (uint8_t)(r>>6); |
| p[1] = tx | (((uint8_t)(r))&maskx); |
| return 2; |
| } else { |
| if (r > MaxRune || (surrogateMin <= r && r <= surrogateMax)) { |
| r = replacementChar; |
| } |
| if (r <= rune3Max) { |
| p[0] = t3 | (uint8_t)(r>>12); |
| p[1] = tx | (((uint8_t)(r>>6))&maskx); |
| p[2] = tx | (((uint8_t)(r))&maskx); |
| return 3; |
| } else { |
| p[0] = t4 | (uint8_t)(r>>18); |
| p[1] = tx | (((uint8_t)(r>>12))&maskx); |
| p[2] = tx | (((uint8_t)(r>>6))&maskx); |
| p[3] = tx | (((uint8_t)(r))&maskx); |
| return 4; |
| } |
| } |
| } |
| |
| // utf16_decode decodes an array of UTF16 characters to a UTF-8 encoded |
| // nstring copy. The support functions and utf16_decode itself are heavily |
| // based on the unicode/utf8 and unicode/utf16 Go packages. |
| static nstring utf16_decode(jchar *chars, jsize len) { |
| jsize worstCaseLen = 4*len; |
| uint8_t *buf = malloc(worstCaseLen); |
| if (buf == NULL) { |
| LOG_FATAL("utf16Decode: malloc failed"); |
| } |
| jsize nsrc = 0; |
| jsize ndst = 0; |
| while (nsrc < len) { |
| uint32_t r = chars[nsrc]; |
| nsrc++; |
| if (surr1 <= r && r < surr2 && nsrc < len) { |
| uint32_t r2 = chars[nsrc]; |
| if (surr2 <= r2 && r2 < surr3) { |
| nsrc++; |
| r = (((r-surr1)<<10) | (r2 - surr2)) + 0x10000; |
| } |
| } |
| if (ndst + 4 > worstCaseLen) { |
| LOG_FATAL("utf16Decode: buffer overflow"); |
| } |
| ndst += encode_rune(buf + ndst, r); |
| } |
| struct nstring res = {.chars = buf, .len = ndst}; |
| return res; |
| } |
| |
| nstring go_seq_from_java_string(JNIEnv *env, jstring str) { |
| struct nstring res = {NULL, 0}; |
| if (str == NULL) { |
| return res; |
| } |
| jsize nchars = (*env)->GetStringLength(env, str); |
| if (nchars == 0) { |
| return res; |
| } |
| jchar *chars = (jchar *)(*env)->GetStringChars(env, str, NULL); |
| if (chars == NULL) { |
| LOG_FATAL("GetStringChars failed"); |
| } |
| nstring nstr = utf16_decode(chars, nchars); |
| (*env)->ReleaseStringChars(env, str, chars); |
| return nstr; |
| } |
| |
| nbyteslice go_seq_from_java_bytearray(JNIEnv *env, jbyteArray arr, int copy) { |
| struct nbyteslice res = {NULL, 0}; |
| if (arr == NULL) { |
| return res; |
| } |
| |
| jsize len = (*env)->GetArrayLength(env, arr); |
| if (len == 0) { |
| return res; |
| } |
| jbyte *ptr = (*env)->GetByteArrayElements(env, arr, NULL); |
| if (ptr == NULL) { |
| LOG_FATAL("GetByteArrayElements failed"); |
| } |
| if (copy) { |
| void *ptr_copy = (void *)malloc(len); |
| if (ptr_copy == NULL) { |
| LOG_FATAL("malloc failed"); |
| } |
| memcpy(ptr_copy, ptr, len); |
| (*env)->ReleaseByteArrayElements(env, arr, ptr, JNI_ABORT); |
| ptr = (jbyte *)ptr_copy; |
| } |
| res.ptr = ptr; |
| res.len = len; |
| return res; |
| } |
| |
| int32_t go_seq_to_refnum_go(JNIEnv *env, jobject o) { |
| if (o == NULL) { |
| return NULL_REFNUM; |
| } |
| return (int32_t)(*env)->CallStaticIntMethod(env, seq_class, seq_incGoObjectRef, o); |
| } |
| |
| int32_t go_seq_to_refnum(JNIEnv *env, jobject o) { |
| if (o == NULL) { |
| return NULL_REFNUM; |
| } |
| return (int32_t)(*env)->CallStaticIntMethod(env, seq_class, seq_incRef, o); |
| } |
| |
| int32_t go_seq_unwrap(jint refnum) { |
| JNIEnv *env = go_seq_push_local_frame(0); |
| jobject jobj = go_seq_from_refnum(env, refnum, NULL, NULL); |
| int32_t goref = go_seq_to_refnum_go(env, jobj); |
| go_seq_pop_local_frame(env); |
| return goref; |
| } |
| |
| jobject go_seq_from_refnum(JNIEnv *env, int32_t refnum, jclass proxy_class, jmethodID proxy_cons) { |
| if (refnum == NULL_REFNUM) { |
| return NULL; |
| } |
| // Seq.Ref ref = Seq.getRef(refnum) |
| jobject ref = (*env)->CallStaticObjectMethod(env, seq_class, seq_getRef, (jint)refnum); |
| if (ref == NULL) { |
| LOG_FATAL("Unknown reference: %d", refnum); |
| } |
| if (refnum > 0) { // Java object |
| // Go incremented the reference count just before passing the refnum. Decrement it here. |
| (*env)->CallStaticVoidMethod(env, seq_class, seq_decRef, (jint)refnum); |
| // return ref.obj |
| return (*env)->GetObjectField(env, ref, ref_objField); |
| } else if (proxy_class != NULL) { |
| // return new <Proxy>(ref) |
| return (*env)->NewObject(env, proxy_class, proxy_cons, ref); |
| } else { |
| // We're inside a Java proxy constructor and only need the Seq.Ref |
| return ref; |
| } |
| } |
| |
| // go_seq_to_java_string converts a nstring to a jstring. |
| jstring go_seq_to_java_string(JNIEnv *env, nstring str) { |
| jstring s = (*env)->NewString(env, str.chars, str.len/2); |
| if (str.chars != NULL) { |
| free(str.chars); |
| } |
| return s; |
| } |
| |
| // go_seq_push_local_frame retrieves or creates the JNIEnv* for the current thread |
| // and pushes a JNI reference frame. Must be matched with call to go_seq_pop_local_frame. |
| JNIEnv *go_seq_push_local_frame(jint nargs) { |
| JNIEnv *env = go_seq_get_thread_env(); |
| // Given the number of function arguments, compute a conservative bound for the minimal frame size. |
| // Assume two slots for each per parameter (Seq.Ref and Seq.Object) and add extra |
| // extra space for the receiver, the return value, and exception (if any). |
| jint frameSize = 2*nargs + 10; |
| if ((*env)->PushLocalFrame(env, frameSize) < 0) { |
| LOG_FATAL("PushLocalFrame failed"); |
| } |
| return env; |
| } |
| |
| // Pop the current local frame, freeing all JNI local references in it |
| void go_seq_pop_local_frame(JNIEnv *env) { |
| (*env)->PopLocalFrame(env, NULL); |
| } |
| |
| void go_seq_inc_ref(int32_t ref) { |
| JNIEnv *env = go_seq_get_thread_env(); |
| (*env)->CallStaticVoidMethod(env, seq_class, seq_incRefnum, (jint)ref); |
| } |
| |
| void go_seq_dec_ref(int32_t ref) { |
| JNIEnv *env = go_seq_get_thread_env(); |
| (*env)->CallStaticVoidMethod(env, seq_class, seq_decRef, (jint)ref); |
| } |
| |
| JNIEXPORT void JNICALL |
| Java_go_Seq_init(JNIEnv *env, jclass clazz) { |
| if ((*env)->GetJavaVM(env, &jvm) != 0) { |
| LOG_FATAL("failed to get JVM"); |
| } |
| if (pthread_key_create(&jnienvs, env_destructor) != 0) { |
| LOG_FATAL("failed to initialize jnienvs thread local storage"); |
| } |
| |
| seq_class = (*env)->NewGlobalRef(env, clazz); |
| seq_getRef = (*env)->GetStaticMethodID(env, seq_class, "getRef", "(I)Lgo/Seq$Ref;"); |
| if (seq_getRef == NULL) { |
| LOG_FATAL("failed to find method Seq.getRef"); |
| } |
| seq_decRef = (*env)->GetStaticMethodID(env, seq_class, "decRef", "(I)V"); |
| if (seq_decRef == NULL) { |
| LOG_FATAL("failed to find method Seq.decRef"); |
| } |
| seq_incRefnum = (*env)->GetStaticMethodID(env, seq_class, "incRefnum", "(I)V"); |
| if (seq_incRefnum == NULL) { |
| LOG_FATAL("failed to find method Seq.incRefnum"); |
| } |
| seq_incRef = (*env)->GetStaticMethodID(env, seq_class, "incRef", "(Ljava/lang/Object;)I"); |
| if (seq_incRef == NULL) { |
| LOG_FATAL("failed to find method Seq.incRef"); |
| } |
| seq_incGoObjectRef = (*env)->GetStaticMethodID(env, seq_class, "incGoObjectRef", "(Lgo/Seq$GoObject;)I"); |
| if (seq_incGoObjectRef == NULL) { |
| LOG_FATAL("failed to find method Seq.incGoObjectRef"); |
| } |
| jclass ref_class = (*env)->FindClass(env, "go/Seq$Ref"); |
| if (ref_class == NULL) { |
| LOG_FATAL("failed to find the Seq.Ref class"); |
| } |
| ref_objField = (*env)->GetFieldID(env, ref_class, "obj", "Ljava/lang/Object;"); |
| if (ref_objField == NULL) { |
| LOG_FATAL("failed to find the Seq.Ref.obj field"); |
| } |
| initClasses(); |
| } |
| |
| JNIEXPORT void JNICALL |
| Java_go_Seq_destroyRef(JNIEnv *env, jclass clazz, jint refnum) { |
| DestroyRef(refnum); |
| } |
| |
| JNIEXPORT void JNICALL |
| Java_go_Seq_incGoRef(JNIEnv *env, jclass clazz, jint refnum) { |
| IncGoRef(refnum); |
| } |
| |
| jclass go_seq_find_class(const char *name) { |
| JNIEnv *env = go_seq_push_local_frame(0); |
| jclass clazz = (*env)->FindClass(env, name); |
| if (clazz == NULL) { |
| (*env)->ExceptionClear(env); |
| } else { |
| clazz = (*env)->NewGlobalRef(env, clazz); |
| } |
| go_seq_pop_local_frame(env); |
| return clazz; |
| } |
| |
| jmethodID go_seq_get_static_method_id(jclass clazz, const char *name, const char *sig) { |
| JNIEnv *env = go_seq_push_local_frame(0); |
| jmethodID m = (*env)->GetStaticMethodID(env, clazz, name, sig); |
| if (m == NULL) { |
| (*env)->ExceptionClear(env); |
| } |
| go_seq_pop_local_frame(env); |
| return m; |
| } |
| |
| jmethodID go_seq_get_method_id(jclass clazz, const char *name, const char *sig) { |
| JNIEnv *env = go_seq_push_local_frame(0); |
| jmethodID m = (*env)->GetMethodID(env, clazz, name, sig); |
| if (m == NULL) { |
| (*env)->ExceptionClear(env); |
| } |
| go_seq_pop_local_frame(env); |
| return m; |
| } |
| |
| void go_seq_release_byte_array(JNIEnv *env, jbyteArray arr, jbyte* ptr) { |
| if (ptr != NULL) { |
| (*env)->ReleaseByteArrayElements(env, arr, ptr, 0); |
| } |
| } |
| |
| int go_seq_isinstanceof(jint refnum, jclass clazz) { |
| JNIEnv *env = go_seq_push_local_frame(0); |
| jobject obj = go_seq_from_refnum(env, refnum, NULL, NULL); |
| jboolean isinst = (*env)->IsInstanceOf(env, obj, clazz); |
| go_seq_pop_local_frame(env); |
| return isinst; |
| } |