summaryrefslogtreecommitdiffstats
path: root/vm/Jni.c
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:28:47 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:28:47 -0800
commitf6c387128427e121477c1b32ad35cdcaa5101ba3 (patch)
tree2aa25fa8c8c3a9caeecf98fd8ac4cd9b12717997 /vm/Jni.c
parentf72d5de56a522ac3be03873bdde26f23a5eeeb3c (diff)
downloadandroid_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.tar.gz
android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.tar.bz2
android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.zip
auto import from //depot/cupcake/@135843
Diffstat (limited to 'vm/Jni.c')
-rw-r--r--vm/Jni.c3533
1 files changed, 3533 insertions, 0 deletions
diff --git a/vm/Jni.c b/vm/Jni.c
new file mode 100644
index 000000000..f7a21ffb3
--- /dev/null
+++ b/vm/Jni.c
@@ -0,0 +1,3533 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Dalvik implementation of JNI interfaces.
+ */
+#include "Dalvik.h"
+#include "JniInternal.h"
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <limits.h>
+
+/*
+Native methods and interaction with the GC
+
+All JNI methods must start by changing their thread status to
+THREAD_RUNNING, and finish by changing it back to THREAD_NATIVE before
+returning to native code. The switch to "running" triggers a thread
+suspension check.
+
+With a rudimentary GC we should be able to skip the status change for
+simple functions, e.g. IsSameObject, GetJavaVM, GetStringLength, maybe
+even access to fields with primitive types. Our options are more limited
+with a compacting GC, so we should replace JNI_ENTER with JNI_ENTER_NCGC
+or somesuch on the "lite" functions if we want to try this optimization.
+
+For performance reasons we do as little error-checking as possible here.
+For example, we don't check to make sure the correct type of Object is
+passed in when setting a field, and we don't prevent you from storing
+new values in a "final" field. Such things are best handled in the
+"check" version. For actions that are common, dangerous, and must be
+checked at runtime, such as array bounds checks, we do the tests here.
+
+
+General notes on local/global reference tracking
+
+JNI provides explicit control over natively-held references that the VM GC
+needs to know about. These can be local, in which case they're released
+when the native method returns, or global, which are held until explicitly
+released.
+
+The references can be created and deleted with JNI NewLocalRef /
+NewGlobalRef calls, but this is unusual except perhaps for holding on
+to a Class reference. Most often they are created transparently by the
+JNI functions. For example, the paired Get/Release calls guarantee that
+objects survive until explicitly released, so a simple way to implement
+this is to create a global reference on "Get" and delete it on "Release".
+The AllocObject/NewObject functions must create local references, because
+nothing else in the GC root set has a reference to the new objects.
+
+The most common mode of operation is for a method to create zero or
+more local references and return. Explicit "local delete" operations
+are expected to be exceedingly rare, except when walking through an
+object array, and the Push/PopLocalFrame calls are expected to be used
+infrequently. For efficient operation, we want to add new local refs
+with a simple store/increment operation; to avoid infinite growth in
+pathological situations, we need to reclaim the space used by deleted
+entries.
+
+The simplest implementation is an expanding append-only array that compacts
+when objects are deleted. In typical situations, e.g. running through
+an array of objects, we will be deleting one of the most recently added
+entries, so we can minimize the number of elements moved (or avoid having
+to move any).
+
+The spec says, "Local references are only valid in the thread in which
+they are created. The native code must not pass local references from
+one thread to another." It should also be noted that, while some calls
+will *create* global references as a side-effect, only the NewGlobalRef
+and NewWeakGlobalRef calls actually *return* global references.
+
+
+Global reference tracking
+
+There should be a small "active" set centered around the most-recently
+added items. We can use an append-only, compacting array like we do for
+local refs.
+
+Because it's global, access to it has to be synchronized.
+
+The JNI spec does not define any sort of limit, so the list must be able
+to expand. It may be useful to log significant increases in usage to
+help identify resource leaks.
+
+TODO: we currently use global references on strings and primitive array
+data, because they have the property we need (i.e. the pointer we return
+is guaranteed valid until we explicitly release it). However, if we have
+a compacting GC and don't want to pin all memory held by all global refs,
+we actually want to treat these differently. Either we need a way to
+tell the GC that specific global references are pinned, or we have to
+make a copy of the data and return that instead (something JNI supports).
+
+
+Local reference tracking
+
+The table of local references can be stored on the interpreted stack or
+in a parallel data structure (one per thread).
+
+*** Approach #1: use the interpreted stack
+
+The easiest place to tuck it is between the frame ptr and the first saved
+register, which is always in0. (See the ASCII art in Stack.h.) We can
+shift the "VM-specific goop" and frame ptr down, effectively inserting
+the JNI local refs in the space normally occupied by local variables.
+
+(Three things are accessed from the frame pointer:
+ (1) framePtr[N] is register vN, used to get at "ins" and "locals".
+ (2) framePtr - sizeof(StackSaveArea) is the VM frame goop.
+ (3) framePtr - sizeof(StackSaveArea) - numOuts is where the "outs" go.
+The only thing that isn't determined by an offset from the current FP
+is the previous frame. However, tucking things below the previous frame
+can be problematic because the "outs" of the previous frame overlap with
+the "ins" of the current frame. If the "ins" are altered they must be
+restored before we return. For a native method call, the easiest and
+safest thing to disrupt is #1, because there are no locals and the "ins"
+are all copied to the native stack.)
+
+We can implement Push/PopLocalFrame with the existing stack frame calls,
+making sure we copy some goop from the previous frame (notably the method
+ptr, so that dvmGetCurrentJNIMethod() doesn't require extra effort).
+
+We can pre-allocate the storage at the time the stack frame is first
+set up, but we have to be careful. When calling from interpreted code
+the frame ptr points directly at the arguments we're passing, but we can
+offset the args pointer when calling the native bridge.
+
+To manage the local ref collection, we need to be able to find three
+things: (1) the start of the region, (2) the end of the region, and (3)
+the next available entry. The last is only required for quick adds.
+We currently have two easily-accessible pointers, the current FP and the
+previous frame's FP. (The "stack pointer" shown in the ASCII art doesn't
+actually exist in the interpreted world.)
+
+We can't use the current FP to find the first "in", because we want to
+insert the variable-sized local refs table between them. It's awkward
+to use the previous frame's FP because native methods invoked via
+dvmCallMethod() or dvmInvokeMethod() don't have "ins", but native methods
+invoked from interpreted code do. We can either track the local refs
+table size with a field in the stack frame, or insert unnecessary items
+so that all native stack frames have "ins".
+
+Assuming we can find the region bounds, we still need pointer #3
+for an efficient implementation. This can be stored in an otherwise
+unused-for-native field in the frame goop.
+
+When we run out of room we have to make more space. If we start allocating
+locals immediately below in0 and grow downward, we will detect end-of-space
+by running into the current frame's FP. We then memmove() the goop down
+(memcpy if we guarantee the additional size is larger than the frame).
+This is nice because we only have to move sizeof(StackSaveArea) bytes
+each time.
+
+Stack walking should be okay so long as nothing tries to access the
+"ins" by an offset from the FP. In theory the "ins" could be read by
+the debugger or SIGQUIT handler looking for "this" or other arguments,
+but in practice this behavior isn't expected to work for native methods,
+so we can simply disallow it.
+
+A conservative GC can just scan the entire stack from top to bottom to find
+all references. An exact GC will need to understand the actual layout.
+
+*** Approach #2: use a parallel stack
+
+Each Thread/JNIEnv points to a ReferenceTable struct. The struct
+has a system-heap-allocated array of references and a pointer to the
+next-available entry ("nextEntry").
+
+Each stack frame has a pointer to what it sees as the "top" element in the
+array (we can double-up the "currentPc" field). This is set to "nextEntry"
+when the frame is pushed on. As local references are added or removed,
+"nextEntry" is updated.
+
+We implement Push/PopLocalFrame with actual stack frames. Before a JNI
+frame gets popped, we set "nextEntry" to the "top" pointer of the current
+frame, effectively releasing the references.
+
+The GC will scan all references from the start of the table to the
+"nextEntry" pointer.
+
+*** Comparison
+
+All approaches will return a failure result when they run out of local
+reference space. For #1 that means blowing out the stack, for #2 it's
+running out of room in the array.
+
+Compared to #1, approach #2:
+ - Needs only one pointer in the stack frame goop.
+ - Makes pre-allocating storage unnecessary.
+ - Doesn't contend with interpreted stack depth for space. In most
+ cases, if something blows out the local ref storage, it's because the
+ JNI code was misbehaving rather than called from way down.
+ - Allows the GC to do a linear scan per thread in a buffer that is 100%
+ references. The GC can be slightly less smart when scanning the stack.
+ - Will be easier to work with if we combine native and interpeted stacks.
+
+ - Isn't as clean, especially when popping frames, since we have to do
+ explicit work. Fortunately we only have to do it when popping native
+ method calls off, so it doesn't add overhead to interpreted code paths.
+ - Is awkward to expand dynamically. We'll want to pre-allocate the full
+ amount of space; this is fine, since something on the order of 1KB should
+ be plenty. The JNI spec allows us to limit this.
+ - Requires the GC to scan even more memory. With the references embedded
+ in the stack we get better locality of reference.
+
+*/
+
+static const struct JNINativeInterface gNativeInterface; // fwd
+
+
+#ifdef WITH_JNI_STACK_CHECK
+# define COMPUTE_STACK_SUM(_self) computeStackSum(_self);
+# define CHECK_STACK_SUM(_self) checkStackSum(_self);
+static void computeStackSum(Thread* self);
+static void checkStackSum(Thread* self);
+#else
+# define COMPUTE_STACK_SUM(_self) ((void)0)
+# define CHECK_STACK_SUM(_self) ((void)0)
+#endif
+
+
+/*
+ * ===========================================================================
+ * JNI call bridge
+ * ===========================================================================
+ */
+
+/*
+ * Bridge to calling a JNI function. This ideally gets some help from
+ * assembly language code in dvmPlatformInvoke, because the arguments
+ * must be pushed into the native stack as if we were calling a <stdarg.h>
+ * function.
+ *
+ * The number of values in "args" must match method->insSize.
+ *
+ * This is generally just set up by the resolver and then called through.
+ * We don't call here explicitly. This takes the same arguments as all
+ * of the "internal native" methods.
+ */
+void dvmCallJNIMethod(const u4* args, JValue* pResult, const Method* method,
+ Thread* self)
+{
+ int oldStatus;
+
+ assert(method->insns != NULL);
+
+ //int i;
+ //LOGI("JNI calling %p (%s.%s %s):\n", method->insns,
+ // method->clazz->descriptor, method->name, method->signature);
+ //for (i = 0; i < method->insSize; i++)
+ // LOGI(" %d: 0x%08x\n", i, args[i]);
+
+ oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
+
+ COMPUTE_STACK_SUM(self);
+ // TODO: should we be converting 'this' to a local ref?
+ dvmPlatformInvoke(self->jniEnv,
+ dvmIsStaticMethod(method) ? method->clazz : NULL,
+ method->jniArgInfo, method->insSize, args, method->shorty,
+ (void*)method->insns, pResult);
+ CHECK_STACK_SUM(self);
+
+ dvmChangeStatus(self, oldStatus);
+}
+
+/*
+ * Alternate call bridge for the unusual case of a synchronized native method.
+ *
+ * Lock the object, then call through the usual function.
+ */
+void dvmCallSynchronizedJNIMethod(const u4* args, JValue* pResult,
+ const Method* method, Thread* self)
+{
+ Object* lockObj;
+
+ assert(dvmIsSynchronizedMethod(method));
+
+ if (dvmIsStaticMethod(method))
+ lockObj = (Object*) method->clazz;
+ else
+ lockObj = (Object*) args[0];
+
+ LOGVV("Calling %s.%s: locking %p (%s)\n",
+ method->clazz->descriptor, method->name,
+ lockObj, lockObj->clazz->descriptor);
+
+ dvmLockObject(self, lockObj);
+ dvmCallJNIMethod(args, pResult, method, self);
+ dvmUnlockObject(self, lockObj);
+}
+
+/*
+ * Extract the return type enum from the "jniArgInfo" field.
+ */
+DalvikJniReturnType dvmGetArgInfoReturnType(int jniArgInfo)
+{
+ return (jniArgInfo & DALVIK_JNI_RETURN_MASK) >> DALVIK_JNI_RETURN_SHIFT;
+}
+
+
+/*
+ * ===========================================================================
+ * Utility functions
+ * ===========================================================================
+ */
+
+/*
+ * Entry/exit processing for all JNI calls.
+ *
+ * If TRUSTED_JNIENV is set, we get to skip the (curiously expensive)
+ * thread-local storage lookup on our Thread*. If the caller has passed
+ * the wrong JNIEnv in, we're going to be accessing unsynchronized
+ * structures from more than one thread, and things are going to fail
+ * in bizarre ways. This is only sensible if the native code has been
+ * fully exercised with CheckJNI enabled.
+ */
+#define TRUSTED_JNIENV
+#ifdef TRUSTED_JNIENV
+# define JNI_ENTER() \
+ Thread* _self = ((JNIEnvExt*)env)->self; \
+ CHECK_STACK_SUM(_self); \
+ dvmChangeStatus(_self, THREAD_RUNNING)
+#else
+# define JNI_ENTER() \
+ Thread* _self = dvmThreadSelf(); \
+ UNUSED_PARAMETER(env); \
+ CHECK_STACK_SUM(_self); \
+ dvmChangeStatus(_self, THREAD_RUNNING)
+#endif
+#define JNI_EXIT() \
+ dvmChangeStatus(_self, THREAD_NATIVE); \
+ COMPUTE_STACK_SUM(_self)
+
+#define kGlobalRefsTableInitialSize 512
+#define kGlobalRefsTableMaxSize 51200 /* arbitrary */
+#define kGrefWaterInterval 100
+
+#define kTrackGrefUsage true
+
+/*
+ * Allocate the global references table.
+ */
+bool dvmJniStartup(void)
+{
+ if (!dvmInitReferenceTable(&gDvm.jniGlobalRefTable,
+ kGlobalRefsTableInitialSize, kGlobalRefsTableMaxSize))
+ return false;
+
+ dvmInitMutex(&gDvm.jniGlobalRefLock);
+
+ gDvm.jniGlobalRefLoMark = 0;
+ gDvm.jniGlobalRefHiMark = kGrefWaterInterval * 2;
+
+ return true;
+}
+
+/*
+ * Free the global references table.
+ */
+void dvmJniShutdown(void)
+{
+ dvmClearReferenceTable(&gDvm.jniGlobalRefTable);
+}
+
+
+/*
+ * Find the JNIEnv associated with the current thread.
+ *
+ * Currently stored in the Thread struct. Could also just drop this into
+ * thread-local storage.
+ */
+JNIEnvExt* dvmGetJNIEnvForThread(void)
+{
+ Thread* self = dvmThreadSelf();
+ if (self == NULL)
+ return NULL;
+ return (JNIEnvExt*) dvmGetThreadJNIEnv(self);
+}
+
+/*
+ * Create a new JNIEnv struct and add it to the VM's list.
+ *
+ * "self" will be NULL for the main thread, since the VM hasn't started
+ * yet; the value will be filled in later.
+ */
+JNIEnv* dvmCreateJNIEnv(Thread* self)
+{
+ JavaVMExt* vm = (JavaVMExt*) gDvm.vmList;
+ JNIEnvExt* newEnv;
+
+ //if (self != NULL)
+ // LOGI("Ent CreateJNIEnv: threadid=%d %p\n", self->threadId, self);
+
+ assert(vm != NULL);
+
+ newEnv = (JNIEnvExt*) calloc(1, sizeof(JNIEnvExt));
+ newEnv->funcTable = &gNativeInterface;
+ newEnv->vm = vm;
+ newEnv->forceDataCopy = vm->forceDataCopy;
+ if (self != NULL) {
+ dvmSetJniEnvThreadId((JNIEnv*) newEnv, self);
+ assert(newEnv->envThreadId != 0);
+ } else {
+ /* make it obvious if we fail to initialize these later */
+ newEnv->envThreadId = 0x77777775;
+ newEnv->self = (Thread*) 0x77777779;
+ }
+ if (vm->useChecked)
+ dvmUseCheckedJniEnv(newEnv);
+
+ dvmLockMutex(&vm->envListLock);
+
+ /* insert at head of list */
+ newEnv->next = vm->envList;
+ assert(newEnv->prev == NULL);
+ if (vm->envList == NULL) // rare, but possible
+ vm->envList = newEnv;
+ else
+ vm->envList->prev = newEnv;
+ vm->envList = newEnv;
+
+ dvmUnlockMutex(&vm->envListLock);
+
+ //if (self != NULL)
+ // LOGI("Xit CreateJNIEnv: threadid=%d %p\n", self->threadId, self);
+ return (JNIEnv*) newEnv;
+}
+
+/*
+ * Remove a JNIEnv struct from the list and free it.
+ */
+void dvmDestroyJNIEnv(JNIEnv* env)
+{
+ JNIEnvExt* extEnv = (JNIEnvExt*) env;
+ JavaVMExt* vm = extEnv->vm;
+ Thread* self;
+
+ if (env == NULL)
+ return;
+
+ self = dvmThreadSelf();
+ assert(self != NULL);
+
+ //LOGI("Ent DestroyJNIEnv: threadid=%d %p\n", self->threadId, self);
+
+ dvmLockMutex(&vm->envListLock);
+
+ if (extEnv == vm->envList) {
+ assert(extEnv->prev == NULL);
+ vm->envList = extEnv->next;
+ } else {
+ assert(extEnv->prev != NULL);
+ extEnv->prev->next = extEnv->next;
+ }
+ if (extEnv->next != NULL)
+ extEnv->next->prev = extEnv->prev;
+
+ dvmUnlockMutex(&extEnv->vm->envListLock);
+
+ free(env);
+ //LOGI("Xit DestroyJNIEnv: threadid=%d %p\n", self->threadId, self);
+}
+
+
+/*
+ * Retrieve the ReferenceTable struct for the current thread.
+ *
+ * If we know the code isn't sharing JNIEnv pointers between threads, we
+ * could put this into env and skip the TLS lookup.
+ */
+static inline ReferenceTable* getLocalRefTable(void)
+{
+ return &dvmThreadSelf()->jniLocalRefTable;
+}
+
+/*
+ * Add a local reference for an object to the current stack frame. When
+ * the native function returns, the reference will be discarded.
+ *
+ * We need to allow the same reference to be added multiple times.
+ *
+ * This will be called on otherwise unreferenced objects. We cannot do
+ * GC allocations here, and it's best if we don't grab a mutex.
+ *
+ * Returns the local reference (currently just the same pointer that was
+ * passed in), or NULL on failure.
+ */
+static jobject addLocalReference(jobject obj)
+{
+ if (obj == NULL)
+ return NULL;
+
+ ReferenceTable* pRef = getLocalRefTable();
+
+ if (!dvmAddToReferenceTable(pRef, (Object*)obj)) {
+ dvmDumpReferenceTable(pRef, "JNI local");
+ LOGE("Failed adding to JNI local ref table (has %d entries)\n",
+ (int) dvmReferenceTableEntries(pRef));
+ dvmDumpThread(dvmThreadSelf(), false);
+ dvmAbort(); // spec says call FatalError; this is equivalent
+ } else {
+ LOGVV("LREF add %p (%s.%s)\n", obj,
+ dvmGetCurrentJNIMethod()->clazz->descriptor,
+ dvmGetCurrentJNIMethod()->name);
+ }
+
+ return obj;
+}
+
+/*
+ * Ensure that at least "capacity" references can be held in the local
+ * refs table of the current thread.
+ */
+static bool ensureLocalCapacity(int capacity)
+{
+ ReferenceTable* pRef = getLocalRefTable();
+
+ return (kJniLocalRefMax - (pRef->nextEntry - pRef->table) >= capacity);
+}
+
+/*
+ * Explicitly delete a reference from the local list.
+ */
+static void deleteLocalReference(jobject obj)
+{
+ if (obj == NULL)
+ return;
+
+ ReferenceTable* pRef = getLocalRefTable();
+ Thread* self = dvmThreadSelf();
+ Object** top = SAVEAREA_FROM_FP(self->curFrame)->xtra.localRefTop;
+
+ if (!dvmRemoveFromReferenceTable(pRef, top, (Object*) obj)) {
+ /*
+ * Attempting to delete a local reference that is not in the
+ * topmost local reference frame is a no-op. DeleteLocalRef returns
+ * void and doesn't throw any exceptions, but we should probably
+ * complain about it so the user will notice that things aren't
+ * going quite the way they expect.
+ */
+ LOGW("JNI WARNING: DeleteLocalRef(%p) failed to find entry (valid=%d)\n",
+ obj, dvmIsValidObject((Object*) obj));
+ }
+}
+
+/*
+ * Add a global reference for an object.
+ *
+ * We may add the same object more than once. Add/remove calls are paired,
+ * so it needs to appear on the list multiple times.
+ */
+static jobject addGlobalReference(jobject obj)
+{
+ if (obj == NULL)
+ return NULL;
+
+ //LOGI("adding obj=%p\n", obj);
+ //dvmDumpThread(dvmThreadSelf(), false);
+
+ if (false && ((Object*)obj)->clazz == gDvm.classJavaLangClass) {
+ ClassObject* clazz = (ClassObject*) obj;
+ LOGI("-------\n");
+ LOGI("Adding global ref on class %s\n", clazz->descriptor);
+ dvmDumpThread(dvmThreadSelf(), false);
+ }
+ if (false && ((Object*)obj)->clazz == gDvm.classJavaLangString) {
+ StringObject* strObj = (StringObject*) obj;
+ char* str = dvmCreateCstrFromString(strObj);
+ if (strcmp(str, "sync-response") == 0) {
+ LOGI("-------\n");
+ LOGI("Adding global ref on string '%s'\n", str);
+ dvmDumpThread(dvmThreadSelf(), false);
+ //dvmAbort();
+ }
+ free(str);
+ }
+ if (false && ((Object*)obj)->clazz == gDvm.classArrayByte) {
+ ArrayObject* arrayObj = (ArrayObject*) obj;
+ if (arrayObj->length == 8192 &&
+ dvmReferenceTableEntries(&gDvm.jniGlobalRefTable) > 400)
+ {
+ LOGI("Adding global ref on byte array %p (len=%d)\n",
+ arrayObj, arrayObj->length);
+ dvmDumpThread(dvmThreadSelf(), false);
+ }
+ }
+
+ dvmLockMutex(&gDvm.jniGlobalRefLock);
+
+ /*
+ * Expanding the table should happen rarely, so I'm not overly
+ * concerned about the performance impact of copying the old list
+ * over. We shouldn't see one-time activity spikes, so freeing
+ * up storage shouldn't be required.
+ *
+ * Throwing an exception on failure is problematic, because JNI code
+ * may not be expecting an exception, and things sort of cascade. We
+ * want to have a hard limit to catch leaks during debugging, but this
+ * otherwise needs to expand until memory is consumed. As a practical
+ * matter, if we have many thousands of global references, chances are
+ * we're either leaking global ref table entries or we're going to
+ * run out of space in the GC heap.
+ */
+ if (!dvmAddToReferenceTable(&gDvm.jniGlobalRefTable, (Object*)obj)) {
+ dvmDumpReferenceTable(&gDvm.jniGlobalRefTable, "JNI global");
+ LOGE("Failed adding to JNI global ref table (%d entries)\n",
+ (int) dvmReferenceTableEntries(&gDvm.jniGlobalRefTable));
+ dvmAbort();
+ }
+
+ LOGVV("GREF add %p (%s.%s)\n", obj,
+ dvmGetCurrentJNIMethod()->clazz->descriptor,
+ dvmGetCurrentJNIMethod()->name);
+
+ /* GREF usage tracking; should probably be disabled for production env */
+ if (kTrackGrefUsage && gDvm.jniGrefLimit != 0) {
+ int count = dvmReferenceTableEntries(&gDvm.jniGlobalRefTable);
+ if (count > gDvm.jniGlobalRefHiMark) {
+ LOGD("GREF has increased to %d\n", count);
+ gDvm.jniGlobalRefHiMark += kGrefWaterInterval;
+ gDvm.jniGlobalRefLoMark += kGrefWaterInterval;
+
+ /* watch for "excessive" use; not generally appropriate */
+ if (count >= gDvm.jniGrefLimit) {
+ JavaVMExt* vm = (JavaVMExt*) gDvm.vmList;
+ if (vm->warnError) {
+ dvmDumpReferenceTable(&gDvm.jniGlobalRefTable,"JNI global");
+ LOGE("Excessive JNI global references (%d)\n", count);
+ dvmAbort();
+ } else {
+ LOGW("Excessive JNI global references (%d)\n", count);
+ }
+ }
+ }
+ }
+
+bail:
+ dvmUnlockMutex(&gDvm.jniGlobalRefLock);
+ return obj;
+}
+
+/*
+ * Remove a global reference. In most cases it's the entry most recently
+ * added, which makes this pretty quick.
+ *
+ * Thought: if it's not the most recent entry, just null it out. When we
+ * fill up, do a compaction pass before we expand the list.
+ */
+static void deleteGlobalReference(jobject obj)
+{
+ if (obj == NULL)
+ return;
+
+ dvmLockMutex(&gDvm.jniGlobalRefLock);
+
+ if (!dvmRemoveFromReferenceTable(&gDvm.jniGlobalRefTable,
+ gDvm.jniGlobalRefTable.table, obj))
+ {
+ LOGW("JNI: DeleteGlobalRef(%p) failed to find entry (valid=%d)\n",
+ obj, dvmIsValidObject((Object*) obj));
+ goto bail;
+ }
+
+ if (kTrackGrefUsage && gDvm.jniGrefLimit != 0) {
+ int count = dvmReferenceTableEntries(&gDvm.jniGlobalRefTable);
+ if (count < gDvm.jniGlobalRefLoMark) {
+ LOGD("GREF has decreased to %d\n", count);
+ gDvm.jniGlobalRefHiMark -= kGrefWaterInterval;
+ gDvm.jniGlobalRefLoMark -= kGrefWaterInterval;
+ }
+ }
+
+bail:
+ dvmUnlockMutex(&gDvm.jniGlobalRefLock);
+}
+
+/*
+ * GC helper function to mark all JNI global references.
+ */
+void dvmGcMarkJniGlobalRefs()
+{
+ Object **op;
+
+ dvmLockMutex(&gDvm.jniGlobalRefLock);
+
+ op = gDvm.jniGlobalRefTable.table;
+ while ((uintptr_t)op < (uintptr_t)gDvm.jniGlobalRefTable.nextEntry) {
+ dvmMarkObjectNonNull(*(op++));
+ }
+
+ dvmUnlockMutex(&gDvm.jniGlobalRefLock);
+}
+
+
+/*
+ * Determine if "obj" appears in the argument list for the native method.
+ *
+ * We use the "shorty" signature to determine which argument slots hold
+ * reference types.
+ */
+static bool findInArgList(Thread* self, Object* obj)
+{
+ const Method* meth;
+ u4* fp;
+ int i;
+
+ fp = self->curFrame;
+ while (1) {
+ /*
+ * Back up over JNI PushLocalFrame frames. This works because the
+ * previous frame on the interpreted stack is either a break frame
+ * (if we called here via native code) or an interpreted method (if
+ * we called here via the interpreter). In both cases the method
+ * pointer won't match.
+ */
+ StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
+ meth = saveArea->method;
+ if (meth != SAVEAREA_FROM_FP(saveArea->prevFrame)->method)
+ break;
+ fp = saveArea->prevFrame;
+ }
+
+ LOGVV("+++ scanning %d args in %s (%s)\n",
+ meth->insSize, meth->name, meth->shorty);
+ const char* shorty = meth->shorty +1; /* skip return type char */
+ for (i = 0; i < meth->insSize; i++) {
+ if (i == 0 && !dvmIsStaticMethod(meth)) {
+ /* first arg is "this" ref, not represented in "shorty" */
+ if (fp[i] == (u4) obj)
+ return true;
+ } else {
+ /* if this is a reference type, see if it matches */
+ switch (*shorty) {
+ case 'L':
+ if (fp[i] == (u4) obj)
+ return true;
+ break;
+ case 'D':
+ case 'J':
+ i++;
+ break;
+ case '\0':
+ LOGE("Whoops! ran off the end of %s (%d)\n",
+ meth->shorty, meth->insSize);
+ break;
+ default:
+ if (fp[i] == (u4) obj)
+ LOGI("NOTE: ref %p match on arg type %c\n", obj, *shorty);
+ break;
+ }
+ shorty++;
+ }
+ }
+
+ /*
+ * For static methods, we also pass a class pointer in.
+ */
+ if (dvmIsStaticMethod(meth)) {
+ //LOGI("+++ checking class pointer in %s\n", meth->name);
+ if ((void*)obj == (void*)meth->clazz)
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Verify that a reference passed in from native code is one that the
+ * code is allowed to have.
+ *
+ * It's okay for native code to pass us a reference that:
+ * - was just passed in as an argument when invoked by native code
+ * - was returned to it from JNI (and is now in the JNI local refs table)
+ * - is present in the JNI global refs table
+ * The first one is a little awkward. The latter two are just table lookups.
+ *
+ * Used by -Xcheck:jni and GetObjectRefType.
+ *
+ * NOTE: in the current VM, global and local references are identical. If
+ * something is both global and local, we can't tell them apart, and always
+ * return "local".
+ */
+jobjectRefType dvmGetJNIRefType(Object* obj)
+{
+ ReferenceTable* pRef = getLocalRefTable();
+ Thread* self = dvmThreadSelf();
+ //Object** top;
+ Object** ptr;
+
+ /* check args */
+ if (findInArgList(self, obj)) {
+ //LOGI("--- REF found %p on stack\n", obj);
+ return JNILocalRefType;
+ }
+
+ /* check locals */
+ //top = SAVEAREA_FROM_FP(self->curFrame)->xtra.localRefTop;
+ if (dvmFindInReferenceTable(pRef, pRef->table, obj) != NULL) {
+ //LOGI("--- REF found %p in locals\n", obj);
+ return JNILocalRefType;
+ }
+
+ /* check globals */
+ dvmLockMutex(&gDvm.jniGlobalRefLock);
+ if (dvmFindInReferenceTable(&gDvm.jniGlobalRefTable,
+ gDvm.jniGlobalRefTable.table, obj))
+ {
+ //LOGI("--- REF found %p in globals\n", obj);
+ dvmUnlockMutex(&gDvm.jniGlobalRefLock);
+ return JNIGlobalRefType;
+ }
+ dvmUnlockMutex(&gDvm.jniGlobalRefLock);
+
+ /* not found! */
+ return JNIInvalidRefType;
+}
+
+/*
+ * Register a method that uses JNI calling conventions.
+ */
+static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
+ const char* signature, void* fnPtr)
+{
+ Method* method;
+ bool result = false;
+
+ if (fnPtr == NULL)
+ goto bail;
+
+ method = dvmFindDirectMethodByDescriptor(clazz, methodName, signature);
+ if (method == NULL)
+ method = dvmFindVirtualMethodByDescriptor(clazz, methodName, signature);
+ if (method == NULL) {
+ LOGW("ERROR: Unable to find decl for native %s.%s %s\n",
+ clazz->descriptor, methodName, signature);
+ goto bail;
+ }
+
+ if (!dvmIsNativeMethod(method)) {
+ LOGW("Unable to register: not native: %s.%s %s\n",
+ clazz->descriptor, methodName, signature);
+ goto bail;
+ }
+
+ if (method->nativeFunc != dvmResolveNativeMethod) {
+ LOGW("Warning: %s.%s %s was already registered/resolved?\n",
+ clazz->descriptor, methodName, signature);
+ /* keep going, I guess */
+ }
+
+ /*
+ * Point "nativeFunc" at the JNI bridge, and overload "insns" to
+ * point at the actual function.
+ */
+ if (dvmIsSynchronizedMethod(method))
+ dvmSetNativeFunc(method, dvmCallSynchronizedJNIMethod, fnPtr);
+ else
+ dvmSetNativeFunc(method, dvmCallJNIMethod, fnPtr);
+
+ LOGV("JNI-registered %s.%s %s\n", clazz->descriptor, methodName,
+ signature);
+ result = true;
+
+bail:
+ return result;
+}
+
+/*
+ * Get the method currently being executed by examining the interp stack.
+ */
+const Method* dvmGetCurrentJNIMethod(void)
+{
+ assert(dvmThreadSelf() != NULL);
+
+ void* fp = dvmThreadSelf()->curFrame;
+ const Method* meth = SAVEAREA_FROM_FP(fp)->method;
+
+ assert(meth != NULL);
+ assert(dvmIsNativeMethod(meth));
+ return meth;
+}
+
+
+/*
+ * Track a JNI MonitorEnter in the current thread.
+ *
+ * The goal is to be able to "implicitly" release all JNI-held monitors
+ * when the thread detaches.
+ *
+ * Monitors may be entered multiple times, so we add a new entry for each
+ * enter call. It would be more efficient to keep a counter. At present
+ * there's no real motivation to improve this however.
+ */
+static void trackMonitorEnter(Thread* self, Object* obj)
+{
+ static const int kInitialSize = 16;
+ ReferenceTable* refTable = &self->jniMonitorRefTable;
+
+ /* init table on first use */
+ if (refTable->table == NULL) {
+ assert(refTable->maxEntries == 0);
+
+ if (!dvmInitReferenceTable(refTable, kInitialSize, INT_MAX)) {
+ LOGE("Unable to initialize monitor tracking table\n");
+ dvmAbort();
+ }
+ }
+
+ if (!dvmAddToReferenceTable(refTable, obj)) {
+ /* ran out of memory? could throw exception instead */
+ LOGE("Unable to add entry to monitor tracking table\n");
+ dvmAbort();
+ } else {
+ LOGVV("--- added monitor %p\n", obj);
+ }
+}
+
+/*
+ * Track a JNI MonitorExit in the current thread.
+ */
+static void trackMonitorExit(Thread* self, Object* obj)
+{
+ ReferenceTable* refTable = &self->jniMonitorRefTable;
+
+ if (!dvmRemoveFromReferenceTable(refTable, refTable->table, obj)) {
+ LOGE("JNI monitor %p not found in tracking list\n", obj);
+ /* keep going? */
+ } else {
+ LOGVV("--- removed monitor %p\n", obj);
+ }
+}
+
+/*
+ * Release all monitors held by the jniMonitorRefTable list.
+ */
+void dvmReleaseJniMonitors(Thread* self)
+{
+ ReferenceTable* refTable = &self->jniMonitorRefTable;
+ Object** top = refTable->table;
+
+ if (top == NULL)
+ return;
+
+ Object** ptr = refTable->nextEntry;
+ while (--ptr >= top) {
+ if (!dvmUnlockObject(self, *ptr)) {
+ LOGW("Unable to unlock monitor %p at thread detach\n", *ptr);
+ } else {
+ LOGVV("--- detach-releasing monitor %p\n", *ptr);
+ }
+ }
+
+ /* zap it */
+ refTable->nextEntry = refTable->table;
+}
+
+#ifdef WITH_JNI_STACK_CHECK
+/*
+ * Compute a CRC on the entire interpreted stack.
+ *
+ * Would be nice to compute it on "self" as well, but there are parts of
+ * the Thread that can be altered by other threads (e.g. prev/next pointers).
+ */
+static void computeStackSum(Thread* self)
+{
+ const u1* low = (const u1*)SAVEAREA_FROM_FP(self->curFrame);
+ u4 crc = dvmInitCrc32();
+ self->stackCrc = 0;
+ crc = dvmComputeCrc32(crc, low, self->interpStackStart - low);
+ self->stackCrc = crc;
+}
+
+/*
+ * Compute a CRC on the entire interpreted stack, and compare it to what
+ * we previously computed.
+ *
+ * We can execute JNI directly from native code without calling in from
+ * interpreted code during VM initialization and immediately after JNI
+ * thread attachment. Another opportunity exists during JNI_OnLoad. Rather
+ * than catching these cases we just ignore them here, which is marginally
+ * less accurate but reduces the amount of code we have to touch with #ifdefs.
+ */
+static void checkStackSum(Thread* self)
+{
+ const u1* low = (const u1*)SAVEAREA_FROM_FP(self->curFrame);
+ u4 stackCrc, crc;
+
+ stackCrc = self->stackCrc;
+ self->stackCrc = 0;
+ crc = dvmInitCrc32();
+ crc = dvmComputeCrc32(crc, low, self->interpStackStart - low);
+ if (crc != stackCrc) {
+ const Method* meth = dvmGetCurrentJNIMethod();
+ if (dvmComputeExactFrameDepth(self->curFrame) == 1) {
+ LOGD("JNI: bad stack CRC (0x%08x) -- okay during init\n",
+ stackCrc);
+ } else if (strcmp(meth->name, "nativeLoad") == 0 &&
+ (strcmp(meth->clazz->descriptor, "Ljava/lang/Runtime;") == 0))
+ {
+ LOGD("JNI: bad stack CRC (0x%08x) -- okay during JNI_OnLoad\n",
+ stackCrc);
+ } else {
+ LOGW("JNI: bad stack CRC (%08x vs %08x)\n", crc, stackCrc);
+ dvmAbort();
+ }
+ }
+ self->stackCrc = (u4) -1; /* make logic errors more noticeable */
+}
+#endif
+
+
+/*
+ * ===========================================================================
+ * JNI implementation
+ * ===========================================================================
+ */
+
+/*
+ * Return the version of the native method interface.
+ */
+static jint GetVersion(JNIEnv* env)
+{
+ JNI_ENTER();
+ /*
+ * There is absolutely no need to toggle the mode for correct behavior.
+ * However, it does provide native code with a simple "suspend self
+ * if necessary" call.
+ */
+ JNI_EXIT();
+ return JNI_VERSION_1_6;
+}
+
+/*
+ * Create a new class from a bag of bytes.
+ *
+ * This is not currently supported within Dalvik.
+ */
+static jclass DefineClass(JNIEnv* env, const char *name, jobject loader,
+ const jbyte* buf, jsize bufLen)
+{
+ UNUSED_PARAMETER(name);
+ UNUSED_PARAMETER(loader);
+ UNUSED_PARAMETER(buf);
+ UNUSED_PARAMETER(bufLen);
+
+ JNI_ENTER();
+ LOGW("Rejecting JNI DefineClass request\n");
+ JNI_EXIT();
+ return NULL;
+}
+
+/*
+ * Find a class by name.
+ *
+ * We have to use the "no init" version of FindClass here, because we might
+ * be getting the class prior to registering native methods that will be
+ * used in <clinit>.
+ *
+ * We need to get the class loader associated with the current native
+ * method. If there is no native method, e.g. we're calling this from native
+ * code right after creating the VM, the spec says we need to use the class
+ * loader returned by "ClassLoader.getBaseClassLoader". There is no such
+ * method, but it's likely they meant ClassLoader.getSystemClassLoader.
+ * We can't get that until after the VM has initialized though.
+ */
+static jclass FindClass(JNIEnv* env, const char* name)
+{
+ JNI_ENTER();
+
+ const Method* thisMethod;
+ ClassObject* clazz;
+ Object* loader;
+ char* descriptor = NULL;
+
+ thisMethod = dvmGetCurrentJNIMethod();
+ assert(thisMethod != NULL);
+
+ descriptor = dvmNameToDescriptor(name);
+ if (descriptor == NULL) {
+ clazz = NULL;
+ goto bail;
+ }
+
+ //Thread* self = dvmThreadSelf();
+ if (_self->classLoaderOverride != NULL) {
+ /* hack for JNI_OnLoad */
+ assert(strcmp(thisMethod->name, "nativeLoad") == 0);
+ loader = _self->classLoaderOverride;
+ } else if (thisMethod == gDvm.methFakeNativeEntry) {
+ /* start point of invocation interface */
+ if (!gDvm.initializing)
+ loader = dvmGetSystemClassLoader();
+ else
+ loader = NULL;
+ } else {
+ loader = thisMethod->clazz->classLoader;
+ }
+
+ clazz = dvmFindClassNoInit(descriptor, loader);
+ clazz = addLocalReference(clazz);
+
+bail:
+ free(descriptor);
+
+ JNI_EXIT();
+ return (jclass)clazz;
+}
+
+/*
+ * Return the superclass of a class.
+ */
+static jclass GetSuperclass(JNIEnv* env, jclass clazz)
+{
+ JNI_ENTER();
+ jclass super = (jclass) ((ClassObject*) clazz)->super;
+ super = addLocalReference(super);
+ JNI_EXIT();
+ return super;
+}
+
+/*
+ * Determine whether an object of clazz1 can be safely cast to clazz2.
+ *
+ * Like IsInstanceOf, but with a pair of class objects instead of obj+class.
+ */
+static jboolean IsAssignableFrom(JNIEnv* env, jclass clazz1, jclass clazz2)
+{
+ JNI_ENTER();
+
+ jboolean result;
+ result = dvmInstanceof((ClassObject*) clazz1, (ClassObject*) clazz2);
+
+ JNI_EXIT();
+ return result;
+}
+
+/*
+ * Given a java.lang.reflect.Method or .Constructor, return a methodID.
+ */
+static jmethodID FromReflectedMethod(JNIEnv* env, jobject method)
+{
+ JNI_ENTER();
+ jmethodID methodID;
+ methodID = (jmethodID) dvmGetMethodFromReflectObj((Object*)method);
+ JNI_EXIT();
+ return methodID;
+}
+
+/*
+ * Given a java.lang.reflect.Field, return a fieldID.
+ */
+static jfieldID FromReflectedField(JNIEnv* env, jobject field)
+{
+ JNI_ENTER();
+ jfieldID fieldID = (jfieldID) dvmGetFieldFromReflectObj((Object*)field);
+ JNI_EXIT();
+ return fieldID;
+}
+
+/*
+ * Convert a methodID to a java.lang.reflect.Method or .Constructor.
+ *
+ * (The "isStatic" field does not appear in the spec.)
+ *
+ * Throws OutOfMemory and returns NULL on failure.
+ */
+static jobject ToReflectedMethod(JNIEnv* env, jclass cls, jmethodID methodID,
+ jboolean isStatic)
+{
+ JNI_ENTER();
+ jobject obj;
+ obj = (jobject) dvmCreateReflectObjForMethod((ClassObject*) cls,
+ (Method*) methodID);
+ dvmReleaseTrackedAlloc(obj, NULL);
+ obj = addLocalReference(obj);
+ JNI_EXIT();
+ return obj;
+}
+
+/*
+ * Convert a fieldID to a java.lang.reflect.Field.
+ *
+ * (The "isStatic" field does not appear in the spec.)
+ *
+ * Throws OutOfMemory and returns NULL on failure.
+ */
+static jobject ToReflectedField(JNIEnv* env, jclass cls, jfieldID fieldID,
+ jboolean isStatic)
+{
+ JNI_ENTER();
+ jobject obj;
+ obj = (jobject) dvmCreateReflectObjForField((ClassObject*) cls,
+ (Field*) fieldID);
+ dvmReleaseTrackedAlloc(obj, NULL);
+ obj = addLocalReference(obj);
+ JNI_EXIT();
+ return obj;
+}
+
+
+/*
+ * Take this exception and throw it.
+ */
+static jint Throw(JNIEnv* env, jthrowable obj)
+{
+ JNI_ENTER();
+
+ jint retval;
+
+ if (obj != NULL) {
+ dvmSetException(_self, obj);
+ retval = JNI_OK;
+ } else
+ retval = JNI_ERR;
+
+ JNI_EXIT();
+ return retval;
+}
+
+/*
+ * Constructs an exeption object from the specified class with the message
+ * specified by "message", and throws it.
+ */
+static jint ThrowNew(JNIEnv* env, jclass clazz, const char* message)
+{
+ JNI_ENTER();
+
+ ClassObject* classObj = (ClassObject*) clazz;
+
+ dvmThrowExceptionByClass(classObj, message);
+
+ JNI_EXIT();
+ return JNI_OK;
+}
+
+/*
+ * If an exception is being thrown, return the exception object. Otherwise,
+ * return NULL.
+ *
+ * TODO: if there is no pending exception, we should be able to skip the
+ * enter/exit checks. If we find one, we need to enter and then re-fetch
+ * the exception (in case it got moved by a compacting GC).
+ */
+static jthrowable ExceptionOccurred(JNIEnv* env)
+{
+ JNI_ENTER();
+
+ Object* exception;
+ Object* localException;
+
+ exception = (Object*) dvmGetException(_self);
+ localException = addLocalReference(exception);
+ if (localException == NULL && exception != NULL) {
+ /*
+ * We were unable to add a new local reference, and threw a new
+ * exception. We can't return "exception", because it's not a
+ * local reference. So we have to return NULL, indicating that
+ * there was no exception, even though it's pretty much raining
+ * exceptions in here.
+ */
+ LOGW("JNI WARNING: addLocal/exception combo\n");
+ }
+
+ JNI_EXIT();
+ return localException;
+}
+
+/*
+ * Print an exception and stack trace to stderr.
+ */
+static void ExceptionDescribe(JNIEnv* env)
+{
+ JNI_ENTER();
+
+ Object* exception = dvmGetException(_self);
+ if (exception != NULL) {
+ dvmPrintExceptionStackTrace();
+ } else {
+ LOGI("Odd: ExceptionDescribe called, but no exception pending\n");
+ }
+
+ JNI_EXIT();
+}
+
+/*
+ * Clear the exception currently being thrown.
+ *
+ * TODO: we should be able to skip the enter/exit stuff.
+ */
+static void ExceptionClear(JNIEnv* env)
+{
+ JNI_ENTER();
+ dvmClearException(_self);
+ JNI_EXIT();
+}
+
+/*
+ * Kill the VM. This function does not return.
+ */
+static void FatalError(JNIEnv* env, const char* msg)
+{
+ //dvmChangeStatus(NULL, THREAD_RUNNING);
+ LOGE("JNI posting fatal error: %s\n", msg);
+ dvmAbort();
+}
+
+/*
+ * Push a new JNI frame on the stack, with a new set of locals.
+ *
+ * The new frame must have the same method pointer. (If for no other
+ * reason than FindClass needs it to get the appropriate class loader.)
+ */
+static jint PushLocalFrame(JNIEnv* env, jint capacity)
+{
+ JNI_ENTER();
+ int result = JNI_OK;
+ if (!ensureLocalCapacity(capacity) ||
+ !dvmPushLocalFrame(_self /*dvmThreadSelf()*/, dvmGetCurrentJNIMethod()))
+ {
+ /* yes, OutOfMemoryError, not StackOverflowError */
+ dvmClearException(_self);
+ dvmThrowException("Ljava/lang/OutOfMemoryError;",
+ "out of stack in JNI PushLocalFrame");
+ result = JNI_ERR;
+ }
+ JNI_EXIT();
+ return result;
+}
+
+/*
+ * Pop the local frame off. If "result" is not null, add it as a
+ * local reference on the now-current frame.
+ */
+static jobject PopLocalFrame(JNIEnv* env, jobject result)
+{
+ JNI_ENTER();
+ if (!dvmPopLocalFrame(_self /*dvmThreadSelf()*/)) {
+ LOGW("JNI WARNING: too many PopLocalFrame calls\n");
+ dvmClearException(_self);
+ dvmThrowException("Ljava/lang/RuntimeException;",
+ "too many PopLocalFrame calls");
+ }
+ result = addLocalReference(result);
+ JNI_EXIT();
+ return result;
+}
+
+/*
+ * Add a reference to the global list.
+ */
+static jobject NewGlobalRef(JNIEnv* env, jobject obj)
+{
+ JNI_ENTER();
+ jobject retval = addGlobalReference(obj);
+ JNI_EXIT();
+ return retval;
+}
+
+/*
+ * Delete a reference from the global list.
+ */
+static void DeleteGlobalRef(JNIEnv* env, jobject globalRef)
+{
+ JNI_ENTER();
+ deleteGlobalReference(globalRef);
+ JNI_EXIT();
+}
+
+
+/*
+ * Add a reference to the local list.
+ */
+static jobject NewLocalRef(JNIEnv* env, jobject ref)
+{
+ JNI_ENTER();
+
+ jobject retval = addLocalReference(ref);
+
+ JNI_EXIT();
+ return retval;
+}
+
+/*
+ * Delete a reference from the local list.
+ */
+static void DeleteLocalRef(JNIEnv* env, jobject localRef)
+{
+ JNI_ENTER();
+ deleteLocalReference(localRef);
+ JNI_EXIT();
+}
+
+/*
+ * Ensure that the local references table can hold at least this many
+ * references.
+ */
+static jint EnsureLocalCapacity(JNIEnv *env, jint capacity)
+{
+ JNI_ENTER();
+ bool okay = ensureLocalCapacity(capacity);
+ if (!okay) {
+ dvmThrowException("Ljava/lang/OutOfMemoryError;",
+ "can't ensure local reference capacity");
+ }
+ JNI_EXIT();
+ if (okay)
+ return 0;
+ else
+ return -1;
+}
+
+
+/*
+ * Determine whether two Object references refer to the same underlying object.
+ */
+static jboolean IsSameObject(JNIEnv* env, jobject ref1, jobject ref2)
+{
+ JNI_ENTER();
+ jboolean result = (ref1 == ref2);
+ JNI_EXIT();
+ return result;
+}
+
+/*
+ * Allocate a new object without invoking any constructors.
+ */
+static jobject AllocObject(JNIEnv* env, jclass jclazz)
+{
+ JNI_ENTER();
+
+ ClassObject* clazz = (ClassObject*) jclazz;
+ jobject newObj;
+
+ if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
+ assert(dvmCheckException(_self));
+ newObj = NULL;
+ } else {
+ newObj = (jobject) dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+ newObj = addLocalReference(newObj);
+ }
+
+ JNI_EXIT();
+ return newObj;
+}
+
+/*
+ * Construct a new object.
+ */
+static jobject NewObject(JNIEnv* env, jclass jclazz, jmethodID methodID, ...)
+{
+ JNI_ENTER();
+
+ ClassObject* clazz = (ClassObject*) jclazz;
+ jobject newObj;
+
+ if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
+ assert(dvmCheckException(_self));
+ newObj = NULL;
+ } else {
+ newObj = (jobject) dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+ newObj = addLocalReference(newObj);
+ if (newObj != NULL) {
+ JValue unused;
+ va_list args;
+ va_start(args, methodID);
+ dvmCallMethodV(_self, (Method*) methodID, (Object*)newObj, &unused,
+ args);
+ va_end(args);
+ }
+ }
+
+ JNI_EXIT();
+ return newObj;
+}
+static jobject NewObjectV(JNIEnv* env, jclass clazz, jmethodID methodID,
+ va_list args)
+{
+ JNI_ENTER();
+
+ jobject newObj;
+ newObj = (jobject) dvmAllocObject((ClassObject*) clazz, ALLOC_DONT_TRACK);
+ newObj = addLocalReference(newObj);
+ if (newObj != NULL) {
+ JValue unused;
+ dvmCallMethodV(_self, (Method*) methodID, (Object*)newObj, &unused,
+ args);
+ }
+
+ JNI_EXIT();
+ return newObj;
+}
+static jobject NewObjectA(JNIEnv* env, jclass clazz, jmethodID methodID,
+ jvalue* args)
+{
+ JNI_ENTER();
+
+ jobject newObj;
+ newObj = (jobject) dvmAllocObject((ClassObject*) clazz, ALLOC_DONT_TRACK);
+ newObj = addLocalReference(newObj);
+ if (newObj != NULL) {
+ JValue unused;
+ dvmCallMethodA(_self, (Method*) methodID, (Object*)newObj, &unused,
+ args);
+ }
+
+ JNI_EXIT();
+ return newObj;
+}
+
+/*
+ * Returns the class of an object.
+ *
+ * JNI spec says: obj must not be NULL.
+ */
+static jclass GetObjectClass(JNIEnv* env, jobject obj)
+{
+ JNI_ENTER();
+
+ assert(obj != NULL);
+
+ jclass clazz;
+ clazz = (jclass) ((Object*)obj)->clazz;
+ clazz = addLocalReference(clazz);
+
+ JNI_EXIT();
+ return clazz;
+}
+
+/*
+ * Determine whether "obj" is an instance of "clazz".
+ */
+static jboolean IsInstanceOf(JNIEnv* env, jobject obj, jclass clazz)
+{
+ JNI_ENTER();
+
+ jboolean result;
+
+ if (obj == NULL)
+ result = true;
+ else
+ result = dvmInstanceof(((Object*)obj)->clazz, (ClassObject*) clazz);
+
+ JNI_EXIT();
+ return result;
+}
+
+/*
+ * Get a method ID for an instance method.
+ *
+ * JNI defines <init> as an instance method, but Dalvik considers it a
+ * "direct" method, so we have to special-case it here.
+ *
+ * Dalvik also puts all private methods into the "direct" list, so we
+ * really need to just search both lists.
+ */
+static jmethodID GetMethodID(JNIEnv* env, jclass jclazz, const char* name,
+ const char* sig)
+{
+ JNI_ENTER();
+
+ ClassObject* clazz = (ClassObject*) jclazz;
+ jmethodID id = NULL;
+
+ if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
+ assert(dvmCheckException(_self));
+ } else {
+ Method* meth;
+
+ meth = dvmFindVirtualMethodHierByDescriptor(clazz, name, sig);
+ if (meth == NULL) {
+ /* search private methods and constructors; non-hierarchical */
+ meth = dvmFindDirectMethodByDescriptor(clazz, name, sig);
+ }
+ if (meth != NULL && dvmIsStaticMethod(meth)) {
+ IF_LOGD() {
+ char* desc = dexProtoCopyMethodDescriptor(&meth->prototype);
+ LOGD("GetMethodID: not returning static method %s.%s %s\n",
+ clazz->descriptor, meth->name, desc);
+ free(desc);
+ }
+ meth = NULL;
+ }
+ if (meth == NULL) {
+ LOGI("Method not found: '%s' '%s' in %s\n",
+ name, sig, clazz->descriptor);
+ dvmThrowException("Ljava/lang/NoSuchMethodError;", name);
+ }
+
+ /*
+ * The method's class may not be the same as clazz, but if
+ * it isn't this must be a virtual method and the class must
+ * be a superclass (and, hence, already initialized).
+ */
+ if (meth != NULL) {
+ assert(dvmIsClassInitialized(meth->clazz) ||
+ dvmIsClassInitializing(meth->clazz));
+ }
+ id = (jmethodID) meth;
+ }
+ JNI_EXIT();
+ return id;
+}
+
+/*
+ * Get a field ID (instance fields).
+ */
+static jfieldID GetFieldID(JNIEnv* env, jclass jclazz,
+ const char* name, const char* sig)
+{
+ JNI_ENTER();
+
+ ClassObject* clazz = (ClassObject*) jclazz;
+ jfieldID id;
+
+ if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
+ assert(dvmCheckException(_self));
+ id = NULL;
+ } else {
+ id = (jfieldID) dvmFindInstanceFieldHier(clazz, name, sig);
+ if (id == NULL)
+ dvmThrowException("Ljava/lang/NoSuchFieldError;", name);
+ }
+ JNI_EXIT();
+ return id;
+}
+
+/*
+ * Get the method ID for a static method in a class.
+ */
+static jmethodID GetStaticMethodID(JNIEnv* env, jclass jclazz,
+ const char* name, const char* sig)
+{
+ JNI_ENTER();
+
+ ClassObject* clazz = (ClassObject*) jclazz;
+ jmethodID id;
+
+ if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
+ assert(dvmCheckException(_self));
+ id = NULL;
+ } else {
+ Method* meth;
+
+ meth = dvmFindDirectMethodHierByDescriptor(clazz, name, sig);
+
+ /* make sure it's static, not virtual+private */
+ if (meth != NULL && !dvmIsStaticMethod(meth)) {
+ IF_LOGD() {
+ char* desc = dexProtoCopyMethodDescriptor(&meth->prototype);
+ LOGD("GetStaticMethodID: "
+ "not returning nonstatic method %s.%s %s\n",
+ clazz->descriptor, meth->name, desc);
+ free(desc);
+ }
+ meth = NULL;
+ }
+
+ id = (jmethodID) meth;
+ if (id == NULL)
+ dvmThrowException("Ljava/lang/NoSuchMethodError;", name);
+ }
+
+ JNI_EXIT();
+ return id;
+}
+
+/*
+ * Get a field ID (static fields).
+ */
+static jfieldID GetStaticFieldID(JNIEnv* env, jclass jclazz,
+ const char* name, const char* sig)
+{
+ JNI_ENTER();
+
+ ClassObject* clazz = (ClassObject*) jclazz;
+ jfieldID id;
+
+ if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
+ assert(dvmCheckException(_self));
+ id = NULL;
+ } else {
+ id = (jfieldID) dvmFindStaticField(clazz, name, sig);
+ if (id == NULL)
+ dvmThrowException("Ljava/lang/NoSuchFieldError;", name);
+ }
+ JNI_EXIT();
+ return id;
+}
+
+/*
+ * Get a static field.
+ *
+ * If we get an object reference, add it to the local refs list.
+ */
+#define GET_STATIC_TYPE_FIELD(_ctype, _jname, _isref) \
+ static _ctype GetStatic##_jname##Field(JNIEnv* env, jclass clazz, \
+ jfieldID fieldID) \
+ { \
+ UNUSED_PARAMETER(clazz); \
+ JNI_ENTER(); \
+ StaticField* sfield = (StaticField*) fieldID; \
+ _ctype value = dvmGetStaticField##_jname(sfield); \
+ if (_isref) /* only when _ctype==jobject */ \
+ value = (_ctype)(u4)addLocalReference((jobject)(u4)value); \
+ JNI_EXIT(); \
+ return value; \
+ }
+GET_STATIC_TYPE_FIELD(jobject, Object, true);
+GET_STATIC_TYPE_FIELD(jboolean, Boolean, false);
+GET_STATIC_TYPE_FIELD(jbyte, Byte, false);
+GET_STATIC_TYPE_FIELD(jchar, Char, false);
+GET_STATIC_TYPE_FIELD(jshort, Short, false);
+GET_STATIC_TYPE_FIELD(jint, Int, false);
+GET_STATIC_TYPE_FIELD(jlong, Long, false);
+GET_STATIC_TYPE_FIELD(jfloat, Float, false);
+GET_STATIC_TYPE_FIELD(jdouble, Double, false);
+
+/*
+ * Set a static field.
+ */
+#define SET_STATIC_TYPE_FIELD(_ctype, _jname, _jvfld) \
+ static void SetStatic##_jname##Field(JNIEnv* env, jclass clazz, \
+ jfieldID fieldID, _ctype value) \
+ { \
+ UNUSED_PARAMETER(clazz); \
+ JNI_ENTER(); \
+ StaticField* sfield = (StaticField*) fieldID; \
+ dvmSetStaticField##_jname(sfield, value); \
+ JNI_EXIT(); \
+ }
+SET_STATIC_TYPE_FIELD(jobject, Object, l);
+SET_STATIC_TYPE_FIELD(jboolean, Boolean, z);
+SET_STATIC_TYPE_FIELD(jbyte, Byte, b);
+SET_STATIC_TYPE_FIELD(jchar, Char, c);
+SET_STATIC_TYPE_FIELD(jshort, Short, s);
+SET_STATIC_TYPE_FIELD(jint, Int, i);
+SET_STATIC_TYPE_FIELD(jlong, Long, j);
+SET_STATIC_TYPE_FIELD(jfloat, Float, f);
+SET_STATIC_TYPE_FIELD(jdouble, Double, d);
+
+/*
+ * Get an instance field.
+ *
+ * If we get an object reference, add it to the local refs list.
+ */
+#define GET_TYPE_FIELD(_ctype, _jname, _isref) \
+ static _ctype Get##_jname##Field(JNIEnv* env, jobject obj, \
+ jfieldID fieldID) \
+ { \
+ JNI_ENTER(); \
+ InstField* field = (InstField*) fieldID; \
+ _ctype value = dvmGetField##_jname((Object*) obj,field->byteOffset);\
+ if (_isref) /* only when _ctype==jobject */ \
+ value = (_ctype)(u4)addLocalReference((jobject)(u4)value); \
+ JNI_EXIT(); \
+ return value; \
+ }
+GET_TYPE_FIELD(jobject, Object, true);
+GET_TYPE_FIELD(jboolean, Boolean, false);
+GET_TYPE_FIELD(jbyte, Byte, false);
+GET_TYPE_FIELD(jchar, Char, false);
+GET_TYPE_FIELD(jshort, Short, false);
+GET_TYPE_FIELD(jint, Int, false);
+GET_TYPE_FIELD(jlong, Long, false);
+GET_TYPE_FIELD(jfloat, Float, false);
+GET_TYPE_FIELD(jdouble, Double, false);
+
+/*
+ * Set an instance field.
+ */
+#define SET_TYPE_FIELD(_ctype, _jname) \
+ static void Set##_jname##Field(JNIEnv* env, jobject obj, \
+ jfieldID fieldID, _ctype value) \
+ { \
+ JNI_ENTER(); \
+ InstField* field = (InstField*) fieldID; \
+ dvmSetField##_jname((Object*) obj, field->byteOffset, value); \
+ JNI_EXIT(); \
+ }
+SET_TYPE_FIELD(jobject, Object);
+SET_TYPE_FIELD(jboolean, Boolean);
+SET_TYPE_FIELD(jbyte, Byte);
+SET_TYPE_FIELD(jchar, Char);
+SET_TYPE_FIELD(jshort, Short);
+SET_TYPE_FIELD(jint, Int);
+SET_TYPE_FIELD(jlong, Long);
+SET_TYPE_FIELD(jfloat, Float);
+SET_TYPE_FIELD(jdouble, Double);
+
+/*
+ * Make a virtual method call.
+ *
+ * Three versions (..., va_list, jvalue[]) for each return type. If we're
+ * returning an Object, we have to add it to the local references table.
+ */
+#define CALL_VIRTUAL(_ctype, _jname, _retfail, _retok, _isref) \
+ static _ctype Call##_jname##Method(JNIEnv* env, jobject obj, \
+ jmethodID methodID, ...) \
+ { \
+ JNI_ENTER(); \
+ Object* dobj = (Object*) obj; \
+ const Method* meth; \
+ va_list args; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod(dobj->clazz, (Method*)methodID); \
+ if (meth == NULL) { \
+ JNI_EXIT(); \
+ return _retfail; \
+ } \
+ va_start(args, methodID); \
+ dvmCallMethodV(_self, meth, dobj, &result, args); \
+ va_end(args); \
+ if (_isref) \
+ result.l = addLocalReference(result.l); \
+ JNI_EXIT(); \
+ return _retok; \
+ } \
+ static _ctype Call##_jname##MethodV(JNIEnv* env, jobject obj, \
+ jmethodID methodID, va_list args) \
+ { \
+ JNI_ENTER(); \
+ Object* dobj = (Object*) obj; \
+ const Method* meth; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod(dobj->clazz, (Method*)methodID); \
+ if (meth == NULL) { \
+ JNI_EXIT(); \
+ return _retfail; \
+ } \
+ dvmCallMethodV(_self, meth, dobj, &result, args); \
+ if (_isref) \
+ result.l = addLocalReference(result.l); \
+ JNI_EXIT(); \
+ return _retok; \
+ } \
+ static _ctype Call##_jname##MethodA(JNIEnv* env, jobject obj, \
+ jmethodID methodID, jvalue* args) \
+ { \
+ JNI_ENTER(); \
+ Object* dobj = (Object*) obj; \
+ const Method* meth; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod(dobj->clazz, (Method*)methodID); \
+ if (meth == NULL) { \
+ JNI_EXIT(); \
+ return _retfail; \
+ } \
+ dvmCallMethodA(_self, meth, dobj, &result, args); \
+ if (_isref) \
+ result.l = addLocalReference(result.l); \
+ JNI_EXIT(); \
+ return _retok; \
+ }
+CALL_VIRTUAL(jobject, Object, NULL, result.l, true);
+CALL_VIRTUAL(jboolean, Boolean, 0, result.z, false);
+CALL_VIRTUAL(jbyte, Byte, 0, result.b, false);
+CALL_VIRTUAL(jchar, Char, 0, result.c, false);
+CALL_VIRTUAL(jshort, Short, 0, result.s, false);
+CALL_VIRTUAL(jint, Int, 0, result.i, false);
+CALL_VIRTUAL(jlong, Long, 0, result.j, false);
+CALL_VIRTUAL(jfloat, Float, 0.0f, result.f, false);
+CALL_VIRTUAL(jdouble, Double, 0.0, result.d, false);
+CALL_VIRTUAL(void, Void, , , false);
+
+/*
+ * Make a "non-virtual" method call. We're still calling a virtual method,
+ * but this time we're not doing an indirection through the object's vtable.
+ * The "clazz" parameter defines which implementation of a method we want.
+ *
+ * Three versions (..., va_list, jvalue[]) for each return type.
+ */
+#define CALL_NONVIRTUAL(_ctype, _jname, _retfail, _retok, _isref) \
+ static _ctype CallNonvirtual##_jname##Method(JNIEnv* env, jobject obj, \
+ jclass clazz, jmethodID methodID, ...) \
+ { \
+ JNI_ENTER(); \
+ Object* dobj = (Object*) obj; \
+ const Method* meth; \
+ va_list args; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod((ClassObject*)clazz, \
+ (Method*)methodID); \
+ if (meth == NULL) { \
+ JNI_EXIT(); \
+ return _retfail; \
+ } \
+ va_start(args, methodID); \
+ dvmCallMethodV(_self, meth, dobj, &result, args); \
+ if (_isref) \
+ result.l = addLocalReference(result.l); \
+ va_end(args); \
+ JNI_EXIT(); \
+ return _retok; \
+ } \
+ static _ctype CallNonvirtual##_jname##MethodV(JNIEnv* env, jobject obj, \
+ jclass clazz, jmethodID methodID, va_list args) \
+ { \
+ JNI_ENTER(); \
+ Object* dobj = (Object*) obj; \
+ const Method* meth; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod((ClassObject*)clazz, \
+ (Method*)methodID); \
+ if (meth == NULL) { \
+ JNI_EXIT(); \
+ return _retfail; \
+ } \
+ dvmCallMethodV(_self, meth, dobj, &result, args); \
+ if (_isref) \
+ result.l = addLocalReference(result.l); \
+ JNI_EXIT(); \
+ return _retok; \
+ } \
+ static _ctype CallNonvirtual##_jname##MethodA(JNIEnv* env, jobject obj, \
+ jclass clazz, jmethodID methodID, jvalue* args) \
+ { \
+ JNI_ENTER(); \
+ Object* dobj = (Object*) obj; \
+ const Method* meth; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod((ClassObject*)clazz, \
+ (Method*)methodID); \
+ if (meth == NULL) { \
+ JNI_EXIT(); \
+ return _retfail; \
+ } \
+ dvmCallMethodA(_self, meth, dobj, &result, args); \
+ if (_isref) \
+ result.l = addLocalReference(result.l); \
+ JNI_EXIT(); \
+ return _retok; \
+ }
+CALL_NONVIRTUAL(jobject, Object, NULL, result.l, true);
+CALL_NONVIRTUAL(jboolean, Boolean, 0, result.z, false);
+CALL_NONVIRTUAL(jbyte, Byte, 0, result.b, false);
+CALL_NONVIRTUAL(jchar, Char, 0, result.c, false);
+CALL_NONVIRTUAL(jshort, Short, 0, result.s, false);
+CALL_NONVIRTUAL(jint, Int, 0, result.i, false);
+CALL_NONVIRTUAL(jlong, Long, 0, result.j, false);
+CALL_NONVIRTUAL(jfloat, Float, 0.0f, result.f, false);
+CALL_NONVIRTUAL(jdouble, Double, 0.0, result.d, false);
+CALL_NONVIRTUAL(void, Void, , , false);
+
+
+/*
+ * Call a static method.
+ */
+#define CALL_STATIC(_ctype, _jname, _retfail, _retok, _isref) \
+ static _ctype CallStatic##_jname##Method(JNIEnv* env, jclass clazz, \
+ jmethodID methodID, ...) \
+ { \
+ JNI_ENTER(); \
+ JValue result; \
+ va_list args; \
+ assert((ClassObject*) clazz == ((Method*)methodID)->clazz); \
+ va_start(args, methodID); \
+ dvmCallMethodV(_self, (Method*) methodID, NULL, &result, args); \
+ va_end(args); \
+ if (_isref) \
+ result.l = addLocalReference(result.l); \
+ JNI_EXIT(); \
+ return _retok; \
+ } \
+ static _ctype CallStatic##_jname##MethodV(JNIEnv* env, jclass clazz, \
+ jmethodID methodID, va_list args) \
+ { \
+ JNI_ENTER(); \
+ JValue result; \
+ assert((ClassObject*) clazz == ((Method*)methodID)->clazz); \
+ dvmCallMethodV(_self, (Method*) methodID, NULL, &result, args); \
+ if (_isref) \
+ result.l = addLocalReference(result.l); \
+ JNI_EXIT(); \
+ return _retok; \
+ } \
+ static _ctype CallStatic##_jname##MethodA(JNIEnv* env, jclass clazz, \
+ jmethodID methodID, jvalue* args) \
+ { \
+ JNI_ENTER(); \
+ JValue result; \
+ assert((ClassObject*) clazz == ((Method*)methodID)->clazz); \
+ dvmCallMethodA(_self, (Method*) methodID, NULL, &result, args); \
+ if (_isref) \
+ result.l = addLocalReference(result.l); \
+ JNI_EXIT(); \
+ return _retok; \
+ }
+CALL_STATIC(jobject, Object, NULL, result.l, true);
+CALL_STATIC(jboolean, Boolean, 0, result.z, false);
+CALL_STATIC(jbyte, Byte, 0, result.b, false);
+CALL_STATIC(jchar, Char, 0, result.c, false);
+CALL_STATIC(jshort, Short, 0, result.s, false);
+CALL_STATIC(jint, Int, 0, result.i, false);
+CALL_STATIC(jlong, Long, 0, result.j, false);
+CALL_STATIC(jfloat, Float, 0.0f, result.f, false);
+CALL_STATIC(jdouble, Double, 0.0, result.d, false);
+CALL_STATIC(void, Void, , , false);
+
+/*
+ * Create a new String from Unicode data.
+ *
+ * If "len" is zero, we will return an empty string even if "unicodeChars"
+ * is NULL. (The JNI spec is vague here.)
+ */
+static jstring NewString(JNIEnv* env, const jchar* unicodeChars, jsize len)
+{
+ JNI_ENTER();
+
+ StringObject* jstr;
+ jstr = dvmCreateStringFromUnicode(unicodeChars, len);
+ if (jstr != NULL) {
+ dvmReleaseTrackedAlloc((Object*) jstr, NULL);
+ jstr = addLocalReference((jstring) jstr);
+ }
+
+ JNI_EXIT();
+ return jstr;
+}
+
+/*
+ * Return the length of a String in Unicode character units.
+ */
+static jsize GetStringLength(JNIEnv* env, jstring string)
+{
+ JNI_ENTER();
+
+ jsize len = dvmStringLen((StringObject*) string);
+
+ JNI_EXIT();
+ return len;
+}
+
+/*
+ * Get a pointer to the string's character data.
+ *
+ * The result is guaranteed to be valid until ReleaseStringChars is
+ * called, which means we can't just hold a reference to it in the local
+ * refs table. We have to add it to the global refs.
+ *
+ * Technically, we don't need to hold a reference to the String, but rather
+ * to the Char[] object within the String.
+ *
+ * We could also just allocate some storage and copy the data into it,
+ * but it's a choice between our synchronized global reference table and
+ * libc's synchronized heap allocator.
+ */
+static const jchar* GetStringChars(JNIEnv* env, jstring string,
+ jboolean* isCopy)
+{
+ JNI_ENTER();
+
+ const u2* data = dvmStringChars((StringObject*) string);
+ addGlobalReference(string);
+
+ if (isCopy != NULL)
+ *isCopy = JNI_FALSE;
+
+ JNI_EXIT();
+ return (jchar*)data;
+}
+
+/*
+ * Release our grip on some characters from a string.
+ */
+static void ReleaseStringChars(JNIEnv* env, jstring string, const jchar* chars)
+{
+ JNI_ENTER();
+ deleteGlobalReference(string);
+ JNI_EXIT();
+}
+
+/*
+ * Create a new java.lang.String object from chars in modified UTF-8 form.
+ *
+ * The spec doesn't say how to handle a NULL string. Popular desktop VMs
+ * accept it and return a NULL pointer in response.
+ */
+static jstring NewStringUTF(JNIEnv* env, const char* bytes)
+{
+ JNI_ENTER();
+
+ StringObject* newStr;
+
+ if (bytes == NULL) {
+ newStr = NULL;
+ } else {
+ newStr = dvmCreateStringFromCstr(bytes, ALLOC_DEFAULT);
+ if (newStr != NULL) {
+ dvmReleaseTrackedAlloc((Object*)newStr, NULL);
+ newStr = addLocalReference((jstring) newStr);
+ }
+ }
+
+ JNI_EXIT();
+ return (jstring)newStr;
+}
+
+/*
+ * Return the length in bytes of the modified UTF-8 form of the string.
+ */
+static jsize GetStringUTFLength(JNIEnv* env, jstring string)
+{
+ JNI_ENTER();
+
+ jsize len = dvmStringUtf8ByteLen((StringObject*) string);
+
+ JNI_EXIT();
+ return len;
+}
+
+/*
+ * Convert "string" to modified UTF-8 and return a pointer. The returned
+ * value must be released with ReleaseStringUTFChars.
+ *
+ * According to the JNI reference, "Returns a pointer to a UTF-8 string,
+ * or NULL if the operation fails. Returns NULL if and only if an invocation
+ * of this function has thrown an exception."
+ *
+ * The behavior here currently follows that of other open-source VMs, which
+ * quietly return NULL if "string" is NULL. We should consider throwing an
+ * NPE. (The CheckJNI code blows up if you try to pass in a NULL string,
+ * which should catch this sort of thing during development.) Certain other
+ * VMs will crash with a segmentation fault.
+ */
+static const char* GetStringUTFChars(JNIEnv* env, jstring string,
+ jboolean* isCopy)
+{
+ JNI_ENTER();
+ char* newStr;
+
+ if (string == NULL) {
+ /* this shouldn't happen; throw NPE? */
+ newStr = NULL;
+ } else {
+ if (isCopy != NULL)
+ *isCopy = JNI_TRUE;
+
+ newStr = dvmCreateCstrFromString((StringObject*) string);
+ if (newStr == NULL) {
+ /* assume memory failure */
+ dvmThrowException("Ljava/lang/OutOfMemoryError;",
+ "native heap string alloc failed");
+ }
+ }
+
+ JNI_EXIT();
+ return newStr;
+}
+
+/*
+ * Release a string created by GetStringUTFChars().
+ */
+static void ReleaseStringUTFChars(JNIEnv* env, jstring string, const char* utf)
+{
+ JNI_ENTER();
+ free((char*)utf);
+ JNI_EXIT();
+}
+
+/*
+ * Return the capacity of the array.
+ */
+static jsize GetArrayLength(JNIEnv* env, jarray array)
+{
+ JNI_ENTER();
+
+ jsize length = ((ArrayObject*) array)->length;
+
+ JNI_EXIT();
+ return length;
+}
+
+/*
+ * Construct a new array that holds objects from class "elementClass".
+ */
+static jobjectArray NewObjectArray(JNIEnv* env, jsize length,
+ jclass elementClass, jobject initialElement)
+{
+ JNI_ENTER();
+
+ ClassObject* elemClassObj = (ClassObject*) elementClass;
+ ArrayObject* newObj = NULL;
+
+ if (elemClassObj == NULL) {
+ dvmThrowException("Ljava/lang/NullPointerException;",
+ "JNI NewObjectArray");
+ goto bail;
+ }
+
+ newObj = dvmAllocObjectArray(elemClassObj, length, ALLOC_DEFAULT);
+ if (newObj == NULL) {
+ assert(dvmCheckException(_self));
+ goto bail;
+ }
+ dvmReleaseTrackedAlloc((Object*) newObj, NULL);
+
+ /*
+ * Initialize the array. Trashes "length".
+ */
+ if (initialElement != NULL) {
+ Object** arrayData = (Object**) newObj->contents;
+
+ while (length--)
+ *arrayData++ = (Object*) initialElement;
+ }
+
+ newObj = addLocalReference((jobjectArray) newObj);
+
+bail:
+ JNI_EXIT();
+ return (jobjectArray) newObj;
+}
+
+/*
+ * Get one element of an Object array.
+ *
+ * Add the object to the local references table in case the array goes away.
+ */
+static jobject GetObjectArrayElement(JNIEnv* env, jobjectArray array,
+ jsize index)
+{
+ JNI_ENTER();
+
+ ArrayObject* arrayObj = (ArrayObject*) array;
+ Object* value = NULL;
+
+ assert(array != NULL);
+
+ /* check the array bounds */
+ if (index < 0 || index >= (int) arrayObj->length) {
+ dvmThrowException("Ljava/lang/ArrayIndexOutOfBoundsException;",
+ arrayObj->obj.clazz->descriptor);
+ goto bail;
+ }
+
+ value = ((Object**) arrayObj->contents)[index];
+ value = addLocalReference(value);
+
+bail:
+ JNI_EXIT();
+ return (jobject) value;
+}
+
+/*
+ * Set one element of an Object array.
+ */
+static void SetObjectArrayElement(JNIEnv* env, jobjectArray array,
+ jsize index, jobject value)
+{
+ JNI_ENTER();
+
+ ArrayObject* arrayObj = (ArrayObject*) array;
+
+ assert(array != NULL);
+
+ /* check the array bounds */
+ if (index < 0 || index >= (int) arrayObj->length) {
+ dvmThrowException("Ljava/lang/ArrayIndexOutOfBoundsException;",
+ arrayObj->obj.clazz->descriptor);
+ goto bail;
+ }
+
+ //LOGV("JNI: set element %d in array %p to %p\n", index, array, value);
+
+ ((Object**) arrayObj->contents)[index] = (Object*) value;
+
+bail:
+ JNI_EXIT();
+}
+
+/*
+ * Create a new array of primitive elements.
+ */
+#define NEW_PRIMITIVE_ARRAY(_artype, _jname, _typechar) \
+ static _artype New##_jname##Array(JNIEnv* env, jsize length) \
+ { \
+ JNI_ENTER(); \
+ ArrayObject* arrayObj; \
+ arrayObj = dvmAllocPrimitiveArray(_typechar, length, \
+ ALLOC_DEFAULT); \
+ if (arrayObj != NULL) { \
+ dvmReleaseTrackedAlloc((Object*) arrayObj, NULL); \
+ arrayObj = addLocalReference(arrayObj); \
+ } \
+ JNI_EXIT(); \
+ return (_artype)arrayObj; \
+ }
+NEW_PRIMITIVE_ARRAY(jbooleanArray, Boolean, 'Z');
+NEW_PRIMITIVE_ARRAY(jbyteArray, Byte, 'B');
+NEW_PRIMITIVE_ARRAY(jcharArray, Char, 'C');
+NEW_PRIMITIVE_ARRAY(jshortArray, Short, 'S');
+NEW_PRIMITIVE_ARRAY(jintArray, Int, 'I');
+NEW_PRIMITIVE_ARRAY(jlongArray, Long, 'J');
+NEW_PRIMITIVE_ARRAY(jfloatArray, Float, 'F');
+NEW_PRIMITIVE_ARRAY(jdoubleArray, Double, 'D');
+
+/*
+ * Get a pointer to a C array of primitive elements from an array object
+ * of the matching type.
+ *
+ * We guarantee availability until Release is called, so we have to add
+ * the array object to the global refs table.
+ *
+ * In a compacting GC, we either need to return a copy of the elements
+ * or "pin" the memory. Otherwise we run the risk of native code using
+ * the buffer as the destination of a blocking read() call that wakes up
+ * during a GC.
+ */
+#define GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname) \
+ static _ctype* Get##_jname##ArrayElements(JNIEnv* env, \
+ _ctype##Array array, jboolean* isCopy) \
+ { \
+ JNI_ENTER(); \
+ _ctype* data; \
+ ArrayObject* arrayObj = (ArrayObject*)array; \
+ addGlobalReference(arrayObj); \
+ data = (_ctype*) arrayObj->contents; \
+ if (isCopy != NULL) \
+ *isCopy = JNI_FALSE; \
+ JNI_EXIT(); \
+ return data; \
+ }
+
+/*
+ * Release the storage locked down by the "get" function.
+ *
+ * The API says, ""'mode' has no effect if 'elems' is not a copy of the
+ * elements in 'array'." They apparently did not anticipate the need to
+ * create a global reference to avoid GC race conditions. We actually
+ * want to delete the global reference in all circumstances that would
+ * result in a copied array being freed. This means anything but a
+ * JNI_COMMIT release.
+ */
+#define RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname) \
+ static void Release##_jname##ArrayElements(JNIEnv* env, \
+ _ctype##Array array, _ctype* elems, jint mode) \
+ { \
+ UNUSED_PARAMETER(elems); \
+ JNI_ENTER(); \
+ if (mode != JNI_COMMIT) \
+ deleteGlobalReference(array); \
+ JNI_EXIT(); \
+ }
+
+/*
+ * Copy a section of a primitive array to a buffer.
+ */
+#define GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname) \
+ static void Get##_jname##ArrayRegion(JNIEnv* env, \
+ _ctype##Array array, jsize start, jsize len, _ctype* buf) \
+ { \
+ JNI_ENTER(); \
+ ArrayObject* arrayObj = (ArrayObject*)array; \
+ _ctype* data = (_ctype*) arrayObj->contents; \
+ if (start < 0 || len < 0 || start + len > (int) arrayObj->length) { \
+ dvmThrowException("Ljava/lang/ArrayIndexOutOfBoundsException;", \
+ arrayObj->obj.clazz->descriptor); \
+ } else { \
+ memcpy(buf, data + start, len * sizeof(_ctype)); \
+ } \
+ JNI_EXIT(); \
+ }
+
+/*
+ * Copy a section of a primitive array to a buffer.
+ */
+#define SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname) \
+ static void Set##_jname##ArrayRegion(JNIEnv* env, \
+ _ctype##Array array, jsize start, jsize len, const _ctype* buf) \
+ { \
+ JNI_ENTER(); \
+ ArrayObject* arrayObj = (ArrayObject*)array; \
+ _ctype* data = (_ctype*) arrayObj->contents; \
+ if (start < 0 || len < 0 || start + len > (int) arrayObj->length) { \
+ dvmThrowException("Ljava/lang/ArrayIndexOutOfBoundsException;", \
+ arrayObj->obj.clazz->descriptor); \
+ } else { \
+ memcpy(data + start, buf, len * sizeof(_ctype)); \
+ } \
+ JNI_EXIT(); \
+ }
+
+/*
+ * 4-in-1:
+ * Get<Type>ArrayElements
+ * Release<Type>ArrayElements
+ * Get<Type>ArrayRegion
+ * Set<Type>ArrayRegion
+ */
+#define PRIMITIVE_ARRAY_FUNCTIONS(_ctype, _jname) \
+ GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname); \
+ RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname); \
+ GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname); \
+ SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname);
+
+PRIMITIVE_ARRAY_FUNCTIONS(jboolean, Boolean);
+PRIMITIVE_ARRAY_FUNCTIONS(jbyte, Byte);
+PRIMITIVE_ARRAY_FUNCTIONS(jchar, Char);
+PRIMITIVE_ARRAY_FUNCTIONS(jshort, Short);
+PRIMITIVE_ARRAY_FUNCTIONS(jint, Int);
+PRIMITIVE_ARRAY_FUNCTIONS(jlong, Long);
+PRIMITIVE_ARRAY_FUNCTIONS(jfloat, Float);
+PRIMITIVE_ARRAY_FUNCTIONS(jdouble, Double);
+
+/*
+ * Register one or more native functions in one class.
+ */
+static jint RegisterNatives(JNIEnv* env, jclass clazz,
+ const JNINativeMethod* methods, jint nMethods)
+{
+ JNI_ENTER();
+
+ jint retval;
+ int i;
+
+ if (gDvm.verboseJni) {
+ LOGI("[Registering JNI native methods for class %s]\n",
+ ((ClassObject*) clazz)->descriptor);
+ }
+
+ for (i = 0; i < nMethods; i++) {
+ if (!dvmRegisterJNIMethod((ClassObject*) clazz,
+ methods[i].name, methods[i].signature, methods[i].fnPtr))
+ {
+ retval = JNI_ERR;
+ goto bail;
+ }
+ }
+ retval = JNI_OK;
+
+bail:
+ JNI_EXIT();
+ return retval;
+}
+
+/*
+ * Un-register a native function.
+ */
+static jint UnregisterNatives(JNIEnv* env, jclass clazz)
+{
+ JNI_ENTER();
+ /*
+ * The JNI docs refer to this as a way to reload/relink native libraries,
+ * and say it "should not be used in normal native code".
+ *
+ * We can implement it if we decide we need it.
+ */
+ JNI_EXIT();
+ return JNI_ERR;
+}
+
+/*
+ * Lock the monitor.
+ *
+ * We have to track all monitor enters and exits, so that we can undo any
+ * outstanding synchronization before the thread exits.
+ */
+static jint MonitorEnter(JNIEnv* env, jobject obj)
+{
+ JNI_ENTER();
+ dvmLockObject(_self, (Object*) obj);
+ trackMonitorEnter(_self, (Object*) obj);
+ JNI_EXIT();
+ return JNI_OK;
+}
+
+/*
+ * Unlock the monitor.
+ *
+ * Throws an IllegalMonitorStateException if the current thread
+ * doesn't own the monitor. (dvmUnlockObject() takes care of the throw.)
+ *
+ * According to the 1.6 spec, it's legal to call here with an exception
+ * pending. If this fails, we'll stomp the original exception.
+ */
+static jint MonitorExit(JNIEnv* env, jobject obj)
+{
+ JNI_ENTER();
+ bool success = dvmUnlockObject(_self, (Object*) obj);
+ if (success)
+ trackMonitorExit(_self, (Object*) obj);
+ JNI_EXIT();
+ return success ? JNI_OK : JNI_ERR;
+}
+
+/*
+ * Return the JavaVM interface associated with the current thread.
+ */
+static jint GetJavaVM(JNIEnv* env, JavaVM** vm)
+{
+ JNI_ENTER();
+ //*vm = gDvm.vmList;
+ *vm = (JavaVM*) ((JNIEnvExt*)env)->vm;
+ JNI_EXIT();
+ if (*vm == NULL)
+ return JNI_ERR;
+ else
+ return JNI_OK;
+}
+
+/*
+ * Copies "len" Unicode characters, from offset "start".
+ */
+static void GetStringRegion(JNIEnv* env, jstring str, jsize start, jsize len,
+ jchar* buf)
+{
+ JNI_ENTER();
+ StringObject* strObj = (StringObject*) str;
+ if (start + len > dvmStringLen(strObj))
+ dvmThrowException("Ljava/lang/StringIndexOutOfBoundsException;", NULL);
+ else
+ memcpy(buf, dvmStringChars(strObj) + start, len * sizeof(u2));
+ JNI_EXIT();
+}
+
+/*
+ * Translates "len" Unicode characters, from offset "start", into
+ * modified UTF-8 encoding.
+ */
+static void GetStringUTFRegion(JNIEnv* env, jstring str, jsize start,
+ jsize len, char* buf)
+{
+ JNI_ENTER();
+ StringObject* strObj = (StringObject*) str;
+ if (start + len > dvmStringLen(strObj))
+ dvmThrowException("Ljava/lang/StringIndexOutOfBoundsException;", NULL);
+ else
+ dvmCreateCstrFromStringRegion(strObj, start, len, buf);
+ JNI_EXIT();
+}
+
+/*
+ * Get a raw pointer to array data.
+ *
+ * The caller is expected to call "release" before doing any JNI calls
+ * or blocking I/O operations.
+ *
+ * In a compacting GC, we need to pin the memory or block GC.
+ */
+static void* GetPrimitiveArrayCritical(JNIEnv* env, jarray array,
+ jboolean* isCopy)
+{
+ JNI_ENTER();
+ void* data;
+ ArrayObject* arrayObj = (ArrayObject*)array;
+ addGlobalReference(arrayObj);
+ data = arrayObj->contents;
+ if (isCopy != NULL)
+ *isCopy = JNI_FALSE;
+ JNI_EXIT();
+ return data;
+}
+
+/*
+ * Release an array obtained with GetPrimitiveArrayCritical.
+ */
+static void ReleasePrimitiveArrayCritical(JNIEnv* env, jarray array,
+ void* carray, jint mode)
+{
+ JNI_ENTER();
+ if (mode != JNI_COMMIT)
+ deleteGlobalReference(array);
+ JNI_EXIT();
+}
+
+/*
+ * Like GetStringChars, but with restricted use.
+ */
+static const jchar* GetStringCritical(JNIEnv* env, jstring string,
+ jboolean* isCopy)
+{
+ JNI_ENTER();
+ const u2* data = dvmStringChars((StringObject*) string);
+ addGlobalReference(string);
+
+ if (isCopy != NULL)
+ *isCopy = JNI_FALSE;
+
+ JNI_EXIT();
+ return (jchar*)data;
+}
+
+/*
+ * Like ReleaseStringChars, but with restricted use.
+ */
+static void ReleaseStringCritical(JNIEnv* env, jstring string,
+ const jchar* carray)
+{
+ JNI_ENTER();
+ deleteGlobalReference(string);
+ JNI_EXIT();
+}
+
+/*
+ * Create a new weak global reference.
+ */
+static jweak NewWeakGlobalRef(JNIEnv* env, jobject obj)
+{
+ JNI_ENTER();
+ // TODO - implement
+ jobject gref = NULL;
+ LOGE("JNI ERROR: NewWeakGlobalRef not implemented\n");
+ dvmAbort();
+ JNI_EXIT();
+ return gref;
+}
+
+/*
+ * Delete the specified weak global reference.
+ */
+static void DeleteWeakGlobalRef(JNIEnv* env, jweak obj)
+{
+ JNI_ENTER();
+ // TODO - implement
+ LOGE("JNI ERROR: DeleteWeakGlobalRef not implemented\n");
+ dvmAbort();
+ JNI_EXIT();
+}
+
+/*
+ * Quick check for pending exceptions.
+ *
+ * TODO: we should be able to skip the enter/exit macros here.
+ */
+static jboolean ExceptionCheck(JNIEnv* env)
+{
+ JNI_ENTER();
+ bool result = dvmCheckException(_self);
+ JNI_EXIT();
+ return result;
+}
+
+/*
+ * Returns the type of the object referred to by "obj". It can be local,
+ * global, or weak global.
+ *
+ * In the current implementation, references can be global and local at
+ * the same time, so while the return value is accurate it may not tell
+ * the whole story.
+ */
+static jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj)
+{
+ JNI_ENTER();
+ jobjectRefType type;
+
+ if (obj == NULL)
+ type = JNIInvalidRefType;
+ else
+ type = dvmGetJNIRefType(obj);
+ JNI_EXIT();
+ return type;
+}
+
+/*
+ * Allocate and return a new java.nio.ByteBuffer for this block of memory.
+ *
+ * ** IMPORTANT ** This function is not considered to be internal to the
+ * VM. It may make JNI calls but must not examine or update internal VM
+ * state. It is not protected by JNI_ENTER/JNI_EXIT.
+ *
+ * "address" may not be NULL. We only test for that when JNI checks are
+ * enabled.
+ *
+ * copied from harmony: DirectBufferUtil.c
+ */
+static jobject NewDirectByteBuffer(JNIEnv * env, void* address, jlong capacity)
+{
+ jmethodID newBufferMethod;
+ jclass directBufferClass;
+ jclass platformaddressClass;
+ jobject platformaddress;
+ jmethodID onMethod;
+
+ directBufferClass = (*env)->FindClass(env,
+ "java/nio/ReadWriteDirectByteBuffer");
+
+ if(!directBufferClass)
+ {
+ return NULL;
+ }
+
+ newBufferMethod = (*env)->GetMethodID(env, directBufferClass, "<init>",
+ "(Lorg/apache/harmony/luni/platform/PlatformAddress;II)V");
+ if(!newBufferMethod)
+ {
+ return NULL;
+ }
+
+ platformaddressClass = (*env)->FindClass(env,
+ "org/apache/harmony/luni/platform/PlatformAddressFactory");
+ if(!platformaddressClass)
+ {
+ return NULL;
+ }
+
+ onMethod = (*env)->GetStaticMethodID(env, platformaddressClass, "on",
+ "(I)Lorg/apache/harmony/luni/platform/PlatformAddress;");
+ if(!onMethod)
+ {
+ return NULL;
+ }
+
+ platformaddress = (*env)->CallStaticObjectMethod(env, platformaddressClass,
+ onMethod, (jint)address);
+
+ return (*env)->NewObject(env, directBufferClass, newBufferMethod,
+ platformaddress, (jint)capacity, (jint)0);
+}
+
+/*
+ * Get the starting address of the buffer for the specified java.nio.Buffer.
+ *
+ * ** IMPORTANT ** This function is not considered to be internal to the
+ * VM. It may make JNI calls but must not examine or update internal VM
+ * state. It is not protected by JNI_ENTER/JNI_EXIT.
+ *
+ * copied from harmony: DirectBufferUtil.c
+ */
+static void* GetDirectBufferAddress(JNIEnv * env, jobject buf)
+{
+ jmethodID tempMethod;
+ jclass tempClass;
+ jobject platformAddr;
+ jclass platformAddrClass;
+ jmethodID toLongMethod;
+
+ tempClass = (*env)->FindClass(env,
+ "org/apache/harmony/nio/internal/DirectBuffer");
+ if(!tempClass)
+ {
+ return 0;
+ }
+
+ if(JNI_FALSE == (*env)->IsInstanceOf(env, buf, tempClass))
+ {
+ return 0;
+ }
+
+ tempMethod = (*env)->GetMethodID(env, tempClass, "getBaseAddress",
+ "()Lorg/apache/harmony/luni/platform/PlatformAddress;");
+ if(!tempMethod){
+ return 0;
+ }
+ platformAddr = (*env)->CallObjectMethod(env, buf, tempMethod);
+ platformAddrClass = (*env)->FindClass (env,
+ "org/apache/harmony/luni/platform/PlatformAddress");
+ if(!platformAddrClass)
+ {
+ return 0;
+
+ }
+ toLongMethod = (*env)->GetMethodID(env, platformAddrClass, "toLong", "()J");
+ if (!toLongMethod)
+ {
+ return 0;
+ }
+
+ return (void*)(u4)(*env)->CallLongMethod(env, platformAddr, toLongMethod);
+}
+
+/*
+ * Get the capacity of the buffer for the specified java.nio.Buffer.
+ *
+ * ** IMPORTANT ** This function is not considered to be internal to the
+ * VM. It may make JNI calls but must not examine or update internal VM
+ * state. It is not protected by JNI_ENTER/JNI_EXIT.
+ *
+ * copied from harmony: DirectBufferUtil.c
+ */
+static jlong GetDirectBufferCapacity(JNIEnv * env, jobject buf)
+{
+ jfieldID fieldCapacity;
+ jclass directBufferClass;
+ jclass bufferClass;
+
+ directBufferClass = (*env)->FindClass(env,
+ "org/apache/harmony/nio/internal/DirectBuffer");
+ if (!directBufferClass)
+ {
+ return -1;
+ }
+
+ if (JNI_FALSE == (*env)->IsInstanceOf(env, buf, directBufferClass))
+ {
+ return -1;
+ }
+
+ bufferClass = (*env)->FindClass(env, "java/nio/Buffer");
+ if (!bufferClass)
+ {
+ return -1;
+ }
+
+ fieldCapacity = (*env)->GetFieldID(env, bufferClass, "capacity", "I");
+ if (!fieldCapacity)
+ {
+ return -1;
+ }
+
+ return (*env)->GetIntField(env, buf, fieldCapacity);
+}
+
+
+/*
+ * ===========================================================================
+ * JNI invocation functions
+ * ===========================================================================
+ */
+
+/*
+ * Handle AttachCurrentThread{AsDaemon}.
+ *
+ * We need to make sure the VM is actually running. For example, if we start
+ * up, issue an Attach, and the VM exits almost immediately, by the time the
+ * attaching happens the VM could already be shutting down.
+ *
+ * It's hard to avoid a race condition here because we don't want to hold
+ * a lock across the entire operation. What we can do is temporarily
+ * increment the thread count to prevent a VM exit.
+ *
+ * This could potentially still have problems if a daemon thread calls here
+ * while the VM is shutting down. dvmThreadSelf() will work, since it just
+ * uses pthread TLS, but dereferencing "vm" could fail. Such is life when
+ * you shut down a VM while threads are still running inside it.
+ *
+ * Remember that some code may call this as a way to find the per-thread
+ * JNIEnv pointer. Don't do excess work for that case.
+ */
+static jint attachThread(JavaVM* vm, JNIEnv** p_env, void* thr_args,
+ bool isDaemon)
+{
+ JavaVMAttachArgs* args = (JavaVMAttachArgs*) thr_args;
+ Thread* self;
+ bool result = false;
+
+ /*
+ * Return immediately if we're already one with the VM.
+ */
+ self = dvmThreadSelf();
+ if (self != NULL) {
+ *p_env = self->jniEnv;
+ return JNI_OK;
+ }
+
+ /*
+ * No threads allowed in zygote mode.
+ */
+ if (gDvm.zygote) {
+ return JNI_ERR;
+ }
+
+ /* increment the count to keep the VM from bailing while we run */
+ dvmLockThreadList(NULL);
+ if (gDvm.nonDaemonThreadCount == 0) {
+ // dead or dying
+ LOGV("Refusing to attach thread '%s' -- VM is shutting down\n",
+ (thr_args == NULL) ? "(unknown)" : args->name);
+ dvmUnlockThreadList();
+ return JNI_ERR;
+ }
+ gDvm.nonDaemonThreadCount++;
+ dvmUnlockThreadList();
+
+ /* tweak the JavaVMAttachArgs as needed */
+ JavaVMAttachArgs argsCopy;
+ if (args == NULL) {
+ /* allow the v1.1 calling convention */
+ argsCopy.version = JNI_VERSION_1_2;
+ argsCopy.name = NULL;
+ argsCopy.group = dvmGetMainThreadGroup();
+ } else {
+ assert(args->version >= JNI_VERSION_1_2);
+
+ argsCopy.version = args->version;
+ argsCopy.name = args->name;
+ if (args->group != NULL)
+ argsCopy.group = args->group;
+ else
+ argsCopy.group = dvmGetMainThreadGroup();
+ }
+
+ result = dvmAttachCurrentThread(&argsCopy, isDaemon);
+
+ /* restore the count */
+ dvmLockThreadList(NULL);
+ gDvm.nonDaemonThreadCount--;
+ dvmUnlockThreadList();
+
+ /*
+ * Change the status to indicate that we're out in native code. This
+ * call is not guarded with state-change macros, so we have to do it
+ * by hand.
+ */
+ if (result) {
+ self = dvmThreadSelf();
+ assert(self != NULL);
+ dvmChangeStatus(self, THREAD_NATIVE);
+ *p_env = self->jniEnv;
+ return JNI_OK;
+ } else {
+ return JNI_ERR;
+ }
+}
+
+/*
+ * Attach the current thread to the VM. If the thread is already attached,
+ * this is a no-op.
+ */
+static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args)
+{
+ return attachThread(vm, p_env, thr_args, false);
+}
+
+/*
+ * Like AttachCurrentThread, but set the "daemon" flag.
+ */
+static jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env,
+ void* thr_args)
+{
+ return attachThread(vm, p_env, thr_args, true);
+}
+
+/*
+ * Dissociate the current thread from the VM.
+ */
+static jint DetachCurrentThread(JavaVM* vm)
+{
+ Thread* self = dvmThreadSelf();
+
+ if (self == NULL) /* not attached, can't do anything */
+ return JNI_ERR;
+
+ /* switch to "running" to check for suspension */
+ dvmChangeStatus(self, THREAD_RUNNING);
+
+ /* detach the thread */
+ dvmDetachCurrentThread();
+
+ /* (no need to change status back -- we have no status) */
+ return JNI_OK;
+}
+
+/*
+ * If current thread is attached to VM, return the associated JNIEnv.
+ * Otherwise, stuff NULL in and return JNI_EDETACHED.
+ *
+ * JVMTI overloads this by specifying a magic value for "version", so we
+ * do want to check that here.
+ */
+static jint GetEnv(JavaVM* vm, void** env, jint version)
+{
+ Thread* self = dvmThreadSelf();
+
+ if (version < JNI_VERSION_1_1 || version > JNI_VERSION_1_6)
+ return JNI_EVERSION;
+
+ if (self == NULL) {
+ *env = NULL;
+ } else {
+ /* TODO: status change is probably unnecessary */
+ dvmChangeStatus(self, THREAD_RUNNING);
+ *env = (void*) dvmGetThreadJNIEnv(self);
+ dvmChangeStatus(self, THREAD_NATIVE);
+ }
+ if (*env == NULL)
+ return JNI_EDETACHED;
+ else
+ return JNI_OK;
+}
+
+/*
+ * Destroy the VM. This may be called from any thread.
+ *
+ * If the current thread is attached, wait until the current thread is
+ * the only non-daemon user-level thread. If the current thread is not
+ * attached, we attach it and do the processing as usual. (If the attach
+ * fails, it's probably because all the non-daemon threads have already
+ * exited and the VM doesn't want to let us back in.)
+ *
+ * TODO: we don't really deal with the situation where more than one thread
+ * has called here. One thread wins, the other stays trapped waiting on
+ * the condition variable forever. Not sure this situation is interesting
+ * in real life.
+ */
+static jint DestroyJavaVM(JavaVM* vm)
+{
+ JavaVMExt* ext = (JavaVMExt*) vm;
+ Thread* self;
+
+ if (ext == NULL)
+ return JNI_ERR;
+
+ LOGD("DestroyJavaVM waiting for non-daemon threads to exit\n");
+
+ /*
+ * Sleep on a condition variable until it's okay to exit.
+ */
+ self = dvmThreadSelf();
+ if (self == NULL) {
+ JNIEnv* tmpEnv;
+ if (AttachCurrentThread(vm, &tmpEnv, NULL) != JNI_OK) {
+ LOGV("Unable to reattach main for Destroy; assuming VM is "
+ "shutting down (count=%d)\n",
+ gDvm.nonDaemonThreadCount);
+ goto shutdown;
+ } else {
+ LOGV("Attached to wait for shutdown in Destroy\n");
+ }
+ }
+ dvmChangeStatus(self, THREAD_VMWAIT);
+
+ dvmLockThreadList(self);
+ gDvm.nonDaemonThreadCount--; // remove current thread from count
+
+ while (gDvm.nonDaemonThreadCount > 0)
+ pthread_cond_wait(&gDvm.vmExitCond, &gDvm.threadListLock);
+
+ dvmUnlockThreadList();
+ self = NULL;
+
+shutdown:
+ // TODO: call System.exit() to run any registered shutdown hooks
+ // (this may not return -- figure out how this should work)
+
+ LOGD("DestroyJavaVM shutting VM down\n");
+ dvmShutdown();
+
+ // TODO - free resources associated with JNI-attached daemon threads
+ free(ext->envList);
+ free(ext);
+
+ return JNI_OK;
+}
+
+
+/*
+ * ===========================================================================
+ * Function tables
+ * ===========================================================================
+ */
+
+static const struct JNINativeInterface gNativeInterface = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+
+ GetVersion,
+
+ DefineClass,
+ FindClass,
+
+ FromReflectedMethod,
+ FromReflectedField,
+ ToReflectedMethod,
+
+ GetSuperclass,
+ IsAssignableFrom,
+
+ ToReflectedField,
+
+ Throw,
+ ThrowNew,
+ ExceptionOccurred,
+ ExceptionDescribe,
+ ExceptionClear,
+ FatalError,
+
+ PushLocalFrame,
+ PopLocalFrame,
+
+ NewGlobalRef,
+ DeleteGlobalRef,
+ DeleteLocalRef,
+ IsSameObject,
+ NewLocalRef,
+ EnsureLocalCapacity,
+
+ AllocObject,
+ NewObject,
+ NewObjectV,
+ NewObjectA,
+
+ GetObjectClass,
+ IsInstanceOf,
+
+ GetMethodID,
+
+ CallObjectMethod,
+ CallObjectMethodV,
+ CallObjectMethodA,
+ CallBooleanMethod,
+ CallBooleanMethodV,
+ CallBooleanMethodA,
+ CallByteMethod,
+ CallByteMethodV,
+ CallByteMethodA,
+ CallCharMethod,
+ CallCharMethodV,
+ CallCharMethodA,
+ CallShortMethod,
+ CallShortMethodV,
+ CallShortMethodA,
+ CallIntMethod,
+ CallIntMethodV,
+ CallIntMethodA,
+ CallLongMethod,
+ CallLongMethodV,
+ CallLongMethodA,
+ CallFloatMethod,
+ CallFloatMethodV,
+ CallFloatMethodA,
+ CallDoubleMethod,
+ CallDoubleMethodV,
+ CallDoubleMethodA,
+ CallVoidMethod,
+ CallVoidMethodV,
+ CallVoidMethodA,
+
+ CallNonvirtualObjectMethod,
+ CallNonvirtualObjectMethodV,
+ CallNonvirtualObjectMethodA,
+ CallNonvirtualBooleanMethod,
+ CallNonvirtualBooleanMethodV,
+ CallNonvirtualBooleanMethodA,
+ CallNonvirtualByteMethod,
+ CallNonvirtualByteMethodV,
+ CallNonvirtualByteMethodA,
+ CallNonvirtualCharMethod,
+ CallNonvirtualCharMethodV,
+ CallNonvirtualCharMethodA,
+ CallNonvirtualShortMethod,
+ CallNonvirtualShortMethodV,
+ CallNonvirtualShortMethodA,
+ CallNonvirtualIntMethod,
+ CallNonvirtualIntMethodV,
+ CallNonvirtualIntMethodA,
+ CallNonvirtualLongMethod,
+ CallNonvirtualLongMethodV,
+ CallNonvirtualLongMethodA,
+ CallNonvirtualFloatMethod,
+ CallNonvirtualFloatMethodV,
+ CallNonvirtualFloatMethodA,
+ CallNonvirtualDoubleMethod,
+ CallNonvirtualDoubleMethodV,
+ CallNonvirtualDoubleMethodA,
+ CallNonvirtualVoidMethod,
+ CallNonvirtualVoidMethodV,
+ CallNonvirtualVoidMethodA,
+
+ GetFieldID,
+
+ GetObjectField,
+ GetBooleanField,
+ GetByteField,
+ GetCharField,
+ GetShortField,
+ GetIntField,
+ GetLongField,
+ GetFloatField,
+ GetDoubleField,
+ SetObjectField,
+ SetBooleanField,
+ SetByteField,
+ SetCharField,
+ SetShortField,
+ SetIntField,
+ SetLongField,
+ SetFloatField,
+ SetDoubleField,
+
+ GetStaticMethodID,
+
+ CallStaticObjectMethod,
+ CallStaticObjectMethodV,
+ CallStaticObjectMethodA,
+ CallStaticBooleanMethod,
+ CallStaticBooleanMethodV,
+ CallStaticBooleanMethodA,
+ CallStaticByteMethod,
+ CallStaticByteMethodV,
+ CallStaticByteMethodA,
+ CallStaticCharMethod,
+ CallStaticCharMethodV,
+ CallStaticCharMethodA,
+ CallStaticShortMethod,
+ CallStaticShortMethodV,
+ CallStaticShortMethodA,
+ CallStaticIntMethod,
+ CallStaticIntMethodV,
+ CallStaticIntMethodA,
+ CallStaticLongMethod,
+ CallStaticLongMethodV,
+ CallStaticLongMethodA,
+ CallStaticFloatMethod,
+ CallStaticFloatMethodV,
+ CallStaticFloatMethodA,
+ CallStaticDoubleMethod,
+ CallStaticDoubleMethodV,
+ CallStaticDoubleMethodA,
+ CallStaticVoidMethod,
+ CallStaticVoidMethodV,
+ CallStaticVoidMethodA,
+
+ GetStaticFieldID,
+
+ GetStaticObjectField,
+ GetStaticBooleanField,
+ GetStaticByteField,
+ GetStaticCharField,
+ GetStaticShortField,
+ GetStaticIntField,
+ GetStaticLongField,
+ GetStaticFloatField,
+ GetStaticDoubleField,
+
+ SetStaticObjectField,
+ SetStaticBooleanField,
+ SetStaticByteField,
+ SetStaticCharField,
+ SetStaticShortField,
+ SetStaticIntField,
+ SetStaticLongField,
+ SetStaticFloatField,
+ SetStaticDoubleField,
+
+ NewString,
+
+ GetStringLength,
+ GetStringChars,
+ ReleaseStringChars,
+
+ NewStringUTF,
+ GetStringUTFLength,
+ GetStringUTFChars,
+ ReleaseStringUTFChars,
+
+ GetArrayLength,
+ NewObjectArray,
+ GetObjectArrayElement,
+ SetObjectArrayElement,
+
+ NewBooleanArray,
+ NewByteArray,
+ NewCharArray,
+ NewShortArray,
+ NewIntArray,
+ NewLongArray,
+ NewFloatArray,
+ NewDoubleArray,
+
+ GetBooleanArrayElements,
+ GetByteArrayElements,
+ GetCharArrayElements,
+ GetShortArrayElements,
+ GetIntArrayElements,
+ GetLongArrayElements,
+ GetFloatArrayElements,
+ GetDoubleArrayElements,
+
+ ReleaseBooleanArrayElements,
+ ReleaseByteArrayElements,
+ ReleaseCharArrayElements,
+ ReleaseShortArrayElements,
+ ReleaseIntArrayElements,
+ ReleaseLongArrayElements,
+ ReleaseFloatArrayElements,
+ ReleaseDoubleArrayElements,
+
+ GetBooleanArrayRegion,
+ GetByteArrayRegion,
+ GetCharArrayRegion,
+ GetShortArrayRegion,
+ GetIntArrayRegion,
+ GetLongArrayRegion,
+ GetFloatArrayRegion,
+ GetDoubleArrayRegion,
+ SetBooleanArrayRegion,
+ SetByteArrayRegion,
+ SetCharArrayRegion,
+ SetShortArrayRegion,
+ SetIntArrayRegion,
+ SetLongArrayRegion,
+ SetFloatArrayRegion,
+ SetDoubleArrayRegion,
+
+ RegisterNatives,
+ UnregisterNatives,
+
+ MonitorEnter,
+ MonitorExit,
+
+ GetJavaVM,
+
+ GetStringRegion,
+ GetStringUTFRegion,
+
+ GetPrimitiveArrayCritical,
+ ReleasePrimitiveArrayCritical,
+
+ GetStringCritical,
+ ReleaseStringCritical,
+
+ NewWeakGlobalRef,
+ DeleteWeakGlobalRef,
+
+ ExceptionCheck,
+
+ NewDirectByteBuffer,
+ GetDirectBufferAddress,
+ GetDirectBufferCapacity,
+
+ GetObjectRefType
+};
+static const struct JNIInvokeInterface gInvokeInterface = {
+ NULL,
+ NULL,
+ NULL,
+
+ DestroyJavaVM,
+ AttachCurrentThread,
+ DetachCurrentThread,
+
+ GetEnv,
+
+ AttachCurrentThreadAsDaemon,
+};
+
+
+/*
+ * ===========================================================================
+ * VM/Env creation
+ * ===========================================================================
+ */
+
+/*
+ * Enable "checked JNI" after the VM has partially started. This must
+ * only be called in "zygote" mode, when we have one thread running.
+ */
+void dvmLateEnableCheckedJni(void)
+{
+ JNIEnvExt* extEnv;
+ JavaVMExt* extVm;
+
+ extEnv = dvmGetJNIEnvForThread();
+ if (extEnv == NULL) {
+ LOGE("dvmLateEnableCheckedJni: thread has no JNIEnv\n");
+ return;
+ }
+ extVm = extEnv->vm;
+ assert(extVm != NULL);
+
+ if (!extVm->useChecked) {
+ LOGD("Late-enabling CheckJNI\n");
+ dvmUseCheckedJniVm(extVm);
+ extVm->useChecked = true;
+ dvmUseCheckedJniEnv(extEnv);
+
+ /* currently no way to pick up jniopts features */
+ } else {
+ LOGD("Not late-enabling CheckJNI (already on)\n");
+ }
+}
+
+/*
+ * Not supported.
+ */
+jint JNI_GetDefaultJavaVMInitArgs(void* vm_args)
+{
+ return JNI_ERR;
+}
+
+/*
+ * Return a buffer full of created VMs.
+ *
+ * We always have zero or one.
+ */
+jint JNI_GetCreatedJavaVMs(JavaVM** vmBuf, jsize bufLen, jsize* nVMs)
+{
+ if (gDvm.vmList != NULL) {
+ *nVMs = 1;
+
+ if (bufLen > 0)
+ *vmBuf++ = gDvm.vmList;
+ } else {
+ *nVMs = 0;
+ }
+
+ return JNI_OK;
+}
+
+
+/*
+ * Create a new VM instance.
+ *
+ * The current thread becomes the main VM thread. We return immediately,
+ * which effectively means the caller is executing in a native method.
+ */
+jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args)
+{
+ const JavaVMInitArgs* args = (JavaVMInitArgs*) vm_args;
+ JNIEnvExt* pEnv = NULL;
+ JavaVMExt* pVM = NULL;
+ const char** argv;
+ int argc = 0;
+ int i, curOpt;
+ int result = JNI_ERR;
+ bool checkJni = false;
+ bool warnError = true;
+ bool forceDataCopy = false;
+
+ if (args->version < JNI_VERSION_1_2)
+ return JNI_EVERSION;
+
+ // TODO: don't allow creation of multiple VMs -- one per customer for now
+
+ /* zero globals; not strictly necessary the first time a VM is started */
+ memset(&gDvm, 0, sizeof(gDvm));
+
+ /*
+ * Set up structures for JNIEnv and VM.
+ */
+ //pEnv = (JNIEnvExt*) malloc(sizeof(JNIEnvExt));
+ pVM = (JavaVMExt*) malloc(sizeof(JavaVMExt));
+
+ //memset(pEnv, 0, sizeof(JNIEnvExt));
+ //pEnv->funcTable = &gNativeInterface;
+ //pEnv->vm = pVM;
+ memset(pVM, 0, sizeof(JavaVMExt));
+ pVM->funcTable = &gInvokeInterface;
+ pVM->envList = pEnv;
+ dvmInitMutex(&pVM->envListLock);
+
+ argv = (const char**) malloc(sizeof(char*) * (args->nOptions));
+ memset(argv, 0, sizeof(char*) * (args->nOptions));
+
+ curOpt = 0;
+
+ /*
+ * Convert JNI args to argv.
+ *
+ * We have to pull out vfprintf/exit/abort, because they use the
+ * "extraInfo" field to pass function pointer "hooks" in. We also
+ * look for the -Xcheck:jni stuff here.
+ */
+ for (i = 0; i < args->nOptions; i++) {
+ const char* optStr = args->options[i].optionString;
+
+ if (optStr == NULL) {
+ fprintf(stderr, "ERROR: arg %d string was null\n", i);
+ goto bail;
+ } else if (strcmp(optStr, "vfprintf") == 0) {
+ gDvm.vfprintfHook = args->options[i].extraInfo;
+ } else if (strcmp(optStr, "exit") == 0) {
+ gDvm.exitHook = args->options[i].extraInfo;
+ } else if (strcmp(optStr, "abort") == 0) {
+ gDvm.abortHook = args->options[i].extraInfo;
+ } else if (strcmp(optStr, "-Xcheck:jni") == 0) {
+ checkJni = true;
+ } else if (strncmp(optStr, "-Xjniopts:", 10) == 0) {
+ const char* jniOpts = optStr + 9;
+ while (jniOpts != NULL) {
+ jniOpts++; /* skip past ':' or ',' */
+ if (strncmp(jniOpts, "warnonly", 8) == 0) {
+ warnError = false;
+ } else if (strncmp(jniOpts, "forcecopy", 9) == 0) {
+ forceDataCopy = true;
+ } else {
+ LOGW("unknown jni opt starting at '%s'\n", jniOpts);
+ }
+ jniOpts = strchr(jniOpts, ',');
+ }
+ } else {
+ /* regular option */
+ argv[curOpt++] = optStr;
+ }
+ }
+ argc = curOpt;
+
+ if (checkJni) {
+ dvmUseCheckedJniVm(pVM);
+ pVM->useChecked = true;
+ }
+ pVM->warnError = warnError;
+ pVM->forceDataCopy = forceDataCopy;
+
+ /* set this up before initializing VM, so it can create some JNIEnvs */
+ gDvm.vmList = (JavaVM*) pVM;
+
+ /*
+ * Create an env for main thread. We need to have something set up
+ * here because some of the class initialization we do when starting
+ * up the VM will call into native code.
+ */
+ pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);
+
+ /* initialize VM */
+ gDvm.initializing = true;
+ if (dvmStartup(argc, argv, args->ignoreUnrecognized, (JNIEnv*)pEnv) != 0) {
+ free(pEnv);
+ free(pVM);
+ goto bail;
+ }
+
+ /*
+ * Success! Return stuff to caller.
+ */
+ dvmChangeStatus(NULL, THREAD_NATIVE);
+ *p_env = (JNIEnv*) pEnv;
+ *p_vm = (JavaVM*) pVM;
+ result = JNI_OK;
+
+bail:
+ gDvm.initializing = false;
+ if (result == JNI_OK)
+ LOGV("JNI_CreateJavaVM succeeded\n");
+ else
+ LOGW("JNI_CreateJavaVM failed\n");
+ free(argv);
+ return result;
+}
+