// 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 #include #include #include #include #include #include #include #include "seq.h" #include "_cgo_export.h" #define NULL_REFNUM 41 // initClasses are only exported from Go if reverse bindings are used. // If they're not, weakly define a no-op function. __attribute__((weak)) void initClasses(void) { } 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; } if (refnum < 0) { // Go object // return new (refnum) return (*env)->NewObject(env, proxy_class, proxy_cons, refnum); } // 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); } // 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); } // 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, jobject ref) { 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; }