diff options
Diffstat (limited to 'vm/Jni.cpp')
-rw-r--r-- | vm/Jni.cpp | 3512 |
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; +} |