summaryrefslogtreecommitdiff
path: root/vm/Jni.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'vm/Jni.cpp')
-rw-r--r--vm/Jni.cpp3512
1 files changed, 3512 insertions, 0 deletions
diff --git a/vm/Jni.cpp b/vm/Jni.cpp
new file mode 100644
index 0000000..fac6696
--- /dev/null
+++ b/vm/Jni.cpp
@@ -0,0 +1,3512 @@
+/*
+ * 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 "ScopedPthreadMutexLock.h"
+#include "UniquePtr.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.
+
+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 GC
+needs to know about. These can be local, in which case they're released
+when the native method returns into the VM, or global, which are held
+until explicitly released. (There are also weak-global references,
+which have the lifespan and visibility of global references, but the
+object they refer to may be collected.)
+
+The references can be created with explicit JNI NewLocalRef / NewGlobalRef
+calls. The former is very unusual, the latter is reasonably common
+(e.g. for caching references to class objects).
+
+Local references are most often created as a side-effect of JNI functions.
+For example, the AllocObject/NewObject functions must create local
+references to the objects returned, 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.
+
+If we just want to maintain a list for the GC root set, we can use 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).
+
+If we want to conceal the pointer values from native code, which is
+necessary to allow the GC to move JNI-referenced objects around, then we
+have to use a more complicated indirection mechanism.
+
+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."
+
+
+Pinned objects
+
+For some large chunks of data, notably primitive arrays and String data,
+JNI allows the VM to choose whether it wants to pin the array object or
+make a copy. We currently pin the memory for better execution performance.
+
+TODO: we're using simple root set references to pin 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 need to treat these differently.
+
+
+Global reference tracking
+
+There should be a small "active" set centered around the most-recently
+added items.
+
+Because it's global, access to it has to be synchronized. Additions and
+removals require grabbing a mutex. If the table serves as an indirection
+mechanism (i.e. it's not just a list for the benefit of the garbage
+collector), reference lookups may also require grabbing a mutex.
+
+The JNI spec does not define any sort of limit, so the list must be able
+to expand to a reasonable size. It may be useful to log significant
+increases in usage to help identify resource leaks.
+
+
+Weak-global reference tracking
+
+[TBD]
+
+
+Local reference tracking
+
+Each Thread/JNIEnv points to an IndirectRefTable.
+
+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 in the table.
+
+*/
+
+#ifdef WITH_JNI_STACK_CHECK
+# define COMPUTE_STACK_SUM(_self) computeStackSum(_self);
+# define CHECK_STACK_SUM(_self) checkStackSum(_self);
+
+/*
+ * 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->interpSave.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->interpSave.curFrame);
+ u4 stackCrc = self->stackCrc;
+ self->stackCrc = 0;
+ u4 crc = dvmInitCrc32();
+ crc = dvmComputeCrc32(crc, low, self->interpStackStart - low);
+ if (crc != stackCrc) {
+ const Method* meth = dvmGetCurrentJNIMethod();
+ if (dvmComputeExactFrameDepth(self->interpSave.curFrame) == 1) {
+ LOGD("JNI: bad stack CRC (0x%08x) -- okay during init", 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", stackCrc);
+ } else {
+ LOGW("JNI: bad stack CRC (%08x vs %08x)", crc, stackCrc);
+ dvmAbort();
+ }
+ }
+ self->stackCrc = (u4) -1; /* make logic errors more noticeable */
+}
+
+#else
+# define COMPUTE_STACK_SUM(_self) ((void)0)
+# define CHECK_STACK_SUM(_self) ((void)0)
+#endif
+
+
+/*
+ * ===========================================================================
+ * Utility functions
+ * ===========================================================================
+ */
+
+/*
+ * Entry/exit processing for all JNI calls.
+ *
+ * We 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.
+ */
+class ScopedJniThreadState {
+public:
+ explicit ScopedJniThreadState(JNIEnv* env) {
+ mSelf = ((JNIEnvExt*) env)->self;
+
+ if (UNLIKELY(gDvmJni.workAroundAppJniBugs)) {
+ // When emulating direct pointers with indirect references, it's critical
+ // that we use the correct per-thread indirect reference table.
+ Thread* self = gDvmJni.workAroundAppJniBugs ? dvmThreadSelf() : mSelf;
+ if (self != mSelf) {
+ LOGE("JNI ERROR: env->self != thread-self (%p vs. %p); auto-correcting", mSelf, self);
+ mSelf = self;
+ }
+ }
+
+ CHECK_STACK_SUM(mSelf);
+ dvmChangeStatus(mSelf, THREAD_RUNNING);
+ }
+
+ ~ScopedJniThreadState() {
+ dvmChangeStatus(mSelf, THREAD_NATIVE);
+ COMPUTE_STACK_SUM(mSelf);
+ }
+
+ inline Thread* self() {
+ return mSelf;
+ }
+
+private:
+ Thread* mSelf;
+
+ // Disallow copy and assignment.
+ ScopedJniThreadState(const ScopedJniThreadState&);
+ void operator=(const ScopedJniThreadState&);
+};
+
+#define kGlobalRefsTableInitialSize 512
+#define kGlobalRefsTableMaxSize 51200 /* arbitrary, must be < 64K */
+#define kGrefWaterInterval 100
+#define kTrackGrefUsage true
+
+#define kWeakGlobalRefsTableInitialSize 16
+
+#define kPinTableInitialSize 16
+#define kPinTableMaxSize 1024
+#define kPinComplainThreshold 10
+
+bool dvmJniStartup() {
+ if (!gDvm.jniGlobalRefTable.init(kGlobalRefsTableInitialSize,
+ kGlobalRefsTableMaxSize,
+ kIndirectKindGlobal)) {
+ return false;
+ }
+ if (!gDvm.jniWeakGlobalRefTable.init(kWeakGlobalRefsTableInitialSize,
+ kGlobalRefsTableMaxSize,
+ kIndirectKindWeakGlobal)) {
+ return false;
+ }
+
+ dvmInitMutex(&gDvm.jniGlobalRefLock);
+ dvmInitMutex(&gDvm.jniWeakGlobalRefLock);
+ gDvm.jniGlobalRefLoMark = 0;
+ gDvm.jniGlobalRefHiMark = kGrefWaterInterval * 2;
+
+ if (!dvmInitReferenceTable(&gDvm.jniPinRefTable, kPinTableInitialSize, kPinTableMaxSize)) {
+ return false;
+ }
+
+ dvmInitMutex(&gDvm.jniPinRefLock);
+
+ return true;
+}
+
+void dvmJniShutdown() {
+ gDvm.jniGlobalRefTable.destroy();
+ gDvm.jniWeakGlobalRefTable.destroy();
+ dvmClearReferenceTable(&gDvm.jniPinRefTable);
+}
+
+/*
+ * 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() {
+ Thread* self = dvmThreadSelf();
+ if (self == NULL) {
+ return NULL;
+ }
+ return (JNIEnvExt*) dvmGetThreadJNIEnv(self);
+}
+
+/*
+ * Convert an indirect reference to an Object reference. The indirect
+ * reference may be local, global, or weak-global.
+ *
+ * If "jobj" is NULL, or is a weak global reference whose reference has
+ * been cleared, this returns NULL. If jobj is an invalid indirect
+ * reference, kInvalidIndirectRefObject is returned.
+ *
+ * Note "env" may be NULL when decoding global references.
+ */
+Object* dvmDecodeIndirectRef(Thread* self, jobject jobj) {
+ if (jobj == NULL) {
+ return NULL;
+ }
+
+ switch (indirectRefKind(jobj)) {
+ case kIndirectKindLocal:
+ {
+ Object* result = self->jniLocalRefTable.get(jobj);
+ if (UNLIKELY(result == NULL)) {
+ LOGE("JNI ERROR (app bug): use of deleted local reference (%p)", jobj);
+ dvmAbort();
+ }
+ return result;
+ }
+ case kIndirectKindGlobal:
+ {
+ // TODO: find a way to avoid the mutex activity here
+ IndirectRefTable* pRefTable = &gDvm.jniGlobalRefTable;
+ ScopedPthreadMutexLock lock(&gDvm.jniGlobalRefLock);
+ Object* result = pRefTable->get(jobj);
+ if (UNLIKELY(result == NULL)) {
+ LOGE("JNI ERROR (app bug): use of deleted global reference (%p)", jobj);
+ dvmAbort();
+ }
+ return result;
+ }
+ case kIndirectKindWeakGlobal:
+ {
+ // TODO: find a way to avoid the mutex activity here
+ IndirectRefTable* pRefTable = &gDvm.jniWeakGlobalRefTable;
+ ScopedPthreadMutexLock lock(&gDvm.jniWeakGlobalRefLock);
+ Object* result = pRefTable->get(jobj);
+ if (result == kClearedJniWeakGlobal) {
+ result = NULL;
+ } else if (UNLIKELY(result == NULL)) {
+ LOGE("JNI ERROR (app bug): use of deleted weak global reference (%p)", jobj);
+ dvmAbort();
+ }
+ return result;
+ }
+ case kIndirectKindInvalid:
+ default:
+ if (UNLIKELY(gDvmJni.workAroundAppJniBugs)) {
+ // Assume an invalid local reference is actually a direct pointer.
+ return reinterpret_cast<Object*>(jobj);
+ }
+ LOGW("Invalid indirect reference %p in decodeIndirectRef", jobj);
+ dvmAbort();
+ return kInvalidIndirectRefObject;
+ }
+}
+
+static void AddLocalReferenceFailure(IndirectRefTable* pRefTable) {
+ pRefTable->dump("JNI local");
+ LOGE("Failed adding to JNI local ref table (has %zd entries)", pRefTable->capacity());
+ dvmDumpThread(dvmThreadSelf(), false);
+ dvmAbort(); // spec says call FatalError; this is equivalent
+}
+
+/*
+ * 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.
+ */
+static inline jobject addLocalReference(Thread* self, Object* obj) {
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ IndirectRefTable* pRefTable = &self->jniLocalRefTable;
+ void* curFrame = self->interpSave.curFrame;
+ u4 cookie = SAVEAREA_FROM_FP(curFrame)->xtra.localRefCookie;
+ jobject jobj = (jobject) pRefTable->add(cookie, obj);
+ if (UNLIKELY(jobj == NULL)) {
+ AddLocalReferenceFailure(pRefTable);
+ }
+ if (UNLIKELY(gDvmJni.workAroundAppJniBugs)) {
+ // Hand out direct pointers to support broken old apps.
+ return reinterpret_cast<jobject>(obj);
+ }
+ return jobj;
+}
+
+/*
+ * Ensure that at least "capacity" references can be held in the local
+ * refs table of the current thread.
+ */
+static bool ensureLocalCapacity(Thread* self, int capacity) {
+ int numEntries = self->jniLocalRefTable.capacity();
+ // TODO: this isn't quite right, since "numEntries" includes holes
+ return ((kJniLocalRefMax - numEntries) >= capacity);
+}
+
+/*
+ * Explicitly delete a reference from the local list.
+ */
+static void deleteLocalReference(Thread* self, jobject jobj) {
+ if (jobj == NULL) {
+ return;
+ }
+
+ IndirectRefTable* pRefTable = &self->jniLocalRefTable;
+ void* curFrame = self->interpSave.curFrame;
+ u4 cookie = SAVEAREA_FROM_FP(curFrame)->xtra.localRefCookie;
+ if (!pRefTable->remove(cookie, jobj)) {
+ /*
+ * 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", jobj);
+ }
+}
+
+/*
+ * 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(Object* obj) {
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ //LOGI("adding obj=%p", obj);
+ //dvmDumpThread(dvmThreadSelf(), false);
+
+ if (false && dvmIsClassObject((Object*)obj)) {
+ ClassObject* clazz = (ClassObject*) obj;
+ LOGI("-------");
+ LOGI("Adding global ref on class %s", 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("-------");
+ LOGI("Adding global ref on string '%s'", 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)",
+ arrayObj, arrayObj->length);
+ dvmDumpThread(dvmThreadSelf(), false);
+ }
+ }
+
+ ScopedPthreadMutexLock lock(&gDvm.jniGlobalRefLock);
+
+ /*
+ * 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.
+ */
+ jobject jobj = (jobject) gDvm.jniGlobalRefTable.add(IRT_FIRST_SEGMENT, obj);
+ if (jobj == NULL) {
+ gDvm.jniGlobalRefTable.dump("JNI global");
+ LOGE("Failed adding to JNI global ref table (%zd entries)",
+ gDvm.jniGlobalRefTable.capacity());
+ dvmAbort();
+ }
+
+ LOGVV("GREF add %p (%s.%s)", obj,
+ dvmGetCurrentJNIMethod()->clazz->descriptor,
+ dvmGetCurrentJNIMethod()->name);
+
+ /* GREF usage tracking; should probably be disabled for production env */
+ if (kTrackGrefUsage && gDvm.jniGrefLimit != 0) {
+ int count = gDvm.jniGlobalRefTable.capacity();
+ // TODO: adjust for "holes"
+ if (count > gDvm.jniGlobalRefHiMark) {
+ LOGD("GREF has increased to %d", count);
+ gDvm.jniGlobalRefHiMark += kGrefWaterInterval;
+ gDvm.jniGlobalRefLoMark += kGrefWaterInterval;
+
+ /* watch for "excessive" use; not generally appropriate */
+ if (count >= gDvm.jniGrefLimit) {
+ if (gDvmJni.warnOnly) {
+ LOGW("Excessive JNI global references (%d)", count);
+ } else {
+ gDvm.jniGlobalRefTable.dump("JNI global");
+ LOGE("Excessive JNI global references (%d)", count);
+ dvmAbort();
+ }
+ }
+ }
+ }
+ return jobj;
+}
+
+static jobject addWeakGlobalReference(Object* obj) {
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ ScopedPthreadMutexLock lock(&gDvm.jniWeakGlobalRefLock);
+ IndirectRefTable *table = &gDvm.jniWeakGlobalRefTable;
+ jobject jobj = (jobject) table->add(IRT_FIRST_SEGMENT, obj);
+ if (jobj == NULL) {
+ gDvm.jniWeakGlobalRefTable.dump("JNI weak global");
+ LOGE("Failed adding to JNI weak global ref table (%zd entries)", table->capacity());
+ dvmAbort();
+ }
+ return jobj;
+}
+
+static void deleteWeakGlobalReference(jobject jobj) {
+ if (jobj == NULL) {
+ return;
+ }
+
+ ScopedPthreadMutexLock lock(&gDvm.jniWeakGlobalRefLock);
+ IndirectRefTable *table = &gDvm.jniWeakGlobalRefTable;
+ if (!table->remove(IRT_FIRST_SEGMENT, jobj)) {
+ LOGW("JNI: DeleteWeakGlobalRef(%p) failed to find entry", jobj);
+ }
+}
+
+/*
+ * 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 jobj) {
+ if (jobj == NULL) {
+ return;
+ }
+
+ ScopedPthreadMutexLock lock(&gDvm.jniGlobalRefLock);
+ if (!gDvm.jniGlobalRefTable.remove(IRT_FIRST_SEGMENT, jobj)) {
+ LOGW("JNI: DeleteGlobalRef(%p) failed to find entry", jobj);
+ return;
+ }
+
+ if (kTrackGrefUsage && gDvm.jniGrefLimit != 0) {
+ int count = gDvm.jniGlobalRefTable.capacity();
+ // TODO: not quite right, need to subtract holes
+ if (count < gDvm.jniGlobalRefLoMark) {
+ LOGD("GREF has decreased to %d", count);
+ gDvm.jniGlobalRefHiMark -= kGrefWaterInterval;
+ gDvm.jniGlobalRefLoMark -= kGrefWaterInterval;
+ }
+ }
+}
+
+/*
+ * Objects don't currently move, so we just need to create a reference
+ * that will ensure the array object isn't collected.
+ *
+ * We use a separate reference table, which is part of the GC root set.
+ */
+static void pinPrimitiveArray(ArrayObject* arrayObj) {
+ if (arrayObj == NULL) {
+ return;
+ }
+
+ ScopedPthreadMutexLock lock(&gDvm.jniPinRefLock);
+
+ if (!dvmAddToReferenceTable(&gDvm.jniPinRefTable, (Object*)arrayObj)) {
+ dvmDumpReferenceTable(&gDvm.jniPinRefTable, "JNI pinned array");
+ LOGE("Failed adding to JNI pinned array ref table (%d entries)",
+ (int) dvmReferenceTableEntries(&gDvm.jniPinRefTable));
+ dvmDumpThread(dvmThreadSelf(), false);
+ dvmAbort();
+ }
+
+ /*
+ * If we're watching global ref usage, also keep an eye on these.
+ *
+ * The total number of pinned primitive arrays should be pretty small.
+ * A single array should not be pinned more than once or twice; any
+ * more than that is a strong indicator that a Release function is
+ * not being called.
+ */
+ if (kTrackGrefUsage && gDvm.jniGrefLimit != 0) {
+ int count = 0;
+ Object** ppObj = gDvm.jniPinRefTable.table;
+ while (ppObj < gDvm.jniPinRefTable.nextEntry) {
+ if (*ppObj++ == (Object*) arrayObj)
+ count++;
+ }
+
+ if (count > kPinComplainThreshold) {
+ LOGW("JNI: pin count on array %p (%s) is now %d",
+ arrayObj, arrayObj->clazz->descriptor, count);
+ /* keep going */
+ }
+ }
+}
+
+/*
+ * Un-pin the array object. If an object was pinned twice, it must be
+ * unpinned twice before it's free to move.
+ */
+static void unpinPrimitiveArray(ArrayObject* arrayObj) {
+ if (arrayObj == NULL) {
+ return;
+ }
+
+ ScopedPthreadMutexLock lock(&gDvm.jniPinRefLock);
+ if (!dvmRemoveFromReferenceTable(&gDvm.jniPinRefTable,
+ gDvm.jniPinRefTable.table, (Object*) arrayObj))
+ {
+ LOGW("JNI: unpinPrimitiveArray(%p) failed to find entry (valid=%d)",
+ arrayObj, dvmIsHeapAddress((Object*) arrayObj));
+ return;
+ }
+}
+
+/*
+ * Dump the contents of the JNI reference tables to the log file.
+ *
+ * We only dump the local refs associated with the current thread.
+ */
+void dvmDumpJniReferenceTables() {
+ Thread* self = dvmThreadSelf();
+ self->jniLocalRefTable.dump("JNI local");
+ gDvm.jniGlobalRefTable.dump("JNI global");
+ dvmDumpReferenceTable(&gDvm.jniPinRefTable, "JNI pinned array");
+}
+
+/*
+ * 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 passed in as an argument when invoked by native code (and hence
+ * is in the JNI local refs table)
+ * - was returned to it from JNI (and is now in the local refs table)
+ * - is present in the JNI global refs table
+ *
+ * Used by -Xcheck:jni and GetObjectRefType.
+ */
+jobjectRefType dvmGetJNIRefType(Thread* self, jobject jobj) {
+ /*
+ * IndirectRefKind is currently defined as an exact match of
+ * jobjectRefType, so this is easy. We have to decode it to determine
+ * if it's a valid reference and not merely valid-looking.
+ */
+ assert(jobj != NULL);
+
+ Object* obj = dvmDecodeIndirectRef(self, jobj);
+ if (obj == reinterpret_cast<Object*>(jobj) && gDvmJni.workAroundAppJniBugs) {
+ // If we're handing out direct pointers, check whether 'jobj' is a direct reference
+ // to a local reference.
+ return self->jniLocalRefTable.contains(obj) ? JNILocalRefType : JNIInvalidRefType;
+ } else if (obj == kInvalidIndirectRefObject) {
+ return JNIInvalidRefType;
+ } else {
+ return (jobjectRefType) indirectRefKind(jobj);
+ }
+}
+
+static void dumpMethods(Method* methods, size_t methodCount, const char* name) {
+ size_t i;
+ for (i = 0; i < methodCount; ++i) {
+ Method* method = &methods[i];
+ if (strcmp(name, method->name) == 0) {
+ char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
+ LOGE("Candidate: %s.%s:%s", method->clazz->descriptor, name, desc);
+ free(desc);
+ }
+ }
+}
+
+static void dumpCandidateMethods(ClassObject* clazz, const char* methodName, const char* signature) {
+ LOGE("ERROR: couldn't find native method");
+ LOGE("Requested: %s.%s:%s", clazz->descriptor, methodName, signature);
+ dumpMethods(clazz->virtualMethods, clazz->virtualMethodCount, methodName);
+ dumpMethods(clazz->directMethods, clazz->directMethodCount, methodName);
+}
+
+/*
+ * Register a method that uses JNI calling conventions.
+ */
+static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
+ const char* signature, void* fnPtr)
+{
+ if (fnPtr == NULL) {
+ return false;
+ }
+
+ // If a signature starts with a '!', we take that as a sign that the native code doesn't
+ // need the extra JNI arguments (the JNIEnv* and the jclass).
+ bool fastJni = false;
+ if (*signature == '!') {
+ fastJni = true;
+ ++signature;
+ LOGV("fast JNI method %s.%s:%s detected", clazz->descriptor, methodName, signature);
+ }
+
+ Method* method = dvmFindDirectMethodByDescriptor(clazz, methodName, signature);
+ if (method == NULL) {
+ method = dvmFindVirtualMethodByDescriptor(clazz, methodName, signature);
+ }
+ if (method == NULL) {
+ dumpCandidateMethods(clazz, methodName, signature);
+ return false;
+ }
+
+ if (!dvmIsNativeMethod(method)) {
+ LOGW("Unable to register: not native: %s.%s:%s", clazz->descriptor, methodName, signature);
+ return false;
+ }
+
+ if (fastJni) {
+ // In this case, we have extra constraints to check...
+ if (dvmIsSynchronizedMethod(method)) {
+ // Synchronization is usually provided by the JNI bridge,
+ // but we won't have one.
+ LOGE("fast JNI method %s.%s:%s cannot be synchronized",
+ clazz->descriptor, methodName, signature);
+ return false;
+ }
+ if (!dvmIsStaticMethod(method)) {
+ // There's no real reason for this constraint, but since we won't
+ // be supplying a JNIEnv* or a jobject 'this', you're effectively
+ // static anyway, so it seems clearer to say so.
+ LOGE("fast JNI method %s.%s:%s cannot be non-static",
+ clazz->descriptor, methodName, signature);
+ return false;
+ }
+ }
+
+ if (method->nativeFunc != dvmResolveNativeMethod) {
+ /* this is allowed, but unusual */
+ LOGV("Note: %s.%s:%s was already registered", clazz->descriptor, methodName, signature);
+ }
+
+ method->fastJni = fastJni;
+ dvmUseJNIBridge(method, fnPtr);
+
+ LOGV("JNI-registered %s.%s:%s", clazz->descriptor, methodName, signature);
+ return true;
+}
+
+static const char* builtInPrefixes[] = {
+ "Landroid/",
+ "Lcom/android/",
+ "Lcom/google/android/",
+ "Ldalvik/",
+ "Ljava/",
+ "Ljavax/",
+ "Llibcore/",
+ "Lorg/apache/harmony/",
+};
+
+static bool shouldTrace(Method* method) {
+ const char* className = method->clazz->descriptor;
+ // Return true if the -Xjnitrace setting implies we should trace 'method'.
+ if (gDvm.jniTrace && strstr(className, gDvm.jniTrace)) {
+ return true;
+ }
+ // Return true if we're trying to log all third-party JNI activity and 'method' doesn't look
+ // like part of Android.
+ if (gDvmJni.logThirdPartyJni) {
+ for (size_t i = 0; i < NELEM(builtInPrefixes); ++i) {
+ if (strstr(className, builtInPrefixes[i]) == className) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Point "method->nativeFunc" at the JNI bridge, and overload "method->insns"
+ * to point at the actual function.
+ */
+void dvmUseJNIBridge(Method* method, void* func) {
+ method->shouldTrace = shouldTrace(method);
+
+ // Does the method take any reference arguments?
+ method->noRef = true;
+ const char* cp = method->shorty;
+ while (*++cp != '\0') { // Pre-increment to skip return type.
+ if (*cp == 'L') {
+ method->noRef = false;
+ break;
+ }
+ }
+
+ DalvikBridgeFunc bridge = gDvmJni.useCheckJni ? dvmCheckCallJNIMethod : dvmCallJNIMethod;
+ dvmSetNativeFunc(method, bridge, (const u2*) func);
+}
+
+// TODO: rewrite this to share code with CheckJNI's tracing...
+static void appendValue(char type, const JValue value, char* buf, size_t n, bool appendComma)
+{
+ size_t len = strlen(buf);
+ if (len >= n - 32) { // 32 should be longer than anything we could append.
+ buf[len - 1] = '.';
+ buf[len - 2] = '.';
+ buf[len - 3] = '.';
+ return;
+ }
+ char* p = buf + len;
+ switch (type) {
+ case 'B':
+ if (value.b >= 0 && value.b < 10) {
+ sprintf(p, "%d", value.b);
+ } else {
+ sprintf(p, "%#x (%d)", value.b, value.b);
+ }
+ break;
+ case 'C':
+ if (value.c < 0x7f && value.c >= ' ') {
+ sprintf(p, "U+%x ('%c')", value.c, value.c);
+ } else {
+ sprintf(p, "U+%x", value.c);
+ }
+ break;
+ case 'D':
+ sprintf(p, "%g", value.d);
+ break;
+ case 'F':
+ sprintf(p, "%g", value.f);
+ break;
+ case 'I':
+ sprintf(p, "%d", value.i);
+ break;
+ case 'L':
+ sprintf(p, "%#x", value.i);
+ break;
+ case 'J':
+ sprintf(p, "%lld", value.j);
+ break;
+ case 'S':
+ sprintf(p, "%d", value.s);
+ break;
+ case 'V':
+ strcpy(p, "void");
+ break;
+ case 'Z':
+ strcpy(p, value.z ? "true" : "false");
+ break;
+ default:
+ sprintf(p, "unknown type '%c'", type);
+ break;
+ }
+
+ if (appendComma) {
+ strcat(p, ", ");
+ }
+}
+
+static void logNativeMethodEntry(const Method* method, const u4* args)
+{
+ char thisString[32] = { 0 };
+ const u4* sp = args;
+ if (!dvmIsStaticMethod(method)) {
+ sprintf(thisString, "this=0x%08x ", *sp++);
+ }
+
+ char argsString[128]= { 0 };
+ const char* desc = &method->shorty[1];
+ while (*desc != '\0') {
+ char argType = *desc++;
+ JValue value;
+ if (argType == 'D' || argType == 'J') {
+ value.j = dvmGetArgLong(sp, 0);
+ sp += 2;
+ } else {
+ value.i = *sp++;
+ }
+ appendValue(argType, value, argsString, sizeof(argsString),
+ *desc != '\0');
+ }
+
+ std::string className(dvmHumanReadableDescriptor(method->clazz->descriptor));
+ char* signature = dexProtoCopyMethodDescriptor(&method->prototype);
+ LOGI("-> %s %s%s %s(%s)", className.c_str(), method->name, signature, thisString, argsString);
+ free(signature);
+}
+
+static void logNativeMethodExit(const Method* method, Thread* self, const JValue returnValue)
+{
+ std::string className(dvmHumanReadableDescriptor(method->clazz->descriptor));
+ char* signature = dexProtoCopyMethodDescriptor(&method->prototype);
+ if (dvmCheckException(self)) {
+ Object* exception = dvmGetException(self);
+ std::string exceptionClassName(dvmHumanReadableDescriptor(exception->clazz->descriptor));
+ LOGI("<- %s %s%s threw %s", className.c_str(),
+ method->name, signature, exceptionClassName.c_str());
+ } else {
+ char returnValueString[128] = { 0 };
+ char returnType = method->shorty[0];
+ appendValue(returnType, returnValue, returnValueString, sizeof(returnValueString), false);
+ LOGI("<- %s %s%s returned %s", className.c_str(),
+ method->name, signature, returnValueString);
+ }
+ free(signature);
+}
+
+/*
+ * Get the method currently being executed by examining the interp stack.
+ */
+const Method* dvmGetCurrentJNIMethod() {
+ assert(dvmThreadSelf() != NULL);
+
+ void* fp = dvmThreadSelf()->interpSave.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");
+ dvmAbort();
+ }
+ }
+
+ if (!dvmAddToReferenceTable(refTable, obj)) {
+ /* ran out of memory? could throw exception instead */
+ LOGE("Unable to add entry to monitor tracking table");
+ dvmAbort();
+ } else {
+ LOGVV("--- added monitor %p", obj);
+ }
+}
+
+/*
+ * Track a JNI MonitorExit in the current thread.
+ */
+static void trackMonitorExit(Thread* self, Object* obj) {
+ ReferenceTable* pRefTable = &self->jniMonitorRefTable;
+
+ if (!dvmRemoveFromReferenceTable(pRefTable, pRefTable->table, obj)) {
+ LOGE("JNI monitor %p not found in tracking list", obj);
+ /* keep going? */
+ } else {
+ LOGVV("--- removed monitor %p", obj);
+ }
+}
+
+/*
+ * Release all monitors held by the jniMonitorRefTable list.
+ */
+void dvmReleaseJniMonitors(Thread* self) {
+ ReferenceTable* pRefTable = &self->jniMonitorRefTable;
+ Object** top = pRefTable->table;
+
+ if (top == NULL) {
+ return;
+ }
+ Object** ptr = pRefTable->nextEntry;
+ while (--ptr >= top) {
+ if (!dvmUnlockObject(self, *ptr)) {
+ LOGW("Unable to unlock monitor %p at thread detach", *ptr);
+ } else {
+ LOGVV("--- detach-releasing monitor %p", *ptr);
+ }
+ }
+
+ /* zap it */
+ pRefTable->nextEntry = pRefTable->table;
+}
+
+/*
+ * Determine if the specified class can be instantiated from JNI. This
+ * is used by AllocObject / NewObject, which are documented as throwing
+ * an exception for abstract and interface classes, and not accepting
+ * array classes. We also want to reject attempts to create new Class
+ * objects, since only DefineClass should do that.
+ */
+static bool canAllocClass(ClassObject* clazz) {
+ if (dvmIsAbstractClass(clazz) || dvmIsInterfaceClass(clazz)) {
+ /* JNI spec defines what this throws */
+ dvmThrowInstantiationException(clazz, "abstract class or interface");
+ return false;
+ } else if (dvmIsArrayClass(clazz) || dvmIsTheClassClass(clazz)) {
+ /* spec says "must not" for arrays, ignores Class */
+ dvmThrowInstantiationException(clazz, "wrong JNI function");
+ return false;
+ }
+ return true;
+}
+
+
+/*
+ * ===========================================================================
+ * JNI call bridge
+ * ===========================================================================
+ */
+
+/*
+ * The functions here form a bridge between interpreted code and JNI native
+ * functions. The basic task is to convert an array of primitives and
+ * references into C-style function arguments. This is architecture-specific
+ * and usually requires help from assembly code.
+ *
+ * The bridge takes four arguments: the array of parameters, a place to
+ * store the function result (if any), the method to call, and a pointer
+ * to the current thread.
+ *
+ * These functions aren't called directly from elsewhere in the VM.
+ * A pointer in the Method struct points to one of these, and when a native
+ * method is invoked the interpreter jumps to it.
+ *
+ * (The "internal native" methods are invoked the same way, but instead
+ * of calling through a bridge, the target method is called directly.)
+ *
+ * The "args" array should not be modified, but we do so anyway for
+ * performance reasons. We know that it points to the "outs" area on
+ * the current method's interpreted stack. This area is ignored by the
+ * precise GC, because there is no register map for a native method (for
+ * an interpreted method the args would be listed in the argument set).
+ * We know all of the values exist elsewhere on the interpreted stack,
+ * because the method call setup copies them right before making the call,
+ * so we don't have to worry about concealing stuff from the GC.
+ *
+ * If we don't want to modify "args", we either have to create a local
+ * copy and modify it before calling dvmPlatformInvoke, or we have to do
+ * the local reference replacement within dvmPlatformInvoke. The latter
+ * has some performance advantages, though if we can inline the local
+ * reference adds we may win when there's a lot of reference args (unless
+ * we want to code up some local ref table manipulation in assembly.
+ */
+
+/*
+ * If necessary, convert the value in pResult from a local/global reference
+ * to an object pointer.
+ *
+ * If the returned reference is invalid, kInvalidIndirectRefObject will
+ * be returned in pResult.
+ */
+static inline void convertReferenceResult(JNIEnv* env, JValue* pResult,
+ const Method* method, Thread* self)
+{
+ if (method->shorty[0] == 'L' && !dvmCheckException(self) && pResult->l != NULL) {
+ pResult->l = dvmDecodeIndirectRef(self, (jobject) pResult->l);
+ }
+}
+
+/*
+ * General form, handles all cases.
+ */
+void dvmCallJNIMethod(const u4* args, JValue* pResult, const Method* method, Thread* self) {
+ u4* modArgs = (u4*) args;
+ jclass staticMethodClass = NULL;
+
+ u4 accessFlags = method->accessFlags;
+ bool isSynchronized = (accessFlags & ACC_SYNCHRONIZED) != 0;
+
+ //LOGI("JNI calling %p (%s.%s:%s):", method->insns,
+ // method->clazz->descriptor, method->name, method->shorty);
+
+ /*
+ * Walk the argument list, creating local references for appropriate
+ * arguments.
+ */
+ int idx = 0;
+ Object* lockObj;
+ if ((accessFlags & ACC_STATIC) != 0) {
+ lockObj = (Object*) method->clazz;
+ /* add the class object we pass in */
+ staticMethodClass = (jclass) addLocalReference(self, (Object*) method->clazz);
+ } else {
+ lockObj = (Object*) args[0];
+ /* add "this" */
+ modArgs[idx++] = (u4) addLocalReference(self, (Object*) modArgs[0]);
+ }
+
+ if (!method->noRef) {
+ const char* shorty = &method->shorty[1]; /* skip return type */
+ while (*shorty != '\0') {
+ switch (*shorty++) {
+ case 'L':
+ //LOGI(" local %d: 0x%08x", idx, modArgs[idx]);
+ if (modArgs[idx] != 0) {
+ modArgs[idx] = (u4) addLocalReference(self, (Object*) modArgs[idx]);
+ }
+ break;
+ case 'D':
+ case 'J':
+ idx++;
+ break;
+ default:
+ /* Z B C S I -- do nothing */
+ break;
+ }
+ idx++;
+ }
+ }
+
+ if (UNLIKELY(method->shouldTrace)) {
+ logNativeMethodEntry(method, args);
+ }
+ if (UNLIKELY(isSynchronized)) {
+ dvmLockObject(self, lockObj);
+ }
+
+ ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
+
+ ANDROID_MEMBAR_FULL(); /* guarantee ordering on method->insns */
+ assert(method->insns != NULL);
+
+ JNIEnv* env = self->jniEnv;
+ COMPUTE_STACK_SUM(self);
+ dvmPlatformInvoke(env,
+ (ClassObject*) staticMethodClass,
+ method->jniArgInfo, method->insSize, modArgs, method->shorty,
+ (void*) method->insns, pResult);
+ CHECK_STACK_SUM(self);
+
+ dvmChangeStatus(self, oldStatus);
+
+ convertReferenceResult(env, pResult, method, self);
+
+ if (UNLIKELY(isSynchronized)) {
+ dvmUnlockObject(self, lockObj);
+ }
+ if (UNLIKELY(method->shouldTrace)) {
+ logNativeMethodExit(method, self, *pResult);
+ }
+}
+
+/*
+ * ===========================================================================
+ * JNI implementation
+ * ===========================================================================
+ */
+
+/*
+ * Return the version of the native method interface.
+ */
+static jint GetVersion(JNIEnv* env) {
+ /*
+ * 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.
+ */
+ ScopedJniThreadState ts(env);
+ 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);
+
+ ScopedJniThreadState ts(env);
+ LOGW("JNI DefineClass is not supported");
+ 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) {
+ ScopedJniThreadState ts(env);
+
+ const Method* thisMethod = dvmGetCurrentJNIMethod();
+ assert(thisMethod != NULL);
+
+ Object* loader;
+ Object* trackedLoader = NULL;
+ if (ts.self()->classLoaderOverride != NULL) {
+ /* hack for JNI_OnLoad */
+ assert(strcmp(thisMethod->name, "nativeLoad") == 0);
+ loader = ts.self()->classLoaderOverride;
+ } else if (thisMethod == gDvm.methDalvikSystemNativeStart_main ||
+ thisMethod == gDvm.methDalvikSystemNativeStart_run) {
+ /* start point of invocation interface */
+ if (!gDvm.initializing) {
+ loader = trackedLoader = dvmGetSystemClassLoader();
+ } else {
+ loader = NULL;
+ }
+ } else {
+ loader = thisMethod->clazz->classLoader;
+ }
+
+ char* descriptor = dvmNameToDescriptor(name);
+ if (descriptor == NULL) {
+ return NULL;
+ }
+ ClassObject* clazz = dvmFindClassNoInit(descriptor, loader);
+ free(descriptor);
+
+ jclass jclazz = (jclass) addLocalReference(ts.self(), (Object*) clazz);
+ dvmReleaseTrackedAlloc(trackedLoader, ts.self());
+ return jclazz;
+}
+
+/*
+ * Return the superclass of a class.
+ */
+static jclass GetSuperclass(JNIEnv* env, jclass jclazz) {
+ ScopedJniThreadState ts(env);
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+ return (jclass) addLocalReference(ts.self(), (Object*)clazz->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 jclazz1, jclass jclazz2) {
+ ScopedJniThreadState ts(env);
+ ClassObject* clazz1 = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz1);
+ ClassObject* clazz2 = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz2);
+ return dvmInstanceof(clazz1, clazz2);
+}
+
+/*
+ * Given a java.lang.reflect.Method or .Constructor, return a methodID.
+ */
+static jmethodID FromReflectedMethod(JNIEnv* env, jobject jmethod) {
+ ScopedJniThreadState ts(env);
+ Object* method = dvmDecodeIndirectRef(ts.self(), jmethod);
+ return (jmethodID) dvmGetMethodFromReflectObj(method);
+}
+
+/*
+ * Given a java.lang.reflect.Field, return a fieldID.
+ */
+static jfieldID FromReflectedField(JNIEnv* env, jobject jfield) {
+ ScopedJniThreadState ts(env);
+ Object* field = dvmDecodeIndirectRef(ts.self(), jfield);
+ return (jfieldID) dvmGetFieldFromReflectObj(field);
+}
+
+/*
+ * 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 jcls, jmethodID methodID, jboolean isStatic) {
+ ScopedJniThreadState ts(env);
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jcls);
+ Object* obj = dvmCreateReflectObjForMethod(clazz, (Method*) methodID);
+ dvmReleaseTrackedAlloc(obj, NULL);
+ return addLocalReference(ts.self(), 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 jcls, jfieldID fieldID, jboolean isStatic) {
+ ScopedJniThreadState ts(env);
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jcls);
+ Object* obj = dvmCreateReflectObjForField(clazz, (Field*) fieldID);
+ dvmReleaseTrackedAlloc(obj, NULL);
+ return addLocalReference(ts.self(), obj);
+}
+
+/*
+ * Take this exception and throw it.
+ */
+static jint Throw(JNIEnv* env, jthrowable jobj) {
+ ScopedJniThreadState ts(env);
+ if (jobj != NULL) {
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj);
+ dvmSetException(ts.self(), obj);
+ return JNI_OK;
+ }
+ return JNI_ERR;
+}
+
+/*
+ * Constructs an exception object from the specified class with the message
+ * specified by "message", and throws it.
+ */
+static jint ThrowNew(JNIEnv* env, jclass jclazz, const char* message) {
+ ScopedJniThreadState ts(env);
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+ dvmThrowException(clazz, message);
+ // TODO: should return failure if this didn't work (e.g. OOM)
+ 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) {
+ ScopedJniThreadState ts(env);
+ Object* exception = dvmGetException(ts.self());
+ jthrowable localException = (jthrowable) addLocalReference(ts.self(), 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");
+ }
+ return localException;
+}
+
+/*
+ * Print an exception and stack trace to stderr.
+ */
+static void ExceptionDescribe(JNIEnv* env) {
+ ScopedJniThreadState ts(env);
+ Object* exception = dvmGetException(ts.self());
+ if (exception != NULL) {
+ dvmPrintExceptionStackTrace();
+ } else {
+ LOGI("Odd: ExceptionDescribe called, but no exception pending");
+ }
+}
+
+/*
+ * Clear the exception currently being thrown.
+ *
+ * TODO: we should be able to skip the enter/exit stuff.
+ */
+static void ExceptionClear(JNIEnv* env) {
+ ScopedJniThreadState ts(env);
+ dvmClearException(ts.self());
+}
+
+/*
+ * 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", 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) {
+ ScopedJniThreadState ts(env);
+ if (!ensureLocalCapacity(ts.self(), capacity) ||
+ !dvmPushLocalFrame(ts.self(), dvmGetCurrentJNIMethod()))
+ {
+ /* yes, OutOfMemoryError, not StackOverflowError */
+ dvmClearException(ts.self());
+ dvmThrowOutOfMemoryError("out of stack in JNI PushLocalFrame");
+ return JNI_ERR;
+ }
+ return JNI_OK;
+}
+
+/*
+ * Pop the local frame off. If "jresult" is not null, add it as a
+ * local reference on the now-current frame.
+ */
+static jobject PopLocalFrame(JNIEnv* env, jobject jresult) {
+ ScopedJniThreadState ts(env);
+ Object* result = dvmDecodeIndirectRef(ts.self(), jresult);
+ if (!dvmPopLocalFrame(ts.self())) {
+ LOGW("JNI WARNING: too many PopLocalFrame calls");
+ dvmClearException(ts.self());
+ dvmThrowRuntimeException("too many PopLocalFrame calls");
+ }
+ return addLocalReference(ts.self(), result);
+}
+
+/*
+ * Add a reference to the global list.
+ */
+static jobject NewGlobalRef(JNIEnv* env, jobject jobj) {
+ ScopedJniThreadState ts(env);
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj);
+ return addGlobalReference(obj);
+}
+
+/*
+ * Delete a reference from the global list.
+ */
+static void DeleteGlobalRef(JNIEnv* env, jobject jglobalRef) {
+ ScopedJniThreadState ts(env);
+ deleteGlobalReference(jglobalRef);
+}
+
+
+/*
+ * Add a reference to the local list.
+ */
+static jobject NewLocalRef(JNIEnv* env, jobject jobj) {
+ ScopedJniThreadState ts(env);
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj);
+ return addLocalReference(ts.self(), obj);
+}
+
+/*
+ * Delete a reference from the local list.
+ */
+static void DeleteLocalRef(JNIEnv* env, jobject jlocalRef) {
+ ScopedJniThreadState ts(env);
+ deleteLocalReference(ts.self(), jlocalRef);
+}
+
+/*
+ * Ensure that the local references table can hold at least this many
+ * references.
+ */
+static jint EnsureLocalCapacity(JNIEnv* env, jint capacity) {
+ ScopedJniThreadState ts(env);
+ bool okay = ensureLocalCapacity(ts.self(), capacity);
+ if (!okay) {
+ dvmThrowOutOfMemoryError("can't ensure local reference capacity");
+ }
+ return okay ? 0 : -1;
+}
+
+
+/*
+ * Determine whether two Object references refer to the same underlying object.
+ */
+static jboolean IsSameObject(JNIEnv* env, jobject jref1, jobject jref2) {
+ ScopedJniThreadState ts(env);
+ Object* obj1 = dvmDecodeIndirectRef(ts.self(), jref1);
+ Object* obj2 = dvmDecodeIndirectRef(ts.self(), jref2);
+ return (obj1 == obj2);
+}
+
+/*
+ * Allocate a new object without invoking any constructors.
+ */
+static jobject AllocObject(JNIEnv* env, jclass jclazz) {
+ ScopedJniThreadState ts(env);
+
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+ if (!canAllocClass(clazz) ||
+ (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)))
+ {
+ assert(dvmCheckException(ts.self()));
+ return NULL;
+ }
+
+ Object* newObj = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+ return addLocalReference(ts.self(), newObj);
+}
+
+/*
+ * Allocate a new object and invoke the supplied constructor.
+ */
+static jobject NewObject(JNIEnv* env, jclass jclazz, jmethodID methodID, ...) {
+ ScopedJniThreadState ts(env);
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+
+ if (!canAllocClass(clazz) || (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz))) {
+ assert(dvmCheckException(ts.self()));
+ return NULL;
+ }
+
+ Object* newObj = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+ jobject result = addLocalReference(ts.self(), newObj);
+ if (newObj != NULL) {
+ JValue unused;
+ va_list args;
+ va_start(args, methodID);
+ dvmCallMethodV(ts.self(), (Method*) methodID, newObj, true, &unused, args);
+ va_end(args);
+ }
+ return result;
+}
+
+static jobject NewObjectV(JNIEnv* env, jclass jclazz, jmethodID methodID, va_list args) {
+ ScopedJniThreadState ts(env);
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+
+ if (!canAllocClass(clazz) || (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz))) {
+ assert(dvmCheckException(ts.self()));
+ return NULL;
+ }
+
+ Object* newObj = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+ jobject result = addLocalReference(ts.self(), newObj);
+ if (newObj != NULL) {
+ JValue unused;
+ dvmCallMethodV(ts.self(), (Method*) methodID, newObj, true, &unused, args);
+ }
+ return result;
+}
+
+static jobject NewObjectA(JNIEnv* env, jclass jclazz, jmethodID methodID, jvalue* args) {
+ ScopedJniThreadState ts(env);
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+
+ if (!canAllocClass(clazz) || (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz))) {
+ assert(dvmCheckException(ts.self()));
+ return NULL;
+ }
+
+ Object* newObj = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+ jobject result = addLocalReference(ts.self(), newObj);
+ if (newObj != NULL) {
+ JValue unused;
+ dvmCallMethodA(ts.self(), (Method*) methodID, newObj, true, &unused, args);
+ }
+ return result;
+}
+
+/*
+ * Returns the class of an object.
+ *
+ * JNI spec says: obj must not be NULL.
+ */
+static jclass GetObjectClass(JNIEnv* env, jobject jobj) {
+ ScopedJniThreadState ts(env);
+
+ assert(jobj != NULL);
+
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj);
+ return (jclass) addLocalReference(ts.self(), (Object*) obj->clazz);
+}
+
+/*
+ * Determine whether "obj" is an instance of "clazz".
+ */
+static jboolean IsInstanceOf(JNIEnv* env, jobject jobj, jclass jclazz) {
+ ScopedJniThreadState ts(env);
+
+ assert(jclazz != NULL);
+ if (jobj == NULL) {
+ return true;
+ }
+
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj);
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+ return dvmInstanceof(obj->clazz, clazz);
+}
+
+/*
+ * Get a method ID for an instance method.
+ *
+ * While Dalvik bytecode has distinct instructions for virtual, super,
+ * static, direct, and interface method invocation, JNI only provides
+ * two functions for acquiring a method ID. This call handles everything
+ * but static methods.
+ *
+ * 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) {
+ ScopedJniThreadState ts(env);
+
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+ if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
+ assert(dvmCheckException(ts.self()));
+ } else if (dvmIsInterfaceClass(clazz)) {
+ Method* meth = dvmFindInterfaceMethodHierByDescriptor(clazz, name, sig);
+ if (meth == NULL) {
+ dvmThrowExceptionFmt(gDvm.exNoSuchMethodError,
+ "no method with name='%s' signature='%s' in interface %s",
+ name, sig, clazz->descriptor);
+ }
+ return (jmethodID) meth;
+ }
+ Method* 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",
+ clazz->descriptor, meth->name, desc);
+ free(desc);
+ }
+ meth = NULL;
+ }
+ if (meth == NULL) {
+ dvmThrowExceptionFmt(gDvm.exNoSuchMethodError,
+ "no method with name='%s' signature='%s' in class %s",
+ name, sig, clazz->descriptor);
+ } else {
+ /*
+ * 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).
+ */
+ assert(dvmIsClassInitialized(meth->clazz) || dvmIsClassInitializing(meth->clazz));
+ }
+ return (jmethodID) meth;
+}
+
+/*
+ * Get a field ID (instance fields).
+ */
+static jfieldID GetFieldID(JNIEnv* env, jclass jclazz, const char* name, const char* sig) {
+ ScopedJniThreadState ts(env);
+
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+
+ if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
+ assert(dvmCheckException(ts.self()));
+ return NULL;
+ }
+
+ jfieldID id = (jfieldID) dvmFindInstanceFieldHier(clazz, name, sig);
+ if (id == NULL) {
+ dvmThrowExceptionFmt(gDvm.exNoSuchFieldError,
+ "no field with name='%s' signature='%s' in class %s",
+ name, sig, clazz->descriptor);
+ }
+ 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) {
+ ScopedJniThreadState ts(env);
+
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+ if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
+ assert(dvmCheckException(ts.self()));
+ return NULL;
+ }
+
+ Method* 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",
+ clazz->descriptor, meth->name, desc);
+ free(desc);
+ }
+ meth = NULL;
+ }
+
+ jmethodID id = (jmethodID) meth;
+ if (id == NULL) {
+ dvmThrowExceptionFmt(gDvm.exNoSuchMethodError,
+ "no static method with name='%s' signature='%s' in class %s",
+ name, sig, clazz->descriptor);
+ }
+ return id;
+}
+
+/*
+ * Get a field ID (static fields).
+ */
+static jfieldID GetStaticFieldID(JNIEnv* env, jclass jclazz, const char* name, const char* sig) {
+ ScopedJniThreadState ts(env);
+
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+ if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
+ assert(dvmCheckException(ts.self()));
+ return NULL;
+ }
+
+ jfieldID id = (jfieldID) dvmFindStaticFieldHier(clazz, name, sig);
+ if (id == NULL) {
+ dvmThrowExceptionFmt(gDvm.exNoSuchFieldError,
+ "no static field with name='%s' signature='%s' in class %s",
+ name, sig, clazz->descriptor);
+ }
+ 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 jclazz, \
+ jfieldID fieldID) \
+ { \
+ UNUSED_PARAMETER(jclazz); \
+ ScopedJniThreadState ts(env); \
+ StaticField* sfield = (StaticField*) fieldID; \
+ _ctype value; \
+ if (dvmIsVolatileField(sfield)) { \
+ if (_isref) { /* only when _ctype==jobject */ \
+ Object* obj = dvmGetStaticFieldObjectVolatile(sfield); \
+ value = (_ctype)(u4)addLocalReference(ts.self(), obj); \
+ } else { \
+ value = (_ctype) dvmGetStaticField##_jname##Volatile(sfield);\
+ } \
+ } else { \
+ if (_isref) { \
+ Object* obj = dvmGetStaticFieldObject(sfield); \
+ value = (_ctype)(u4)addLocalReference(ts.self(), obj); \
+ } else { \
+ value = (_ctype) dvmGetStaticField##_jname(sfield); \
+ } \
+ } \
+ 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, _ctype2, _jname, _isref) \
+ static void SetStatic##_jname##Field(JNIEnv* env, jclass jclazz, \
+ jfieldID fieldID, _ctype value) \
+ { \
+ UNUSED_PARAMETER(jclazz); \
+ ScopedJniThreadState ts(env); \
+ StaticField* sfield = (StaticField*) fieldID; \
+ if (dvmIsVolatileField(sfield)) { \
+ if (_isref) { /* only when _ctype==jobject */ \
+ Object* valObj = dvmDecodeIndirectRef(ts.self(), (jobject)(u4)value); \
+ dvmSetStaticFieldObjectVolatile(sfield, valObj); \
+ } else { \
+ dvmSetStaticField##_jname##Volatile(sfield, (_ctype2)value);\
+ } \
+ } else { \
+ if (_isref) { \
+ Object* valObj = dvmDecodeIndirectRef(ts.self(), (jobject)(u4)value); \
+ dvmSetStaticFieldObject(sfield, valObj); \
+ } else { \
+ dvmSetStaticField##_jname(sfield, (_ctype2)value); \
+ } \
+ } \
+ }
+SET_STATIC_TYPE_FIELD(jobject, Object*, Object, true);
+SET_STATIC_TYPE_FIELD(jboolean, bool, Boolean, false);
+SET_STATIC_TYPE_FIELD(jbyte, s1, Byte, false);
+SET_STATIC_TYPE_FIELD(jchar, u2, Char, false);
+SET_STATIC_TYPE_FIELD(jshort, s2, Short, false);
+SET_STATIC_TYPE_FIELD(jint, s4, Int, false);
+SET_STATIC_TYPE_FIELD(jlong, s8, Long, false);
+SET_STATIC_TYPE_FIELD(jfloat, float, Float, false);
+SET_STATIC_TYPE_FIELD(jdouble, double, Double, false);
+
+/*
+ * 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 jobj, \
+ jfieldID fieldID) \
+ { \
+ ScopedJniThreadState ts(env); \
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj); \
+ InstField* field = (InstField*) fieldID; \
+ _ctype value; \
+ if (dvmIsVolatileField(field)) { \
+ if (_isref) { /* only when _ctype==jobject */ \
+ Object* valObj = \
+ dvmGetFieldObjectVolatile(obj, field->byteOffset); \
+ value = (_ctype)(u4)addLocalReference(ts.self(), valObj); \
+ } else { \
+ value = (_ctype) \
+ dvmGetField##_jname##Volatile(obj, field->byteOffset); \
+ } \
+ } else { \
+ if (_isref) { \
+ Object* valObj = dvmGetFieldObject(obj, field->byteOffset); \
+ value = (_ctype)(u4)addLocalReference(ts.self(), valObj); \
+ } else { \
+ value = (_ctype) dvmGetField##_jname(obj, field->byteOffset);\
+ } \
+ } \
+ 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, _ctype2, _jname, _isref) \
+ static void Set##_jname##Field(JNIEnv* env, jobject jobj, \
+ jfieldID fieldID, _ctype value) \
+ { \
+ ScopedJniThreadState ts(env); \
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj); \
+ InstField* field = (InstField*) fieldID; \
+ if (dvmIsVolatileField(field)) { \
+ if (_isref) { /* only when _ctype==jobject */ \
+ Object* valObj = dvmDecodeIndirectRef(ts.self(), (jobject)(u4)value); \
+ dvmSetFieldObjectVolatile(obj, field->byteOffset, valObj); \
+ } else { \
+ dvmSetField##_jname##Volatile(obj, \
+ field->byteOffset, (_ctype2)value); \
+ } \
+ } else { \
+ if (_isref) { \
+ Object* valObj = dvmDecodeIndirectRef(ts.self(), (jobject)(u4)value); \
+ dvmSetFieldObject(obj, field->byteOffset, valObj); \
+ } else { \
+ dvmSetField##_jname(obj, \
+ field->byteOffset, (_ctype2)value); \
+ } \
+ } \
+ }
+SET_TYPE_FIELD(jobject, Object*, Object, true);
+SET_TYPE_FIELD(jboolean, bool, Boolean, false);
+SET_TYPE_FIELD(jbyte, s1, Byte, false);
+SET_TYPE_FIELD(jchar, u2, Char, false);
+SET_TYPE_FIELD(jshort, s2, Short, false);
+SET_TYPE_FIELD(jint, s4, Int, false);
+SET_TYPE_FIELD(jlong, s8, Long, false);
+SET_TYPE_FIELD(jfloat, float, Float, false);
+SET_TYPE_FIELD(jdouble, double, Double, false);
+
+/*
+ * 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 jobj, \
+ jmethodID methodID, ...) \
+ { \
+ ScopedJniThreadState ts(env); \
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj); \
+ const Method* meth; \
+ va_list args; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod(obj->clazz, (Method*)methodID); \
+ if (meth == NULL) { \
+ return _retfail; \
+ } \
+ va_start(args, methodID); \
+ dvmCallMethodV(ts.self(), meth, obj, true, &result, args); \
+ va_end(args); \
+ if (_isref && !dvmCheckException(ts.self())) \
+ result.l = (Object*)addLocalReference(ts.self(), result.l); \
+ return _retok; \
+ } \
+ static _ctype Call##_jname##MethodV(JNIEnv* env, jobject jobj, \
+ jmethodID methodID, va_list args) \
+ { \
+ ScopedJniThreadState ts(env); \
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj); \
+ const Method* meth; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod(obj->clazz, (Method*)methodID); \
+ if (meth == NULL) { \
+ return _retfail; \
+ } \
+ dvmCallMethodV(ts.self(), meth, obj, true, &result, args); \
+ if (_isref && !dvmCheckException(ts.self())) \
+ result.l = (Object*)addLocalReference(ts.self(), result.l); \
+ return _retok; \
+ } \
+ static _ctype Call##_jname##MethodA(JNIEnv* env, jobject jobj, \
+ jmethodID methodID, jvalue* args) \
+ { \
+ ScopedJniThreadState ts(env); \
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj); \
+ const Method* meth; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod(obj->clazz, (Method*)methodID); \
+ if (meth == NULL) { \
+ return _retfail; \
+ } \
+ dvmCallMethodA(ts.self(), meth, obj, true, &result, args); \
+ if (_isref && !dvmCheckException(ts.self())) \
+ result.l = (Object*)addLocalReference(ts.self(), result.l); \
+ return _retok; \
+ }
+CALL_VIRTUAL(jobject, Object, NULL, (jobject) 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 jobj, \
+ jclass jclazz, jmethodID methodID, ...) \
+ { \
+ ScopedJniThreadState ts(env); \
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj); \
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz); \
+ const Method* meth; \
+ va_list args; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod(clazz, (Method*)methodID); \
+ if (meth == NULL) { \
+ return _retfail; \
+ } \
+ va_start(args, methodID); \
+ dvmCallMethodV(ts.self(), meth, obj, true, &result, args); \
+ if (_isref && !dvmCheckException(ts.self())) \
+ result.l = (Object*)addLocalReference(ts.self(), result.l); \
+ va_end(args); \
+ return _retok; \
+ } \
+ static _ctype CallNonvirtual##_jname##MethodV(JNIEnv* env, jobject jobj,\
+ jclass jclazz, jmethodID methodID, va_list args) \
+ { \
+ ScopedJniThreadState ts(env); \
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj); \
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz); \
+ const Method* meth; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod(clazz, (Method*)methodID); \
+ if (meth == NULL) { \
+ return _retfail; \
+ } \
+ dvmCallMethodV(ts.self(), meth, obj, true, &result, args); \
+ if (_isref && !dvmCheckException(ts.self())) \
+ result.l = (Object*)addLocalReference(ts.self(), result.l); \
+ return _retok; \
+ } \
+ static _ctype CallNonvirtual##_jname##MethodA(JNIEnv* env, jobject jobj,\
+ jclass jclazz, jmethodID methodID, jvalue* args) \
+ { \
+ ScopedJniThreadState ts(env); \
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj); \
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz); \
+ const Method* meth; \
+ JValue result; \
+ meth = dvmGetVirtualizedMethod(clazz, (Method*)methodID); \
+ if (meth == NULL) { \
+ return _retfail; \
+ } \
+ dvmCallMethodA(ts.self(), meth, obj, true, &result, args); \
+ if (_isref && !dvmCheckException(ts.self())) \
+ result.l = (Object*)addLocalReference(ts.self(), result.l); \
+ return _retok; \
+ }
+CALL_NONVIRTUAL(jobject, Object, NULL, (jobject) 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 jclazz, \
+ jmethodID methodID, ...) \
+ { \
+ UNUSED_PARAMETER(jclazz); \
+ ScopedJniThreadState ts(env); \
+ JValue result; \
+ va_list args; \
+ va_start(args, methodID); \
+ dvmCallMethodV(ts.self(), (Method*)methodID, NULL, true, &result, args);\
+ va_end(args); \
+ if (_isref && !dvmCheckException(ts.self())) \
+ result.l = (Object*)addLocalReference(ts.self(), result.l); \
+ return _retok; \
+ } \
+ static _ctype CallStatic##_jname##MethodV(JNIEnv* env, jclass jclazz, \
+ jmethodID methodID, va_list args) \
+ { \
+ UNUSED_PARAMETER(jclazz); \
+ ScopedJniThreadState ts(env); \
+ JValue result; \
+ dvmCallMethodV(ts.self(), (Method*)methodID, NULL, true, &result, args);\
+ if (_isref && !dvmCheckException(ts.self())) \
+ result.l = (Object*)addLocalReference(ts.self(), result.l); \
+ return _retok; \
+ } \
+ static _ctype CallStatic##_jname##MethodA(JNIEnv* env, jclass jclazz, \
+ jmethodID methodID, jvalue* args) \
+ { \
+ UNUSED_PARAMETER(jclazz); \
+ ScopedJniThreadState ts(env); \
+ JValue result; \
+ dvmCallMethodA(ts.self(), (Method*)methodID, NULL, true, &result, args);\
+ if (_isref && !dvmCheckException(ts.self())) \
+ result.l = (Object*)addLocalReference(ts.self(), result.l); \
+ return _retok; \
+ }
+CALL_STATIC(jobject, Object, NULL, (jobject) 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) {
+ ScopedJniThreadState ts(env);
+ StringObject* jstr = dvmCreateStringFromUnicode(unicodeChars, len);
+ if (jstr == NULL) {
+ return NULL;
+ }
+ dvmReleaseTrackedAlloc((Object*) jstr, NULL);
+ return (jstring) addLocalReference(ts.self(), (Object*) jstr);
+}
+
+/*
+ * Return the length of a String in Unicode character units.
+ */
+static jsize GetStringLength(JNIEnv* env, jstring jstr) {
+ ScopedJniThreadState ts(env);
+ StringObject* strObj = (StringObject*) dvmDecodeIndirectRef(ts.self(), jstr);
+ return strObj->length();
+}
+
+
+/*
+ * Get a string's character data.
+ *
+ * The result is guaranteed to be valid until ReleaseStringChars is
+ * called, which means we have to pin it or return a copy.
+ */
+static const jchar* GetStringChars(JNIEnv* env, jstring jstr, jboolean* isCopy) {
+ ScopedJniThreadState ts(env);
+
+ StringObject* strObj = (StringObject*) dvmDecodeIndirectRef(ts.self(), jstr);
+ ArrayObject* strChars = strObj->array();
+
+ pinPrimitiveArray(strChars);
+
+ const u2* data = strObj->chars();
+ if (isCopy != NULL) {
+ *isCopy = JNI_FALSE;
+ }
+ return (jchar*) data;
+}
+
+/*
+ * Release our grip on some characters from a string.
+ */
+static void ReleaseStringChars(JNIEnv* env, jstring jstr, const jchar* chars) {
+ ScopedJniThreadState ts(env);
+ StringObject* strObj = (StringObject*) dvmDecodeIndirectRef(ts.self(), jstr);
+ ArrayObject* strChars = strObj->array();
+ unpinPrimitiveArray(strChars);
+}
+
+/*
+ * 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) {
+ ScopedJniThreadState ts(env);
+ if (bytes == NULL) {
+ return NULL;
+ }
+ /* note newStr could come back NULL on OOM */
+ StringObject* newStr = dvmCreateStringFromCstr(bytes);
+ jstring result = (jstring) addLocalReference(ts.self(), (Object*) newStr);
+ dvmReleaseTrackedAlloc((Object*)newStr, NULL);
+ return result;
+}
+
+/*
+ * Return the length in bytes of the modified UTF-8 form of the string.
+ */
+static jsize GetStringUTFLength(JNIEnv* env, jstring jstr) {
+ ScopedJniThreadState ts(env);
+ StringObject* strObj = (StringObject*) dvmDecodeIndirectRef(ts.self(), jstr);
+ if (strObj == NULL) {
+ return 0; // Should we throw something or assert?
+ }
+ return strObj->utfLength();
+}
+
+/*
+ * 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 jstr, jboolean* isCopy) {
+ ScopedJniThreadState ts(env);
+ if (jstr == NULL) {
+ /* this shouldn't happen; throw NPE? */
+ return NULL;
+ }
+ if (isCopy != NULL) {
+ *isCopy = JNI_TRUE;
+ }
+ StringObject* strObj = (StringObject*) dvmDecodeIndirectRef(ts.self(), jstr);
+ char* newStr = dvmCreateCstrFromString(strObj);
+ if (newStr == NULL) {
+ /* assume memory failure */
+ dvmThrowOutOfMemoryError("native heap string alloc failed");
+ }
+ return newStr;
+}
+
+/*
+ * Release a string created by GetStringUTFChars().
+ */
+static void ReleaseStringUTFChars(JNIEnv* env, jstring jstr, const char* utf) {
+ ScopedJniThreadState ts(env);
+ free((char*) utf);
+}
+
+/*
+ * Return the capacity of the array.
+ */
+static jsize GetArrayLength(JNIEnv* env, jarray jarr) {
+ ScopedJniThreadState ts(env);
+ ArrayObject* arrObj = (ArrayObject*) dvmDecodeIndirectRef(ts.self(), jarr);
+ return arrObj->length;
+}
+
+/*
+ * Construct a new array that holds objects from class "elementClass".
+ */
+static jobjectArray NewObjectArray(JNIEnv* env, jsize length,
+ jclass jelementClass, jobject jinitialElement)
+{
+ ScopedJniThreadState ts(env);
+
+ if (jelementClass == NULL) {
+ dvmThrowNullPointerException("JNI NewObjectArray elementClass == NULL");
+ return NULL;
+ }
+
+ ClassObject* elemClassObj = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jelementClass);
+ ClassObject* arrayClass = dvmFindArrayClassForElement(elemClassObj);
+ ArrayObject* newObj = dvmAllocArrayByClass(arrayClass, length, ALLOC_DEFAULT);
+ if (newObj == NULL) {
+ assert(dvmCheckException(ts.self()));
+ return NULL;
+ }
+ jobjectArray newArray = (jobjectArray) addLocalReference(ts.self(), (Object*) newObj);
+ dvmReleaseTrackedAlloc((Object*) newObj, NULL);
+
+ /*
+ * Initialize the array.
+ */
+ if (jinitialElement != NULL) {
+ Object* initialElement = dvmDecodeIndirectRef(ts.self(), jinitialElement);
+ Object** arrayData = (Object**) (void*) newObj->contents;
+ for (jsize i = 0; i < length; ++i) {
+ arrayData[i] = initialElement;
+ }
+ }
+
+ return newArray;
+}
+
+static bool checkArrayElementBounds(ArrayObject* arrayObj, jsize index) {
+ assert(arrayObj != NULL);
+ if (index < 0 || index >= (int) arrayObj->length) {
+ dvmThrowArrayIndexOutOfBoundsException(arrayObj->length, index);
+ return false;
+ }
+ return true;
+}
+
+/*
+ * 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 jarr, jsize index) {
+ ScopedJniThreadState ts(env);
+
+ ArrayObject* arrayObj = (ArrayObject*) dvmDecodeIndirectRef(ts.self(), jarr);
+ if (!checkArrayElementBounds(arrayObj, index)) {
+ return NULL;
+ }
+
+ Object* value = ((Object**) (void*) arrayObj->contents)[index];
+ return addLocalReference(ts.self(), value);
+}
+
+/*
+ * Set one element of an Object array.
+ */
+static void SetObjectArrayElement(JNIEnv* env, jobjectArray jarr, jsize index, jobject jobj) {
+ ScopedJniThreadState ts(env);
+
+ ArrayObject* arrayObj = (ArrayObject*) dvmDecodeIndirectRef(ts.self(), jarr);
+ if (!checkArrayElementBounds(arrayObj, index)) {
+ return;
+ }
+
+ //LOGV("JNI: set element %d in array %p to %p", index, array, value);
+
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj);
+ dvmSetObjectArrayElement(arrayObj, index, obj);
+}
+
+/*
+ * Create a new array of primitive elements.
+ */
+#define NEW_PRIMITIVE_ARRAY(_artype, _jname, _typechar) \
+ static _artype New##_jname##Array(JNIEnv* env, jsize length) { \
+ ScopedJniThreadState ts(env); \
+ ArrayObject* arrayObj = dvmAllocPrimitiveArray(_typechar, length, ALLOC_DEFAULT); \
+ if (arrayObj == NULL) { \
+ return NULL; \
+ } \
+ _artype result = (_artype) addLocalReference(ts.self(), (Object*) arrayObj); \
+ dvmReleaseTrackedAlloc((Object*) arrayObj, NULL); \
+ return result; \
+ }
+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.
+ *
+ * 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 e.g. 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 jarr, jboolean* isCopy) \
+ { \
+ ScopedJniThreadState ts(env); \
+ ArrayObject* arrayObj = (ArrayObject*) dvmDecodeIndirectRef(ts.self(), jarr); \
+ pinPrimitiveArray(arrayObj); \
+ _ctype* data = (_ctype*) (void*) arrayObj->contents; \
+ if (isCopy != NULL) { \
+ *isCopy = JNI_FALSE; \
+ } \
+ return data; \
+ }
+
+/*
+ * Release the storage locked down by the "get" function.
+ *
+ * The spec says, "'mode' has no effect if 'elems' is not a copy of the
+ * elements in 'array'." They apparently did not anticipate the need to
+ * un-pin memory.
+ */
+#define RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname) \
+ static void Release##_jname##ArrayElements(JNIEnv* env, \
+ _ctype##Array jarr, _ctype* elems, jint mode) \
+ { \
+ UNUSED_PARAMETER(elems); \
+ if (mode != JNI_COMMIT) { \
+ ScopedJniThreadState ts(env); \
+ ArrayObject* arrayObj = (ArrayObject*) dvmDecodeIndirectRef(ts.self(), jarr); \
+ unpinPrimitiveArray(arrayObj); \
+ } \
+ }
+
+static void throwArrayRegionOutOfBounds(ArrayObject* arrayObj, jsize start,
+ jsize len, const char* arrayIdentifier)
+{
+ dvmThrowExceptionFmt(gDvm.exArrayIndexOutOfBoundsException,
+ "%s offset=%d length=%d %s.length=%d",
+ arrayObj->clazz->descriptor, start, len, arrayIdentifier,
+ arrayObj->length);
+}
+
+/*
+ * 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 jarr, jsize start, jsize len, _ctype* buf) \
+ { \
+ ScopedJniThreadState ts(env); \
+ ArrayObject* arrayObj = (ArrayObject*) dvmDecodeIndirectRef(ts.self(), jarr); \
+ _ctype* data = (_ctype*) (void*) arrayObj->contents; \
+ if (start < 0 || len < 0 || start + len > (int) arrayObj->length) { \
+ throwArrayRegionOutOfBounds(arrayObj, start, len, "src"); \
+ } else { \
+ memcpy(buf, data + start, len * sizeof(_ctype)); \
+ } \
+ }
+
+/*
+ * Copy a section of a primitive array from a buffer.
+ */
+#define SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname) \
+ static void Set##_jname##ArrayRegion(JNIEnv* env, \
+ _ctype##Array jarr, jsize start, jsize len, const _ctype* buf) \
+ { \
+ ScopedJniThreadState ts(env); \
+ ArrayObject* arrayObj = (ArrayObject*) dvmDecodeIndirectRef(ts.self(), jarr); \
+ _ctype* data = (_ctype*) (void*) arrayObj->contents; \
+ if (start < 0 || len < 0 || start + len > (int) arrayObj->length) { \
+ throwArrayRegionOutOfBounds(arrayObj, start, len, "dst"); \
+ } else { \
+ memcpy(data + start, buf, len * sizeof(_ctype)); \
+ } \
+ }
+
+/*
+ * 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.
+ *
+ * This can be called multiple times on the same method, allowing the
+ * caller to redefine the method implementation at will.
+ */
+static jint RegisterNatives(JNIEnv* env, jclass jclazz,
+ const JNINativeMethod* methods, jint nMethods)
+{
+ ScopedJniThreadState ts(env);
+
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+
+ if (gDvm.verboseJni) {
+ LOGI("[Registering JNI native methods for class %s]",
+ clazz->descriptor);
+ }
+
+ for (int i = 0; i < nMethods; i++) {
+ if (!dvmRegisterJNIMethod(clazz, methods[i].name,
+ methods[i].signature, methods[i].fnPtr))
+ {
+ return JNI_ERR;
+ }
+ }
+ return JNI_OK;
+}
+
+/*
+ * Un-register all native methods associated with the class.
+ *
+ * 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". In particular,
+ * there is no need to do this during shutdown, and you do not need to do
+ * this before redefining a method implementation with RegisterNatives.
+ *
+ * It's chiefly useful for a native "plugin"-style library that wasn't
+ * loaded with System.loadLibrary() (since there's no way to unload those).
+ * For example, the library could upgrade itself by:
+ *
+ * 1. call UnregisterNatives to unbind the old methods
+ * 2. ensure that no code is still executing inside it (somehow)
+ * 3. dlclose() the library
+ * 4. dlopen() the new library
+ * 5. use RegisterNatives to bind the methods from the new library
+ *
+ * The above can work correctly without the UnregisterNatives call, but
+ * creates a window of opportunity in which somebody might try to call a
+ * method that is pointing at unmapped memory, crashing the VM. In theory
+ * the same guards that prevent dlclose() from unmapping executing code could
+ * prevent that anyway, but with this we can be more thorough and also deal
+ * with methods that only exist in the old or new form of the library (maybe
+ * the lib wants to try the call and catch the UnsatisfiedLinkError).
+ */
+static jint UnregisterNatives(JNIEnv* env, jclass jclazz) {
+ ScopedJniThreadState ts(env);
+
+ ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
+ if (gDvm.verboseJni) {
+ LOGI("[Unregistering JNI native methods for class %s]",
+ clazz->descriptor);
+ }
+ dvmUnregisterJNINativeMethods(clazz);
+ return JNI_OK;
+}
+
+/*
+ * 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 jobj) {
+ ScopedJniThreadState ts(env);
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj);
+ dvmLockObject(ts.self(), obj);
+ trackMonitorEnter(ts.self(), obj);
+ 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 jobj) {
+ ScopedJniThreadState ts(env);
+ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj);
+ bool success = dvmUnlockObject(ts.self(), obj);
+ if (success) {
+ trackMonitorExit(ts.self(), obj);
+ }
+ return success ? JNI_OK : JNI_ERR;
+}
+
+/*
+ * Return the JavaVM interface associated with the current thread.
+ */
+static jint GetJavaVM(JNIEnv* env, JavaVM** vm) {
+ ScopedJniThreadState ts(env);
+ *vm = gDvmJni.jniVm;
+ return (*vm == NULL) ? JNI_ERR : JNI_OK;
+}
+
+/*
+ * Copies "len" Unicode characters, from offset "start".
+ */
+static void GetStringRegion(JNIEnv* env, jstring jstr, jsize start, jsize len, jchar* buf) {
+ ScopedJniThreadState ts(env);
+ StringObject* strObj = (StringObject*) dvmDecodeIndirectRef(ts.self(), jstr);
+ int strLen = strObj->length();
+ if (((start|len) < 0) || (start + len > strLen)) {
+ dvmThrowStringIndexOutOfBoundsExceptionWithRegion(strLen, start, len);
+ return;
+ }
+ memcpy(buf, strObj->chars() + start, len * sizeof(u2));
+}
+
+/*
+ * Translates "len" Unicode characters, from offset "start", into
+ * modified UTF-8 encoding.
+ */
+static void GetStringUTFRegion(JNIEnv* env, jstring jstr, jsize start, jsize len, char* buf) {
+ ScopedJniThreadState ts(env);
+ StringObject* strObj = (StringObject*) dvmDecodeIndirectRef(ts.self(), jstr);
+ int strLen = strObj->length();
+ if (((start|len) < 0) || (start + len > strLen)) {
+ dvmThrowStringIndexOutOfBoundsExceptionWithRegion(strLen, start, len);
+ return;
+ }
+ dvmGetStringUtfRegion(strObj, start, len, buf);
+}
+
+/*
+ * Get a raw pointer to array data.
+ *
+ * The caller is expected to call "release" before doing any JNI calls
+ * or blocking I/O operations.
+ *
+ * We need to pin the memory or block GC.
+ */
+static void* GetPrimitiveArrayCritical(JNIEnv* env, jarray jarr, jboolean* isCopy) {
+ ScopedJniThreadState ts(env);
+ ArrayObject* arrayObj = (ArrayObject*) dvmDecodeIndirectRef(ts.self(), jarr);
+ pinPrimitiveArray(arrayObj);
+ void* data = arrayObj->contents;
+ if (UNLIKELY(isCopy != NULL)) {
+ *isCopy = JNI_FALSE;
+ }
+ return data;
+}
+
+/*
+ * Release an array obtained with GetPrimitiveArrayCritical.
+ */
+static void ReleasePrimitiveArrayCritical(JNIEnv* env, jarray jarr, void* carray, jint mode) {
+ if (mode != JNI_COMMIT) {
+ ScopedJniThreadState ts(env);
+ ArrayObject* arrayObj = (ArrayObject*) dvmDecodeIndirectRef(ts.self(), jarr);
+ unpinPrimitiveArray(arrayObj);
+ }
+}
+
+/*
+ * Like GetStringChars, but with restricted use.
+ */
+static const jchar* GetStringCritical(JNIEnv* env, jstring jstr, jboolean* isCopy) {
+ ScopedJniThreadState ts(env);
+
+ StringObject* strObj = (StringObject*) dvmDecodeIndirectRef(ts.self(), jstr);
+ ArrayObject* strChars = strObj->array();
+
+ pinPrimitiveArray(strChars);
+
+ const u2* data = strObj->chars();
+ if (isCopy != NULL) {
+ *isCopy = JNI_FALSE;
+ }
+ return (jchar*) data;
+}
+
+/*
+ * Like ReleaseStringChars, but with restricted use.
+ */
+static void ReleaseStringCritical(JNIEnv* env, jstring jstr, const jchar* carray) {
+ ScopedJniThreadState ts(env);
+ StringObject* strObj = (StringObject*) dvmDecodeIndirectRef(ts.self(), jstr);
+ ArrayObject* strChars = strObj->array();
+ unpinPrimitiveArray(strChars);
+}
+
+/*
+ * Create a new weak global reference.
+ */
+static jweak NewWeakGlobalRef(JNIEnv* env, jobject jobj) {
+ ScopedJniThreadState ts(env);
+ Object *obj = dvmDecodeIndirectRef(ts.self(), jobj);
+ return (jweak) addWeakGlobalReference(obj);
+}
+
+/*
+ * Delete the specified weak global reference.
+ */
+static void DeleteWeakGlobalRef(JNIEnv* env, jweak wref) {
+ ScopedJniThreadState ts(env);
+ deleteWeakGlobalReference(wref);
+}
+
+/*
+ * Quick check for pending exceptions.
+ *
+ * TODO: we should be able to skip the enter/exit macros here.
+ */
+static jboolean ExceptionCheck(JNIEnv* env) {
+ ScopedJniThreadState ts(env);
+ return dvmCheckException(ts.self());
+}
+
+/*
+ * 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 jobj) {
+ ScopedJniThreadState ts(env);
+ return dvmGetJNIRefType(ts.self(), jobj);
+}
+
+/*
+ * Allocate and return a new java.nio.ByteBuffer for this block of memory.
+ *
+ * "address" may not be NULL, and "capacity" must be > 0. (These are only
+ * verified when CheckJNI is enabled.)
+ */
+static jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) {
+ ScopedJniThreadState ts(env);
+
+ /* create an instance of java.nio.ReadWriteDirectByteBuffer */
+ ClassObject* bufferClazz = gDvm.classJavaNioReadWriteDirectByteBuffer;
+ if (!dvmIsClassInitialized(bufferClazz) && !dvmInitClass(bufferClazz)) {
+ return NULL;
+ }
+ Object* newObj = dvmAllocObject(bufferClazz, ALLOC_DONT_TRACK);
+ if (newObj == NULL) {
+ return NULL;
+ }
+ /* call the constructor */
+ jobject result = addLocalReference(ts.self(), newObj);
+ JValue unused;
+ dvmCallMethod(ts.self(), gDvm.methJavaNioReadWriteDirectByteBuffer_init,
+ newObj, &unused, (jint) address, (jint) capacity);
+ if (dvmGetException(ts.self()) != NULL) {
+ deleteLocalReference(ts.self(), result);
+ return NULL;
+ }
+ return result;
+}
+
+/*
+ * Get the starting address of the buffer for the specified java.nio.Buffer.
+ *
+ * If this is not a "direct" buffer, we return NULL.
+ */
+static void* GetDirectBufferAddress(JNIEnv* env, jobject jbuf) {
+ ScopedJniThreadState ts(env);
+
+ // All Buffer objects have an effectiveDirectAddress field.
+ Object* bufObj = dvmDecodeIndirectRef(ts.self(), jbuf);
+ return (void*) dvmGetFieldInt(bufObj, gDvm.offJavaNioBuffer_effectiveDirectAddress);
+}
+
+/*
+ * Get the capacity of the buffer for the specified java.nio.Buffer.
+ *
+ * Returns -1 if the object is not a direct buffer. (We actually skip
+ * this check, since it's expensive to determine, and just return the
+ * capacity regardless.)
+ */
+static jlong GetDirectBufferCapacity(JNIEnv* env, jobject jbuf) {
+ ScopedJniThreadState ts(env);
+
+ /*
+ * The capacity is always in the Buffer.capacity field.
+ *
+ * (The "check" version should verify that this is actually a Buffer,
+ * but we're not required to do so here.)
+ */
+ Object* buf = dvmDecodeIndirectRef(ts.self(), jbuf);
+ return dvmGetFieldInt(buf, gDvm.offJavaNioBuffer_capacity);
+}
+
+
+/*
+ * ===========================================================================
+ * 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;
+
+ /*
+ * Return immediately if we're already one with the VM.
+ */
+ Thread* 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",
+ (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 = (jobject) dvmGetMainThreadGroup();
+ } else {
+ assert(args->version >= JNI_VERSION_1_2);
+
+ argsCopy.version = args->version;
+ argsCopy.name = args->name;
+ if (args->group != NULL) {
+ argsCopy.group = (jobject) dvmDecodeIndirectRef(NULL, args->group);
+ } else {
+ argsCopy.group = (jobject) dvmGetMainThreadGroup();
+ }
+ }
+
+ bool 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);
+ }
+ return (*env != NULL) ? JNI_OK : JNI_EDETACHED;
+}
+
+/*
+ * 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;
+ if (ext == NULL) {
+ return JNI_ERR;
+ }
+
+ if (gDvm.verboseShutdown) {
+ LOGD("DestroyJavaVM waiting for non-daemon threads to exit");
+ }
+
+ /*
+ * Sleep on a condition variable until it's okay to exit.
+ */
+ Thread* 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)",
+ gDvm.nonDaemonThreadCount);
+ goto shutdown;
+ } else {
+ LOGV("Attached to wait for shutdown in Destroy");
+ }
+ }
+ 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)
+
+ if (gDvm.verboseShutdown) {
+ LOGD("DestroyJavaVM shutting VM down");
+ }
+ 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
+ * ===========================================================================
+ */
+
+/*
+ * 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*) gDvmJni.jniVm;
+
+ //if (self != NULL)
+ // LOGI("Ent CreateJNIEnv: threadid=%d %p", self->threadId, self);
+
+ assert(vm != NULL);
+
+ JNIEnvExt* newEnv = (JNIEnvExt*) calloc(1, sizeof(JNIEnvExt));
+ newEnv->funcTable = &gNativeInterface;
+ 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 (gDvmJni.useCheckJni) {
+ dvmUseCheckedJniEnv(newEnv);
+ }
+
+ ScopedPthreadMutexLock lock(&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;
+
+ //if (self != NULL)
+ // LOGI("Xit CreateJNIEnv: threadid=%d %p", self->threadId, self);
+ return (JNIEnv*) newEnv;
+}
+
+/*
+ * Remove a JNIEnv struct from the list and free it.
+ */
+void dvmDestroyJNIEnv(JNIEnv* env) {
+ if (env == NULL) {
+ return;
+ }
+
+ //LOGI("Ent DestroyJNIEnv: threadid=%d %p", self->threadId, self);
+
+ JNIEnvExt* extEnv = (JNIEnvExt*) env;
+ JavaVMExt* vm = (JavaVMExt*) gDvmJni.jniVm;
+
+ ScopedPthreadMutexLock lock(&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;
+ }
+
+ free(env);
+ //LOGI("Xit DestroyJNIEnv: threadid=%d %p", self->threadId, self);
+}
+
+/*
+ * Enable "checked JNI" after the VM has partially started. This must
+ * only be called in "zygote" mode, when we have one thread running.
+ *
+ * This doesn't attempt to rewrite the JNI call bridge associated with
+ * native methods, so we won't get those checks for any methods that have
+ * already been resolved.
+ */
+void dvmLateEnableCheckedJni() {
+ JNIEnvExt* extEnv = dvmGetJNIEnvForThread();
+ if (extEnv == NULL) {
+ LOGE("dvmLateEnableCheckedJni: thread has no JNIEnv");
+ return;
+ }
+ JavaVMExt* extVm = (JavaVMExt*) gDvmJni.jniVm;
+ assert(extVm != NULL);
+
+ if (!gDvmJni.useCheckJni) {
+ LOGD("Late-enabling CheckJNI");
+ dvmUseCheckedJniVm(extVm);
+ dvmUseCheckedJniEnv(extEnv);
+ } else {
+ LOGD("Not late-enabling CheckJNI (already on)");
+ }
+}
+
+/*
+ * 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 (gDvmJni.jniVm != NULL) {
+ *nVMs = 1;
+ if (bufLen > 0) {
+ *vmBuf++ = gDvmJni.jniVm;
+ }
+ } 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;
+ 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.
+ */
+ JavaVMExt* pVM = (JavaVMExt*) malloc(sizeof(JavaVMExt));
+ memset(pVM, 0, sizeof(JavaVMExt));
+ pVM->funcTable = &gInvokeInterface;
+ pVM->envList = NULL;
+ dvmInitMutex(&pVM->envListLock);
+
+ UniquePtr<const char*[]> argv(new const char*[args->nOptions]);
+ memset(argv.get(), 0, sizeof(char*) * (args->nOptions));
+
+ /*
+ * 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.
+ */
+ int argc = 0;
+ for (int i = 0; i < args->nOptions; i++) {
+ const char* optStr = args->options[i].optionString;
+ if (optStr == NULL) {
+ dvmFprintf(stderr, "ERROR: CreateJavaVM failed: argument %d was NULL\n", i);
+ return JNI_ERR;
+ } else if (strcmp(optStr, "vfprintf") == 0) {
+ gDvm.vfprintfHook = (int (*)(FILE *, const char*, va_list))args->options[i].extraInfo;
+ } else if (strcmp(optStr, "exit") == 0) {
+ gDvm.exitHook = (void (*)(int)) args->options[i].extraInfo;
+ } else if (strcmp(optStr, "abort") == 0) {
+ gDvm.abortHook = (void (*)(void))args->options[i].extraInfo;
+ } else if (strcmp(optStr, "sensitiveThread") == 0) {
+ gDvm.isSensitiveThreadHook = (bool (*)(void))args->options[i].extraInfo;
+ } else if (strcmp(optStr, "-Xcheck:jni") == 0) {
+ gDvmJni.useCheckJni = true;
+ } else if (strncmp(optStr, "-Xjniopts:", 10) == 0) {
+ char* jniOpts = strdup(optStr + 10);
+ size_t jniOptCount = 1;
+ for (char* p = jniOpts; *p != 0; ++p) {
+ if (*p == ',') {
+ ++jniOptCount;
+ *p = 0;
+ }
+ }
+ char* jniOpt = jniOpts;
+ for (size_t i = 0; i < jniOptCount; ++i) {
+ if (strcmp(jniOpt, "warnonly") == 0) {
+ gDvmJni.warnOnly = true;
+ } else if (strcmp(jniOpt, "forcecopy") == 0) {
+ gDvmJni.forceCopy = true;
+ } else if (strcmp(jniOpt, "logThirdPartyJni") == 0) {
+ gDvmJni.logThirdPartyJni = true;
+ } else {
+ dvmFprintf(stderr, "ERROR: CreateJavaVM failed: unknown -Xjniopts option '%s'\n",
+ jniOpt);
+ return JNI_ERR;
+ }
+ jniOpt += strlen(jniOpt) + 1;
+ }
+ free(jniOpts);
+ } else {
+ /* regular option */
+ argv[argc++] = optStr;
+ }
+ }
+
+ if (gDvmJni.useCheckJni) {
+ dvmUseCheckedJniVm(pVM);
+ }
+
+ if (gDvmJni.jniVm != NULL) {
+ dvmFprintf(stderr, "ERROR: Dalvik only supports one VM per process\n");
+ return JNI_ERR;
+ }
+ gDvmJni.jniVm = (JavaVM*) pVM;
+
+ /*
+ * Create a JNIEnv for the 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.
+ */
+ JNIEnvExt* pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);
+
+ /* Initialize VM. */
+ gDvm.initializing = true;
+ std::string status =
+ dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);
+ gDvm.initializing = false;
+
+ if (!status.empty()) {
+ free(pEnv);
+ free(pVM);
+ LOGW("CreateJavaVM failed: %s", status.c_str());
+ return JNI_ERR;
+ }
+
+ /*
+ * Success! Return stuff to caller.
+ */
+ dvmChangeStatus(NULL, THREAD_NATIVE);
+ *p_env = (JNIEnv*) pEnv;
+ *p_vm = (JavaVM*) pVM;
+ LOGV("CreateJavaVM succeeded");
+ return JNI_OK;
+}