/*
 * 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.
 */

/*
 * Support for -Xcheck:jni (the "careful" version of the JNI interfaces).
 *
 * We want to verify types, make sure class and field IDs are valid, and
 * ensure that JNI's semantic expectations are being met.  JNI seems to
 * be relatively lax when it comes to requirements for permission checks,
 * e.g. access to private methods is generally allowed from anywhere.
 *
 * TODO: keep a counter on global Get/Release.  Report a warning if some Gets
 * were not Released.  Do not count explicit Add/DeleteGlobalRef calls (or
 * count them separately, so we can complain if they exceed a certain
 * threshold).
 *
 * TODO: verify that the methodID passed into the Call functions is for
 * a method in the specified class.
 */
#include "Dalvik.h"
#include "JniInternal.h"

#include <zlib.h>

static void abortMaybe(void);       // fwd


/*
 * ===========================================================================
 *      JNI call bridge wrapper
 * ===========================================================================
 */

/*
 * Check the result of a native method call that returns an object reference.
 *
 * The primary goal here is to verify that native code is returning the
 * correct type of object.  If it's declared to return a String but actually
 * returns a byte array, things will fail in strange ways later on.
 *
 * This can be a fairly expensive operation, since we have to look up the
 * return type class by name in method->clazz' class loader.  We take a
 * shortcut here and allow the call to succeed if the descriptor strings
 * match.  This will allow some false-positives when a class is redefined
 * by a class loader, but that's rare enough that it doesn't seem worth
 * testing for.
 *
 * At this point, pResult->l has already been converted to an object pointer.
 */
static void checkCallResultCommon(const u4* args, JValue* pResult,
    const Method* method, Thread* self)
{
    assert(pResult->l != NULL);
    Object* resultObj = (Object*) pResult->l;
    ClassObject* objClazz = resultObj->clazz;

    /*
     * Make sure that pResult->l is an instance of the type this
     * method was expected to return.
     */
    const char* declType = dexProtoGetReturnType(&method->prototype);
    const char* objType = objClazz->descriptor;
    if (strcmp(declType, objType) == 0) {
        /* names match; ignore class loader issues and allow it */
        LOGV("Check %s.%s: %s io %s (FAST-OK)\n",
            method->clazz->descriptor, method->name, objType, declType);
    } else {
        /*
         * Names didn't match.  We need to resolve declType in the context
         * of method->clazz->classLoader, and compare the class objects
         * for equality.
         *
         * Since we're returning an instance of declType, it's safe to
         * assume that it has been loaded and initialized (or, for the case
         * of an array, generated).  However, the current class loader may
         * not be listed as an initiating loader, so we can't just look for
         * it in the loaded-classes list.
         */
        ClassObject* declClazz;

        declClazz = dvmFindClassNoInit(declType, method->clazz->classLoader);
        if (declClazz == NULL) {
            LOGW("JNI WARNING: method declared to return '%s' returned '%s'\n",
                declType, objType);
            LOGW("             failed in %s.%s ('%s' not found)\n",
                method->clazz->descriptor, method->name, declType);
            abortMaybe();
            return;
        }
        if (!dvmInstanceof(objClazz, declClazz)) {
            LOGW("JNI WARNING: method declared to return '%s' returned '%s'\n",
                declType, objType);
            LOGW("             failed in %s.%s\n",
                method->clazz->descriptor, method->name);
            abortMaybe();
            return;
        } else {
            LOGV("Check %s.%s: %s io %s (SLOW-OK)\n",
                method->clazz->descriptor, method->name, objType, declType);
        }
    }
}

/*
 * Determine if we need to check the return type coming out of the call.
 *
 * (We don't do this at the top of checkCallResultCommon() because this is on
 * the critical path for native method calls.)
 */
static inline bool callNeedsCheck(const u4* args, JValue* pResult,
    const Method* method, Thread* self)
{
    return (method->shorty[0] == 'L' && !dvmCheckException(self) &&
            pResult->l != NULL);
}

/*
 * Check a call into native code.
 */
void dvmCheckCallJNIMethod_general(const u4* args, JValue* pResult,
    const Method* method, Thread* self)
{
    dvmCallJNIMethod_general(args, pResult, method, self);
    if (callNeedsCheck(args, pResult, method, self))
        checkCallResultCommon(args, pResult, method, self);
}

/*
 * Check a synchronized call into native code.
 */
void dvmCheckCallJNIMethod_synchronized(const u4* args, JValue* pResult,
    const Method* method, Thread* self)
{
    dvmCallJNIMethod_synchronized(args, pResult, method, self);
    if (callNeedsCheck(args, pResult, method, self))
        checkCallResultCommon(args, pResult, method, self);
}

/*
 * Check a virtual call with no reference arguments (other than "this").
 */
void dvmCheckCallJNIMethod_virtualNoRef(const u4* args, JValue* pResult,
    const Method* method, Thread* self)
{
    dvmCallJNIMethod_virtualNoRef(args, pResult, method, self);
    if (callNeedsCheck(args, pResult, method, self))
        checkCallResultCommon(args, pResult, method, self);
}

/*
 * Check a static call with no reference arguments (other than "clazz").
 */
void dvmCheckCallJNIMethod_staticNoRef(const u4* args, JValue* pResult,
    const Method* method, Thread* self)
{
    dvmCallJNIMethod_staticNoRef(args, pResult, method, self);
    if (callNeedsCheck(args, pResult, method, self))
        checkCallResultCommon(args, pResult, method, self);
}


/*
 * ===========================================================================
 *      JNI function helpers
 * ===========================================================================
 */

#define JNI_ENTER()     dvmChangeStatus(NULL, THREAD_RUNNING)
#define JNI_EXIT()      dvmChangeStatus(NULL, THREAD_NATIVE)

#define BASE_ENV(_env)  (((JNIEnvExt*)_env)->baseFuncTable)
#define BASE_VM(_vm)    (((JavaVMExt*)_vm)->baseFuncTable)

#define kRedundantDirectBufferTest false

/*
 * Flags passed into checkThread().
 */
#define kFlag_Default       0x0000

#define kFlag_CritBad       0x0000      /* calling while in critical is bad */
#define kFlag_CritOkay      0x0001      /* ...okay */
#define kFlag_CritGet       0x0002      /* this is a critical "get" */
#define kFlag_CritRelease   0x0003      /* this is a critical "release" */
#define kFlag_CritMask      0x0003      /* bit mask to get "crit" value */

#define kFlag_ExcepBad      0x0000      /* raised exceptions are bad */
#define kFlag_ExcepOkay     0x0004      /* ...okay */

/*
 * Enter/exit macros for JNI env "check" functions.  These do not change
 * the thread state within the VM.
 */
#define CHECK_ENTER(_env, _flags)                                           \
    do {                                                                    \
        JNI_TRACE(true, true);                                              \
        checkThread(_env, _flags, __FUNCTION__);                            \
    } while(false)

#define CHECK_EXIT(_env)                                                    \
    do { JNI_TRACE(false, true); } while(false)


/*
 * Enter/exit macros for JNI invocation interface "check" functions.  These
 * do not change the thread state within the VM.
 *
 * Set "_hasmeth" to true if we have a valid thread with a method pointer.
 * We won't have one before attaching a thread, after detaching a thread, or
 * after destroying the VM.
 */
#define CHECK_VMENTER(_vm, _hasmeth)                                        \
    do { JNI_TRACE(true, _hasmeth); } while(false)
#define CHECK_VMEXIT(_vm, _hasmeth)                                         \
    do { JNI_TRACE(false, _hasmeth); } while(false)

#define CHECK_FIELD_TYPE(_env, _obj, _fieldid, _prim, _isstatic)            \
    checkFieldType(_env, _obj, _fieldid, _prim, _isstatic, __FUNCTION__)
#define CHECK_INST_FIELD_ID(_env, _obj, _fieldid)                           \
    checkInstanceFieldID(_env, _obj, _fieldid, __FUNCTION__)
#define CHECK_CLASS(_env, _clazz)                                           \
    checkClass(_env, _clazz, __FUNCTION__)
#define CHECK_STRING(_env, _str)                                            \
    checkString(_env, _str, __FUNCTION__)
#define CHECK_UTF_STRING(_env, _str, _nullok)                               \
    checkUtfString(_env, _str, _nullok, __FUNCTION__)
#define CHECK_CLASS_NAME(_env, _str)                                        \
    checkClassName(_env, _str, __FUNCTION__)
#define CHECK_OBJECT(_env, _obj)                                            \
    checkObject(_env, _obj, __FUNCTION__)
#define CHECK_ARRAY(_env, _array)                                           \
    checkArray(_env, _array, __FUNCTION__)
#define CHECK_RELEASE_MODE(_env, _mode)                                     \
    checkReleaseMode(_env, _mode, __FUNCTION__)
#define CHECK_LENGTH_POSITIVE(_env, _length)                                \
    checkLengthPositive(_env, _length, __FUNCTION__)
#define CHECK_NON_NULL(_env, _ptr)                                          \
    checkNonNull(_env, _ptr, __FUNCTION__)
#define CHECK_SIG(_env, _methid, _sigbyte, _isstatic)                       \
    checkSig(_env, _methid, _sigbyte, _isstatic, __FUNCTION__)
#define CHECK_METHOD_ARGS_A(_env, _methid, _args)                           \
    checkMethodArgsA(_env, _methid, _args, __FUNCTION__)
#define CHECK_METHOD_ARGS_V(_env, _methid, _args)                           \
    checkMethodArgsV(_env, _methid, _args, __FUNCTION__)

/*
 * Print trace message when both "checkJNI" and "verbose:jni" are enabled.
 */
#define JNI_TRACE(_entry, _hasmeth)                                         \
    do {                                                                    \
        if (gDvm.verboseJni && (_entry)) {                                  \
            static const char* classDescriptor = "???";                     \
            static const char* methodName = "???";                          \
            if (_hasmeth) {                                                 \
                const Method* meth = dvmGetCurrentJNIMethod();              \
                classDescriptor = meth->clazz->descriptor;                  \
                methodName = meth->name;                                    \
            }                                                               \
            /* use +6 to drop the leading "Check_" */                       \
            LOGI("JNI: %s (from %s.%s)",                                    \
                (__FUNCTION__)+6, classDescriptor, methodName);             \
        }                                                                   \
    } while(false)

/*
 * Log the current location.
 *
 * "func" looks like "Check_DeleteLocalRef"; we drop the "Check_".
 */
static void showLocation(const Method* meth, const char* func)
{
    char* desc = dexProtoCopyMethodDescriptor(&meth->prototype);
    LOGW("             in %s.%s %s (%s)\n",
        meth->clazz->descriptor, meth->name, desc, func + 6);
    free(desc);
}

/*
 * Abort if we are configured to bail out on JNI warnings.
 */
static void abortMaybe(void)
{
    JavaVMExt* vm = (JavaVMExt*) gDvm.vmList;
    if (vm->warnError) {
        dvmDumpThread(dvmThreadSelf(), false);
        dvmAbort();
    }
}

/*
 * Verify that the current thread is (a) attached and (b) associated with
 * this particular instance of JNIEnv.
 *
 * Verify that, if this thread previously made a critical "get" call, we
 * do the corresponding "release" call before we try anything else.
 *
 * Verify that, if an exception has been raised, the native code doesn't
 * make any JNI calls other than the Exception* methods.
 *
 * TODO? if we add support for non-JNI native calls, make sure that the
 * method at the top of the interpreted stack is a JNI method call.  (Or
 * set a flag in the Thread/JNIEnv when the call is made and clear it on
 * return?)
 *
 * NOTE: we are still in THREAD_NATIVE mode.  A GC could happen at any time.
 */
static void checkThread(JNIEnv* env, int flags, const char* func)
{
    JNIEnvExt* threadEnv;
    bool printWarn = false;
    bool printException = false;

    /* get the *correct* JNIEnv by going through our TLS pointer */
    threadEnv = dvmGetJNIEnvForThread();

    /*
     * Verify that the JNIEnv we've been handed matches what we expected
     * to receive.
     */
    if (threadEnv == NULL) {
        LOGE("JNI ERROR: non-VM thread making JNI calls\n");
        // don't set printWarn -- it'll try to call showLocation()
        dvmAbort();
    } else if ((JNIEnvExt*) env != threadEnv) {
        if (dvmThreadSelf()->threadId != threadEnv->envThreadId) {
            LOGE("JNI: threadEnv != thread->env?\n");
            dvmAbort();
        }

        LOGW("JNI WARNING: threadid=%d using env from threadid=%d\n",
            threadEnv->envThreadId, ((JNIEnvExt*)env)->envThreadId);
        printWarn = true;

        /* this is a bad idea -- need to throw as we exit, or abort func */
        //dvmThrowException("Ljava/lang/RuntimeException;",
        //    "invalid use of JNI env ptr");
    } else if (((JNIEnvExt*) env)->self != dvmThreadSelf()) {
        /* correct JNIEnv*; make sure the "self" pointer is correct */
        LOGE("JNI ERROR: env->self != thread-self (%p vs. %p)\n",
            ((JNIEnvExt*) env)->self, dvmThreadSelf());
        dvmAbort();
    }

    /*
     * Check for critical resource misuse.
     */
    switch (flags & kFlag_CritMask) {
    case kFlag_CritOkay:    // okay to call this method
        break;
    case kFlag_CritBad:     // not okay to call
        if (threadEnv->critical) {
            LOGW("JNI WARNING: threadid=%d using JNI after critical get\n",
                threadEnv->envThreadId);
            printWarn = true;
        }
        break;
    case kFlag_CritGet:     // this is a "get" call
        /* don't check here; we allow nested gets */
        threadEnv->critical++;
        break;
    case kFlag_CritRelease: // this is a "release" call
        threadEnv->critical--;
        if (threadEnv->critical < 0) {
            LOGW("JNI WARNING: threadid=%d called too many crit releases\n",
                threadEnv->envThreadId);
            printWarn = true;
        }
        break;
    default:
        assert(false);
    }

    /*
     * Check for raised exceptions.
     */
    if ((flags & kFlag_ExcepOkay) == 0 && dvmCheckException(dvmThreadSelf())) {
        LOGW("JNI WARNING: JNI method called with exception raised\n");
        printWarn = true;
        printException = true;
    }

    if (printWarn)
        showLocation(dvmGetCurrentJNIMethod(), func);
    if (printException) {
        LOGW("Pending exception is:\n");
        dvmLogExceptionStackTrace();
    }
    if (printWarn)
        abortMaybe();
}

/*
 * Verify that the field is of the appropriate type.  If the field has an
 * object type, "obj" is the object we're trying to assign into it.
 *
 * Works for both static and instance fields.
 */
static void checkFieldType(JNIEnv* env, jobject jobj, jfieldID fieldID,
    PrimitiveType prim, bool isStatic, const char* func)
{
    static const char* primNameList[] = {
        "Object/Array", "boolean", "char", "float", "double",
        "byte", "short", "int", "long", "void"
    };
    const char** primNames = &primNameList[1];      // shift up for PRIM_NOT
    Field* field = (Field*) fieldID;
    bool printWarn = false;

    if (fieldID == NULL) {
        LOGE("JNI ERROR: null field ID\n");
        abortMaybe();
    }

    if (field->signature[0] == 'L' || field->signature[0] == '[') {
        Object* obj = dvmDecodeIndirectRef(env, jobj);
        if (obj != NULL) {
            ClassObject* fieldClass =
                dvmFindLoadedClass(field->signature);
            ClassObject* objClass = obj->clazz;

            assert(fieldClass != NULL);
            assert(objClass != NULL);

            if (!dvmInstanceof(objClass, fieldClass)) {
                LOGW("JNI WARNING: field '%s' with type '%s' set with wrong type (%s)\n",
                    field->name, field->signature, objClass->descriptor);
                printWarn = true;
            }
        }
    } else if (field->signature[0] != PRIM_TYPE_TO_LETTER[prim]) {
        LOGW("JNI WARNING: field '%s' with type '%s' set with wrong type (%s)\n",
            field->name, field->signature, primNames[prim]);
        printWarn = true;
    } else if (isStatic && !dvmIsStaticField(field)) {
        if (isStatic)
            LOGW("JNI WARNING: accessing non-static field %s as static\n",
                field->name);
        else
            LOGW("JNI WARNING: accessing static field %s as non-static\n",
                field->name);
        printWarn = true;
    }

    if (printWarn) {
        showLocation(dvmGetCurrentJNIMethod(), func);
        abortMaybe();
    }
}

/*
 * Verify that "jobj" is a valid object, and that it's an object that JNI
 * is allowed to know about.  We allow NULL references.
 *
 * Must be in "running" mode before calling here.
 */
static void checkObject0(JNIEnv* env, jobject jobj, const char* func)
{
    UNUSED_PARAMETER(env);
    bool printWarn = false;

    if (jobj == NULL)
        return;

    if (dvmIsWeakGlobalRef(jobj)) {
        /*
         * Normalize and continue.  This will tell us if the PhantomReference
         * object is valid.
         */
        jobj = dvmNormalizeWeakGlobalRef((jweak) jobj);
    }

    if (dvmGetJNIRefType(env, jobj) == JNIInvalidRefType) {
        LOGW("JNI WARNING: %p is not a valid JNI reference\n", jobj);
        printWarn = true;
    } else {
        Object* obj = dvmDecodeIndirectRef(env, jobj);

        if (obj == NULL || !dvmIsValidObject(obj)) {
            LOGW("JNI WARNING: native code passing in bad object %p %p (%s)\n",
                jobj, obj, func);
            printWarn = true;
        }
    }

    if (printWarn) {
        showLocation(dvmGetCurrentJNIMethod(), func);
        abortMaybe();
    }
}

/*
 * Verify that "jobj" is a valid object, and that it's an object that JNI
 * is allowed to know about.  We allow NULL references.
 *
 * Switches to "running" mode before performing checks.
 */
static void checkObject(JNIEnv* env, jobject jobj, const char* func)
{
    JNI_ENTER();
    checkObject0(env, jobj, func);
    JNI_EXIT();
}

/*
 * Verify that "clazz" actually points to a class object.  (Also performs
 * checkObject.)
 *
 * We probably don't need to identify where we're being called from,
 * because the VM is most likely about to crash and leave a core dump
 * if something is wrong.
 *
 * Because we're looking at an object on the GC heap, we have to switch
 * to "running" mode before doing the checks.
 */
static void checkClass(JNIEnv* env, jclass jclazz, const char* func)
{
    JNI_ENTER();
    bool printWarn = false;

    ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz);

    if (clazz == NULL) {
        LOGW("JNI WARNING: received null jclass\n");
        printWarn = true;
    } else if (!dvmIsValidObject((Object*) clazz)) {
        LOGW("JNI WARNING: jclass points to invalid object %p\n", clazz);
        printWarn = true;
    } else if (clazz->obj.clazz != gDvm.classJavaLangClass) {
        LOGW("JNI WARNING: jclass does not point to class object (%p - %s)\n",
            jclazz, clazz->descriptor);
        printWarn = true;
    }
    JNI_EXIT();

    if (printWarn)
        abortMaybe();
    else
        checkObject(env, jclazz, func);
}

/*
 * Verify that "str" is non-NULL and points to a String object.
 *
 * Since we're dealing with objects, switch to "running" mode.
 */
static void checkString(JNIEnv* env, jstring jstr, const char* func)
{
    JNI_ENTER();
    bool printWarn = false;

    Object* obj = dvmDecodeIndirectRef(env, jstr);

    if (obj == NULL) {
        LOGW("JNI WARNING: received null jstring (%s)\n", func);
        printWarn = true;
    } else if (obj->clazz != gDvm.classJavaLangString) {
        /*
         * TODO: we probably should test dvmIsValidObject first, because
         * this will crash if "obj" is non-null but pointing to an invalid
         * memory region.  However, the "is valid" test is a little slow,
         * we're doing it again over in checkObject().
         */
        if (dvmIsValidObject(obj))
            LOGW("JNI WARNING: jstring %p points to non-string object (%s)\n",
                jstr, func);
        else
            LOGW("JNI WARNING: jstring %p is bogus (%s)\n", jstr, func);
        printWarn = true;
    }
    JNI_EXIT();

    if (printWarn)
        abortMaybe();
    else
        checkObject(env, jstr, func);
}

/*
 * Verify that "bytes" points to valid "modified UTF-8" data.
 */
static void checkUtfString(JNIEnv* env, const char* bytes, bool nullOk,
    const char* func)
{
    const char* origBytes = bytes;

    if (bytes == NULL) {
        if (!nullOk) {
            LOGW("JNI WARNING: unexpectedly null UTF string\n");
            goto fail;
        }

        return;
    }

    while (*bytes != '\0') {
        u1 utf8 = *(bytes++);
        // Switch on the high four bits.
        switch (utf8 >> 4) {
            case 0x00:
            case 0x01:
            case 0x02:
            case 0x03:
            case 0x04:
            case 0x05:
            case 0x06:
            case 0x07: {
                // Bit pattern 0xxx. No need for any extra bytes.
                break;
            }
            case 0x08:
            case 0x09:
            case 0x0a:
            case 0x0b:
            case 0x0f: {
                /*
                 * Bit pattern 10xx or 1111, which are illegal start bytes.
                 * Note: 1111 is valid for normal UTF-8, but not the
                 * modified UTF-8 used here.
                 */
                LOGW("JNI WARNING: illegal start byte 0x%x\n", utf8);
                goto fail;
            }
            case 0x0e: {
                // Bit pattern 1110, so there are two additional bytes.
                utf8 = *(bytes++);
                if ((utf8 & 0xc0) != 0x80) {
                    LOGW("JNI WARNING: illegal continuation byte 0x%x\n", utf8);
                    goto fail;
                }
                // Fall through to take care of the final byte.
            }
            case 0x0c:
            case 0x0d: {
                // Bit pattern 110x, so there is one additional byte.
                utf8 = *(bytes++);
                if ((utf8 & 0xc0) != 0x80) {
                    LOGW("JNI WARNING: illegal continuation byte 0x%x\n", utf8);
                    goto fail;
                }
                break;
            }
        }
    }

    return;

fail:
    LOGW("             string: '%s'\n", origBytes);
    showLocation(dvmGetCurrentJNIMethod(), func);
    abortMaybe();
}

/*
 * In some circumstances the VM will screen class names, but it doesn't
 * for class lookup.  When things get bounced through a class loader, they
 * can actually get normalized a couple of times; as a result, passing in
 * a class name like "java.lang.Thread" instead of "java/lang/Thread" will
 * work in some circumstances.
 *
 * This is incorrect and could cause strange behavior or compatibility
 * problems, so we want to screen that out here.
 *
 * We expect "full-qualified" class names, like "java/lang/Thread" or
 * "[Ljava/lang/Object;".
 */
static void checkClassName(JNIEnv* env, const char* className, const char* func)
{
    const char* cp;

    /* quick check for illegal chars */
    cp = className;
    while (*cp != '\0') {
        if (*cp == '.')     /* catch "java.lang.String" */
            goto fail;
        cp++;
    }
    if (*(cp-1) == ';' && *className == 'L')
        goto fail;         /* catch "Ljava/lang/String;" */

    // TODO: need a more rigorous check here

    return;

fail:
    LOGW("JNI WARNING: illegal class name '%s' (%s)\n", className, func);
    LOGW("             (should be formed like 'java/lang/String')\n");
    abortMaybe();
}

/*
 * Verify that "array" is non-NULL and points to an Array object.
 *
 * Since we're dealing with objects, switch to "running" mode.
 */
static void checkArray(JNIEnv* env, jarray jarr, const char* func)
{
    JNI_ENTER();
    bool printWarn = false;

    Object* obj = dvmDecodeIndirectRef(env, jarr);

    if (obj == NULL) {
        LOGW("JNI WARNING: received null array (%s)\n", func);
        printWarn = true;
    } else if (obj->clazz->descriptor[0] != '[') {
        if (dvmIsValidObject(obj))
            LOGW("JNI WARNING: jarray %p points to non-array object (%s)\n",
                jarr, obj->clazz->descriptor);
        else
            LOGW("JNI WARNING: jarray %p is bogus\n", jarr);
        printWarn = true;
    }

    JNI_EXIT();

    if (printWarn)
        abortMaybe();
    else
        checkObject(env, jarr, func);
}

/*
 * Verify that the "mode" argument passed to a primitive array Release
 * function is one of the valid values.
 */
static void checkReleaseMode(JNIEnv* env, jint mode, const char* func)
{
    if (mode != 0 && mode != JNI_COMMIT && mode != JNI_ABORT) {
        LOGW("JNI WARNING: bad value for mode (%d) (%s)\n", mode, func);
        abortMaybe();
    }
}

/*
 * Verify that the length argument to array-creation calls is >= 0.
 */
static void checkLengthPositive(JNIEnv* env, jsize length, const char* func)
{
    if (length < 0) {
        LOGW("JNI WARNING: negative length for array allocation (%s)\n", func);
        abortMaybe();
    }
}

/*
 * Verify that the pointer value is non-NULL.
 */
static void checkNonNull(JNIEnv* env, const void* ptr, const char* func)
{
    if (ptr == NULL) {
        LOGW("JNI WARNING: invalid null pointer (%s)\n", func);
        abortMaybe();
    }
}

/*
 * Verify that the method's return type matches the type of call.
 *
 * "expectedSigByte" will be 'L' for all objects, including arrays.
 */
static void checkSig(JNIEnv* env, jmethodID methodID, char expectedSigByte,
    bool isStatic, const char* func)
{
    const Method* meth = (const Method*) methodID;
    bool printWarn = false;

    if (expectedSigByte != meth->shorty[0]) {
        LOGW("JNI WARNING: expected return type '%c'\n", expectedSigByte);
        printWarn = true;
    } else if (isStatic && !dvmIsStaticMethod(meth)) {
        if (isStatic)
            LOGW("JNI WARNING: calling non-static method with static call\n");
        else
            LOGW("JNI WARNING: calling static method with non-static call\n");
        printWarn = true;
    }

    if (printWarn) {
        char* desc = dexProtoCopyMethodDescriptor(&meth->prototype);
        LOGW("             calling %s.%s %s\n",
            meth->clazz->descriptor, meth->name, desc);
        free(desc);
        showLocation(dvmGetCurrentJNIMethod(), func);
        abortMaybe();
    }
}

/*
 * Verify that this static field ID is valid for this class.
 */
static void checkStaticFieldID(JNIEnv* env, jclass jclazz, jfieldID fieldID)
{
    ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz);
    StaticField* base = clazz->sfields;
    int fieldCount = clazz->sfieldCount;

    if ((StaticField*) fieldID < base ||
        (StaticField*) fieldID >= base + fieldCount)
    {
        LOGW("JNI WARNING: static fieldID %p not valid for class %s\n",
            fieldID, clazz->descriptor);
        LOGW("             base=%p count=%d\n", base, fieldCount);
        abortMaybe();
    }
}

/*
 * Verify that this instance field ID is valid for this object.
 */
static void checkInstanceFieldID(JNIEnv* env, jobject jobj, jfieldID fieldID,
    const char* func)
{
    JNI_ENTER();

    if (jobj == NULL) {
        LOGW("JNI WARNING: invalid null object (%s)\n", func);
        abortMaybe();
        goto bail;
    }

    Object* obj = dvmDecodeIndirectRef(env, jobj);
    ClassObject* clazz = obj->clazz;

    /*
     * Check this class and all of its superclasses for a matching field.
     * Don't need to scan interfaces.
     */
    while (clazz != NULL) {
        if ((InstField*) fieldID >= clazz->ifields &&
            (InstField*) fieldID < clazz->ifields + clazz->ifieldCount)
        {
            goto bail;
        }

        clazz = clazz->super;
    }

    LOGW("JNI WARNING: inst fieldID %p not valid for class %s\n",
        fieldID, obj->clazz->descriptor);
    abortMaybe();

bail:
    JNI_EXIT();
}

/*
 * Verify that the reference arguments being passed in are appropriate for
 * this method.
 *
 * At a minimum we want to make sure that the argument is a valid
 * reference.  We can also do a class lookup on the method signature
 * and verify that the object is an instance of the appropriate class,
 * but that's more expensive.
 *
 * The basic tests are redundant when indirect references are enabled,
 * since reference arguments must always be converted explicitly.  An
 * instanceof test would not be redundant, but we're not doing that at
 * this time.
 */
static void checkMethodArgsV(JNIEnv* env, jmethodID methodID, va_list args,
    const char* func)
{
#ifndef USE_INDIRECT_REF
    JNI_ENTER();

    const Method* meth = (const Method*) methodID;
    const char* desc = meth->shorty;
    ClassObject* clazz;

    LOGV("V-checking %s.%s:%s...\n", meth->clazz->descriptor, meth->name, desc);

    while (*++desc != '\0') {       /* pre-incr to skip return type */
        switch (*desc) {
        case 'L':
            {     /* 'shorty' descr uses L for all refs, incl array */
                jobject argObj = va_arg(args, jobject);
                checkObject0(env, argObj, func);
            }
            break;
        case 'D':       /* 8-byte double */
        case 'J':       /* 8-byte long */
        case 'F':       /* floats normalized to doubles */
            (void) va_arg(args, u8);
            break;
        default:        /* Z B C S I -- all passed as 32-bit integers */
            (void) va_arg(args, u4);
            break;
        }
    }

bail:
    JNI_EXIT();
#endif
}

/*
 * Same purpose as checkMethodArgsV, but with arguments in an array of
 * jvalue structs.
 */
static void checkMethodArgsA(JNIEnv* env, jmethodID methodID, jvalue* args,
    const char* func)
{
#ifndef USE_INDIRECT_REF
    JNI_ENTER();

    const Method* meth = (const Method*) methodID;
    const char* desc = meth->shorty;
    ClassObject* clazz;
    int idx = 0;

    LOGV("A-checking %s.%s:%s...\n", meth->clazz->descriptor, meth->name, desc);

    while (*++desc != '\0') {       /* pre-incr to skip return type */
        if (*desc == 'L') {
            jobject argObj = args[idx].l;
            checkObject0(env, argObj, func);
        }

        idx++;
    }

bail:
    JNI_EXIT();
#endif
}


/*
 * ===========================================================================
 *      Guarded arrays
 * ===========================================================================
 */

#define kGuardLen       512         /* must be multiple of 2 */
#define kGuardPattern   0xd5e3      /* uncommon values; d5e3d5e3 invalid addr */
#define kGuardMagic     0xffd5aa96
#define kGuardExtra     sizeof(GuardExtra)

/* this gets tucked in at the start of the buffer; struct size must be even */
typedef struct GuardExtra {
    u4          magic;
    uLong       adler;
    size_t      originalLen;
    const void* originalPtr;
} GuardExtra;

/* find the GuardExtra given the pointer into the "live" data */
inline static GuardExtra* getGuardExtra(const void* dataBuf)
{
    u1* fullBuf = ((u1*) dataBuf) - kGuardLen / 2;
    return (GuardExtra*) fullBuf;
}

/*
 * Create an oversized buffer to hold the contents of "buf".  Copy it in,
 * filling in the area around it with guard data.
 *
 * We use a 16-bit pattern to make a rogue memset less likely to elude us.
 */
static void* createGuardedCopy(const void* buf, size_t len, bool modOkay)
{
    GuardExtra* pExtra;
    size_t newLen = (len + kGuardLen +1) & ~0x01;
    u1* newBuf;
    u2* pat;
    int i;

    newBuf = (u1*)malloc(newLen);
    if (newBuf == NULL) {
        LOGE("createGuardedCopy failed on alloc of %d bytes\n", newLen);
        dvmAbort();
    }

    /* fill it in with a pattern */
    pat = (u2*) newBuf;
    for (i = 0; i < (int)newLen / 2; i++)
        *pat++ = kGuardPattern;

    /* copy the data in; note "len" could be zero */
    memcpy(newBuf + kGuardLen / 2, buf, len);

    /* if modification is not expected, grab a checksum */
    uLong adler = 0;
    if (!modOkay) {
        adler = adler32(0L, Z_NULL, 0);
        adler = adler32(adler, buf, len);
        *(uLong*)newBuf = adler;
    }

    pExtra = (GuardExtra*) newBuf;
    pExtra->magic = kGuardMagic;
    pExtra->adler = adler;
    pExtra->originalPtr = buf;
    pExtra->originalLen = len;

    return newBuf + kGuardLen / 2;
}

/*
 * Verify the guard area and, if "modOkay" is false, that the data itself
 * has not been altered.
 *
 * The caller has already checked that "dataBuf" is non-NULL.
 */
static bool checkGuardedCopy(const void* dataBuf, bool modOkay)
{
    static const u4 kMagicCmp = kGuardMagic;
    const u1* fullBuf = ((const u1*) dataBuf) - kGuardLen / 2;
    const GuardExtra* pExtra = getGuardExtra(dataBuf);
    size_t len;
    const u2* pat;
    int i;

    /*
     * Before we do anything with "pExtra", check the magic number.  We
     * do the check with memcmp rather than "==" in case the pointer is
     * unaligned.  If it points to completely bogus memory we're going
     * to crash, but there's no easy way around that.
     */
    if (memcmp(&pExtra->magic, &kMagicCmp, 4) != 0) {
        u1 buf[4];
        memcpy(buf, &pExtra->magic, 4);
        LOGE("JNI: guard magic does not match (found 0x%02x%02x%02x%02x) "
             "-- incorrect data pointer %p?\n",
            buf[3], buf[2], buf[1], buf[0], dataBuf); /* assume little endian */
        return false;
    }

    len = pExtra->originalLen;

    /* check bottom half of guard; skip over optional checksum storage */
    pat = (u2*) fullBuf;
    for (i = kGuardExtra / 2; i < (int) (kGuardLen / 2 - kGuardExtra) / 2; i++)
    {
        if (pat[i] != kGuardPattern) {
            LOGE("JNI: guard pattern(1) disturbed at %p + %d\n",
                fullBuf, i*2);
            return false;
        }
    }

    int offset = kGuardLen / 2 + len;
    if (offset & 0x01) {
        /* odd byte; expected value depends on endian-ness of host */
        const u2 patSample = kGuardPattern;
        if (fullBuf[offset] != ((const u1*) &patSample)[1]) {
            LOGE("JNI: guard pattern disturbed in odd byte after %p "
                 "(+%d) 0x%02x 0x%02x\n",
                fullBuf, offset, fullBuf[offset], ((const u1*) &patSample)[1]);
            return false;
        }
        offset++;
    }

    /* check top half of guard */
    pat = (u2*) (fullBuf + offset);
    for (i = 0; i < kGuardLen / 4; i++) {
        if (pat[i] != kGuardPattern) {
            LOGE("JNI: guard pattern(2) disturbed at %p + %d\n",
                fullBuf, offset + i*2);
            return false;
        }
    }

    /*
     * If modification is not expected, verify checksum.  Strictly speaking
     * this is wrong: if we told the client that we made a copy, there's no
     * reason they can't alter the buffer.
     */
    if (!modOkay) {
        uLong adler = adler32(0L, Z_NULL, 0);
        adler = adler32(adler, dataBuf, len);
        if (pExtra->adler != adler) {
            LOGE("JNI: buffer modified (0x%08lx vs 0x%08lx) at addr %p\n",
                pExtra->adler, adler, dataBuf);
            return false;
        }
    }

    return true;
}

/*
 * Free up the guard buffer, scrub it, and return the original pointer.
 */
static void* freeGuardedCopy(void* dataBuf)
{
    u1* fullBuf = ((u1*) dataBuf) - kGuardLen / 2;
    const GuardExtra* pExtra = getGuardExtra(dataBuf);
    void* originalPtr = (void*) pExtra->originalPtr;
    size_t len = pExtra->originalLen;

    memset(dataBuf, 0xdd, len);
    free(fullBuf);
    return originalPtr;
}

/*
 * Just pull out the original pointer.
 */
static void* getGuardedCopyOriginalPtr(const void* dataBuf)
{
    const GuardExtra* pExtra = getGuardExtra(dataBuf);
    return (void*) pExtra->originalPtr;
}

/*
 * Grab the data length.
 */
static size_t getGuardedCopyOriginalLen(const void* dataBuf)
{
    const GuardExtra* pExtra = getGuardExtra(dataBuf);
    return pExtra->originalLen;
}

/*
 * Return the width, in bytes, of a primitive type.
 */
static int dvmPrimitiveTypeWidth(PrimitiveType primType)
{
    static const int lengths[PRIM_MAX] = {
        1,      // boolean
        2,      // char
        4,      // float
        8,      // double
        1,      // byte
        2,      // short
        4,      // int
        8,      // long
        -1,     // void
    };
    assert(primType >= 0 && primType < PRIM_MAX);
    return lengths[primType];
}

/*
 * Create a guarded copy of a primitive array.  Modifications to the copied
 * data are allowed.  Returns a pointer to the copied data.
 */
static void* createGuardedPACopy(JNIEnv* env, const jarray jarr,
    jboolean* isCopy)
{
    ArrayObject* arrObj = (ArrayObject*) dvmDecodeIndirectRef(env, jarr);
    PrimitiveType primType = arrObj->obj.clazz->elementClass->primitiveType;
    int len = arrObj->length * dvmPrimitiveTypeWidth(primType);
    void* result;

    result = createGuardedCopy(arrObj->contents, len, true);

    if (isCopy != NULL)
        *isCopy = JNI_TRUE;

    return result;
}

/*
 * Perform the array "release" operation, which may or may not copy data
 * back into the VM, and may or may not release the underlying storage.
 */
static void* releaseGuardedPACopy(JNIEnv* env, jarray jarr, void* dataBuf,
    int mode)
{
    ArrayObject* arrObj = (ArrayObject*) dvmDecodeIndirectRef(env, jarr);
    PrimitiveType primType = arrObj->obj.clazz->elementClass->primitiveType;
    //int len = array->length * dvmPrimitiveTypeWidth(primType);
    bool release, copyBack;
    u1* result;

    if (!checkGuardedCopy(dataBuf, true)) {
        LOGE("JNI: failed guarded copy check in releaseGuardedPACopy\n");
        abortMaybe();
        return NULL;
    }

    switch (mode) {
    case 0:
        release = copyBack = true;
        break;
    case JNI_ABORT:
        release = true;
        copyBack = false;
        break;
    case JNI_COMMIT:
        release = false;
        copyBack = true;
        break;
    default:
        LOGE("JNI: bad release mode %d\n", mode);
        dvmAbort();
        return NULL;
    }

    if (copyBack) {
        size_t len = getGuardedCopyOriginalLen(dataBuf);
        memcpy(arrObj->contents, dataBuf, len);
    }

    if (release) {
        result = (u1*) freeGuardedCopy(dataBuf);
    } else {
        result = (u1*) getGuardedCopyOriginalPtr(dataBuf);
    }

    /* pointer is to the array contents; back up to the array object */
    result -= offsetof(ArrayObject, contents);

    return result;
}


/*
 * ===========================================================================
 *      JNI functions
 * ===========================================================================
 */

static jint Check_GetVersion(JNIEnv* env)
{
    CHECK_ENTER(env, kFlag_Default);
    jint result;
    result = BASE_ENV(env)->GetVersion(env);
    CHECK_EXIT(env);
    return result;
}

static jclass Check_DefineClass(JNIEnv* env, const char* name, jobject loader,
    const jbyte* buf, jsize bufLen)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, loader);
    CHECK_UTF_STRING(env, name, false);
    CHECK_CLASS_NAME(env, name);
    jclass result;
    result = BASE_ENV(env)->DefineClass(env, name, loader, buf, bufLen);
    CHECK_EXIT(env);
    return result;
}

static jclass Check_FindClass(JNIEnv* env, const char* name)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_UTF_STRING(env, name, false);
    CHECK_CLASS_NAME(env, name);
    jclass result;
    result = BASE_ENV(env)->FindClass(env, name);
    CHECK_EXIT(env);
    return result;
}

static jclass Check_GetSuperclass(JNIEnv* env, jclass clazz)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    jclass result;
    result = BASE_ENV(env)->GetSuperclass(env, clazz);
    CHECK_EXIT(env);
    return result;
}

static jboolean Check_IsAssignableFrom(JNIEnv* env, jclass clazz1,
    jclass clazz2)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz1);
    CHECK_CLASS(env, clazz2);
    jboolean result;
    result = BASE_ENV(env)->IsAssignableFrom(env, clazz1, clazz2);
    CHECK_EXIT(env);
    return result;
}

static jmethodID Check_FromReflectedMethod(JNIEnv* env, jobject method)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, method);
    jmethodID result;
    result = BASE_ENV(env)->FromReflectedMethod(env, method);
    CHECK_EXIT(env);
    return result;
}

static jfieldID Check_FromReflectedField(JNIEnv* env, jobject field)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, field);
    jfieldID result;
    result = BASE_ENV(env)->FromReflectedField(env, field);
    CHECK_EXIT(env);
    return result;
}

static jobject Check_ToReflectedMethod(JNIEnv* env, jclass cls,
    jmethodID methodID, jboolean isStatic)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, cls);
    jobject result;
    result = BASE_ENV(env)->ToReflectedMethod(env, cls, methodID, isStatic);
    CHECK_EXIT(env);
    return result;
}

static jobject Check_ToReflectedField(JNIEnv* env, jclass cls, jfieldID fieldID,
    jboolean isStatic)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, cls);
    jobject result;
    result = BASE_ENV(env)->ToReflectedField(env, cls, fieldID, isStatic);
    CHECK_EXIT(env);
    return result;
}

static jint Check_Throw(JNIEnv* env, jthrowable obj)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, obj);
    jint result;
    result = BASE_ENV(env)->Throw(env, obj);
    CHECK_EXIT(env);
    return result;
}

static jint Check_ThrowNew(JNIEnv* env, jclass clazz, const char* message)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    CHECK_UTF_STRING(env, message, true);
    jint result;
    result = BASE_ENV(env)->ThrowNew(env, clazz, message);
    CHECK_EXIT(env);
    return result;
}

static jthrowable Check_ExceptionOccurred(JNIEnv* env)
{
    CHECK_ENTER(env, kFlag_ExcepOkay);
    jthrowable result;
    result = BASE_ENV(env)->ExceptionOccurred(env);
    CHECK_EXIT(env);
    return result;
}

static void Check_ExceptionDescribe(JNIEnv* env)
{
    CHECK_ENTER(env, kFlag_ExcepOkay);
    BASE_ENV(env)->ExceptionDescribe(env);
    CHECK_EXIT(env);
}

static void Check_ExceptionClear(JNIEnv* env)
{
    CHECK_ENTER(env, kFlag_ExcepOkay);
    BASE_ENV(env)->ExceptionClear(env);
    CHECK_EXIT(env);
}

static void Check_FatalError(JNIEnv* env, const char* msg)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_UTF_STRING(env, msg, true);
    BASE_ENV(env)->FatalError(env, msg);
    CHECK_EXIT(env);
}

static jint Check_PushLocalFrame(JNIEnv* env, jint capacity)
{
    CHECK_ENTER(env, kFlag_Default | kFlag_ExcepOkay);
    jint result;
    result = BASE_ENV(env)->PushLocalFrame(env, capacity);
    CHECK_EXIT(env);
    return result;
}

static jobject Check_PopLocalFrame(JNIEnv* env, jobject res)
{
    CHECK_ENTER(env, kFlag_Default | kFlag_ExcepOkay);
    CHECK_OBJECT(env, res);
    jobject result;
    result = BASE_ENV(env)->PopLocalFrame(env, res);
    CHECK_EXIT(env);
    return result;
}

static jobject Check_NewGlobalRef(JNIEnv* env, jobject obj)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, obj);
    jobject result;
    result = BASE_ENV(env)->NewGlobalRef(env, obj);
    CHECK_EXIT(env);
    return result;
}

static void Check_DeleteGlobalRef(JNIEnv* env, jobject globalRef)
{
    CHECK_ENTER(env, kFlag_Default | kFlag_ExcepOkay);
    CHECK_OBJECT(env, globalRef);
#ifdef USE_INDIRECT_REF
    if (globalRef != NULL &&
        dvmGetJNIRefType(env, globalRef) != JNIGlobalRefType)
    {
        LOGW("JNI WARNING: DeleteGlobalRef on non-global %p (type=%d)\n",
            globalRef, dvmGetJNIRefType(env, globalRef));
        abortMaybe();
    } else
#endif
    {
        BASE_ENV(env)->DeleteGlobalRef(env, globalRef);
    }
    CHECK_EXIT(env);
}

static jobject Check_NewLocalRef(JNIEnv* env, jobject ref)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, ref);
    jobject result;
    result = BASE_ENV(env)->NewLocalRef(env, ref);
    CHECK_EXIT(env);
    return result;
}

static void Check_DeleteLocalRef(JNIEnv* env, jobject localRef)
{
    CHECK_ENTER(env, kFlag_Default | kFlag_ExcepOkay);
    CHECK_OBJECT(env, localRef);
#ifdef USE_INDIRECT_REF
    if (localRef != NULL &&
        dvmGetJNIRefType(env, localRef) != JNILocalRefType)
    {
        LOGW("JNI WARNING: DeleteLocalRef on non-local %p (type=%d)\n",
            localRef, dvmGetJNIRefType(env, localRef));
        abortMaybe();
    } else
#endif
    {
        BASE_ENV(env)->DeleteLocalRef(env, localRef);
    }
    CHECK_EXIT(env);
}

static jint Check_EnsureLocalCapacity(JNIEnv *env, jint capacity)
{
    CHECK_ENTER(env, kFlag_Default);
    jint result;
    result = BASE_ENV(env)->EnsureLocalCapacity(env, capacity);
    CHECK_EXIT(env);
    return result;
}

static jboolean Check_IsSameObject(JNIEnv* env, jobject ref1, jobject ref2)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, ref1);
    CHECK_OBJECT(env, ref2);
    jboolean result;
    result = BASE_ENV(env)->IsSameObject(env, ref1, ref2);
    CHECK_EXIT(env);
    return result;
}

static jobject Check_AllocObject(JNIEnv* env, jclass clazz)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    jobject result;
    result = BASE_ENV(env)->AllocObject(env, clazz);
    CHECK_EXIT(env);
    return result;
}

static jobject Check_NewObject(JNIEnv* env, jclass clazz, jmethodID methodID,
    ...)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    jobject result;
    va_list args, tmpArgs;

    va_start(args, methodID);

    va_copy(tmpArgs, args);
    CHECK_METHOD_ARGS_V(env, methodID, tmpArgs);
    va_end(tmpArgs);

    result = BASE_ENV(env)->NewObjectV(env, clazz, methodID, args);
    va_end(args);

    CHECK_EXIT(env);
    return result;
}
static jobject Check_NewObjectV(JNIEnv* env, jclass clazz, jmethodID methodID,
    va_list args)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    jobject result;

    va_list tmpArgs;
    va_copy(tmpArgs, args);
    CHECK_METHOD_ARGS_V(env, methodID, tmpArgs);
    va_end(tmpArgs);

    result = BASE_ENV(env)->NewObjectV(env, clazz, methodID, args);
    CHECK_EXIT(env);
    return result;
}
static jobject Check_NewObjectA(JNIEnv* env, jclass clazz, jmethodID methodID,
    jvalue* args)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    jobject result;

    CHECK_METHOD_ARGS_A(env, methodID, args);
    result = BASE_ENV(env)->NewObjectA(env, clazz, methodID, args);
    CHECK_EXIT(env);
    return result;
}

static jclass Check_GetObjectClass(JNIEnv* env, jobject obj)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, obj);
    jclass result;
    result = BASE_ENV(env)->GetObjectClass(env, obj);
    CHECK_EXIT(env);
    return result;
}

static jboolean Check_IsInstanceOf(JNIEnv* env, jobject obj, jclass clazz)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, obj);
    CHECK_CLASS(env, clazz);
    jboolean result;
    result = BASE_ENV(env)->IsInstanceOf(env, obj, clazz);
    CHECK_EXIT(env);
    return result;
}

static jmethodID Check_GetMethodID(JNIEnv* env, jclass clazz, const char* name,
    const char* sig)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    CHECK_UTF_STRING(env, name, false);
    CHECK_UTF_STRING(env, sig, false);
    jmethodID result;
    result = BASE_ENV(env)->GetMethodID(env, clazz, name, sig);
    CHECK_EXIT(env);
    return result;
}

static jfieldID Check_GetFieldID(JNIEnv* env, jclass clazz,
    const char* name, const char* sig)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    CHECK_UTF_STRING(env, name, false);
    CHECK_UTF_STRING(env, sig, false);
    jfieldID result;
    result = BASE_ENV(env)->GetFieldID(env, clazz, name, sig);
    CHECK_EXIT(env);
    return result;
}

static jmethodID Check_GetStaticMethodID(JNIEnv* env, jclass clazz,
    const char* name, const char* sig)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    CHECK_UTF_STRING(env, name, false);
    CHECK_UTF_STRING(env, sig, false);
    jmethodID result;
    result = BASE_ENV(env)->GetStaticMethodID(env, clazz, name, sig);
    CHECK_EXIT(env);
    return result;
}

static jfieldID Check_GetStaticFieldID(JNIEnv* env, jclass clazz,
    const char* name, const char* sig)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    CHECK_UTF_STRING(env, name, false);
    CHECK_UTF_STRING(env, sig, false);
    jfieldID result;
    result = BASE_ENV(env)->GetStaticFieldID(env, clazz, name, sig);
    CHECK_EXIT(env);
    return result;
}

#define GET_STATIC_TYPE_FIELD(_ctype, _jname)                               \
    static _ctype Check_GetStatic##_jname##Field(JNIEnv* env, jclass clazz, \
        jfieldID fieldID)                                                   \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_CLASS(env, clazz);                                            \
        _ctype result;                                                      \
        checkStaticFieldID(env, clazz, fieldID);                            \
        result = BASE_ENV(env)->GetStatic##_jname##Field(env, clazz,        \
            fieldID);                                                       \
        CHECK_EXIT(env);                                                    \
        return result;                                                      \
    }
GET_STATIC_TYPE_FIELD(jobject, Object);
GET_STATIC_TYPE_FIELD(jboolean, Boolean);
GET_STATIC_TYPE_FIELD(jbyte, Byte);
GET_STATIC_TYPE_FIELD(jchar, Char);
GET_STATIC_TYPE_FIELD(jshort, Short);
GET_STATIC_TYPE_FIELD(jint, Int);
GET_STATIC_TYPE_FIELD(jlong, Long);
GET_STATIC_TYPE_FIELD(jfloat, Float);
GET_STATIC_TYPE_FIELD(jdouble, Double);

#define SET_STATIC_TYPE_FIELD(_ctype, _jname, _ftype)                       \
    static void Check_SetStatic##_jname##Field(JNIEnv* env, jclass clazz,   \
        jfieldID fieldID, _ctype value)                                     \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_CLASS(env, clazz);                                            \
        checkStaticFieldID(env, clazz, fieldID);                            \
        /* "value" arg only used when type == ref */                        \
        CHECK_FIELD_TYPE(env, (jobject)(u4)value, fieldID, _ftype, true);   \
        BASE_ENV(env)->SetStatic##_jname##Field(env, clazz, fieldID,        \
            value);                                                         \
        CHECK_EXIT(env);                                                    \
    }
SET_STATIC_TYPE_FIELD(jobject, Object, PRIM_NOT);
SET_STATIC_TYPE_FIELD(jboolean, Boolean, PRIM_BOOLEAN);
SET_STATIC_TYPE_FIELD(jbyte, Byte, PRIM_BYTE);
SET_STATIC_TYPE_FIELD(jchar, Char, PRIM_CHAR);
SET_STATIC_TYPE_FIELD(jshort, Short, PRIM_SHORT);
SET_STATIC_TYPE_FIELD(jint, Int, PRIM_INT);
SET_STATIC_TYPE_FIELD(jlong, Long, PRIM_LONG);
SET_STATIC_TYPE_FIELD(jfloat, Float, PRIM_FLOAT);
SET_STATIC_TYPE_FIELD(jdouble, Double, PRIM_DOUBLE);

#define GET_TYPE_FIELD(_ctype, _jname)                                      \
    static _ctype Check_Get##_jname##Field(JNIEnv* env, jobject obj,        \
        jfieldID fieldID)                                                   \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_OBJECT(env, obj);                                             \
        _ctype result;                                                      \
        CHECK_INST_FIELD_ID(env, obj, fieldID);                             \
        result = BASE_ENV(env)->Get##_jname##Field(env, obj, fieldID);      \
        CHECK_EXIT(env);                                                    \
        return result;                                                      \
    }
GET_TYPE_FIELD(jobject, Object);
GET_TYPE_FIELD(jboolean, Boolean);
GET_TYPE_FIELD(jbyte, Byte);
GET_TYPE_FIELD(jchar, Char);
GET_TYPE_FIELD(jshort, Short);
GET_TYPE_FIELD(jint, Int);
GET_TYPE_FIELD(jlong, Long);
GET_TYPE_FIELD(jfloat, Float);
GET_TYPE_FIELD(jdouble, Double);

#define SET_TYPE_FIELD(_ctype, _jname, _ftype)                              \
    static void Check_Set##_jname##Field(JNIEnv* env, jobject obj,          \
        jfieldID fieldID, _ctype value)                                     \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_OBJECT(env, obj);                                             \
        CHECK_INST_FIELD_ID(env, obj, fieldID);                             \
        /* "value" arg only used when type == ref */                        \
        CHECK_FIELD_TYPE(env, (jobject)(u4) value, fieldID, _ftype, false); \
        BASE_ENV(env)->Set##_jname##Field(env, obj, fieldID, value);        \
        CHECK_EXIT(env);                                                    \
    }
SET_TYPE_FIELD(jobject, Object, PRIM_NOT);
SET_TYPE_FIELD(jboolean, Boolean, PRIM_BOOLEAN);
SET_TYPE_FIELD(jbyte, Byte, PRIM_BYTE);
SET_TYPE_FIELD(jchar, Char, PRIM_CHAR);
SET_TYPE_FIELD(jshort, Short, PRIM_SHORT);
SET_TYPE_FIELD(jint, Int, PRIM_INT);
SET_TYPE_FIELD(jlong, Long, PRIM_LONG);
SET_TYPE_FIELD(jfloat, Float, PRIM_FLOAT);
SET_TYPE_FIELD(jdouble, Double, PRIM_DOUBLE);

#define CALL_VIRTUAL(_ctype, _jname, _retdecl, _retasgn, _retok, _retsig)   \
    static _ctype Check_Call##_jname##Method(JNIEnv* env, jobject obj,      \
        jmethodID methodID, ...)                                            \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_OBJECT(env, obj);                                             \
        CHECK_SIG(env, methodID, _retsig, false);                           \
        _retdecl;                                                           \
        va_list args, tmpArgs;                                              \
        va_start(args, methodID);                                           \
        va_copy(tmpArgs, args);                                             \
        CHECK_METHOD_ARGS_V(env, methodID, tmpArgs);                        \
        va_end(tmpArgs);                                                    \
        _retasgn BASE_ENV(env)->Call##_jname##MethodV(env, obj, methodID,   \
            args);                                                          \
        va_end(args);                                                       \
        CHECK_EXIT(env);                                                    \
        return _retok;                                                      \
    }                                                                       \
    static _ctype Check_Call##_jname##MethodV(JNIEnv* env, jobject obj,     \
        jmethodID methodID, va_list args)                                   \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_OBJECT(env, obj);                                             \
        CHECK_SIG(env, methodID, _retsig, false);                           \
        _retdecl;                                                           \
        va_list tmpArgs;                                                    \
        va_copy(tmpArgs, args);                                             \
        CHECK_METHOD_ARGS_V(env, methodID, tmpArgs);                        \
        va_end(tmpArgs);                                                    \
        _retasgn BASE_ENV(env)->Call##_jname##MethodV(env, obj, methodID,   \
            args);                                                          \
        CHECK_EXIT(env);                                                    \
        return _retok;                                                      \
    }                                                                       \
    static _ctype Check_Call##_jname##MethodA(JNIEnv* env, jobject obj,     \
        jmethodID methodID, jvalue* args)                                   \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_OBJECT(env, obj);                                             \
        CHECK_SIG(env, methodID, _retsig, false);                           \
        _retdecl;                                                           \
        CHECK_METHOD_ARGS_A(env, methodID, args);                           \
        _retasgn BASE_ENV(env)->Call##_jname##MethodA(env, obj, methodID,   \
            args);                                                          \
        CHECK_EXIT(env);                                                    \
        return _retok;                                                      \
    }
CALL_VIRTUAL(jobject, Object, Object* result, result=, result, 'L');
CALL_VIRTUAL(jboolean, Boolean, jboolean result, result=, result, 'Z');
CALL_VIRTUAL(jbyte, Byte, jbyte result, result=, result, 'B');
CALL_VIRTUAL(jchar, Char, jchar result, result=, result, 'C');
CALL_VIRTUAL(jshort, Short, jshort result, result=, result, 'S');
CALL_VIRTUAL(jint, Int, jint result, result=, result, 'I');
CALL_VIRTUAL(jlong, Long, jlong result, result=, result, 'J');
CALL_VIRTUAL(jfloat, Float, jfloat result, result=, result, 'F');
CALL_VIRTUAL(jdouble, Double, jdouble result, result=, result, 'D');
CALL_VIRTUAL(void, Void, , , , 'V');

#define CALL_NONVIRTUAL(_ctype, _jname, _retdecl, _retasgn, _retok,         \
        _retsig)                                                            \
    static _ctype Check_CallNonvirtual##_jname##Method(JNIEnv* env,         \
        jobject obj, jclass clazz, jmethodID methodID, ...)                 \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_CLASS(env, clazz);                                            \
        CHECK_OBJECT(env, obj);                                             \
        CHECK_SIG(env, methodID, _retsig, false);                           \
        _retdecl;                                                           \
        va_list args, tmpArgs;                                              \
        va_start(args, methodID);                                           \
        va_copy(tmpArgs, args);                                             \
        CHECK_METHOD_ARGS_V(env, methodID, tmpArgs);                        \
        va_end(tmpArgs);                                                    \
        _retasgn BASE_ENV(env)->CallNonvirtual##_jname##MethodV(env, obj,   \
            clazz, methodID, args);                                         \
        va_end(args);                                                       \
        CHECK_EXIT(env);                                                    \
        return _retok;                                                      \
    }                                                                       \
    static _ctype Check_CallNonvirtual##_jname##MethodV(JNIEnv* env,        \
        jobject obj, jclass clazz, jmethodID methodID, va_list args)        \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_CLASS(env, clazz);                                            \
        CHECK_OBJECT(env, obj);                                             \
        CHECK_SIG(env, methodID, _retsig, false);                           \
        _retdecl;                                                           \
        va_list tmpArgs;                                                    \
        va_copy(tmpArgs, args);                                             \
        CHECK_METHOD_ARGS_V(env, methodID, tmpArgs);                        \
        va_end(tmpArgs);                                                    \
        _retasgn BASE_ENV(env)->CallNonvirtual##_jname##MethodV(env, obj,   \
            clazz, methodID, args);                                         \
        CHECK_EXIT(env);                                                    \
        return _retok;                                                      \
    }                                                                       \
    static _ctype Check_CallNonvirtual##_jname##MethodA(JNIEnv* env,        \
        jobject obj, jclass clazz, jmethodID methodID, jvalue* args)        \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_CLASS(env, clazz);                                            \
        CHECK_OBJECT(env, obj);                                             \
        CHECK_SIG(env, methodID, _retsig, false);                           \
        _retdecl;                                                           \
        CHECK_METHOD_ARGS_A(env, methodID, args);                           \
        _retasgn BASE_ENV(env)->CallNonvirtual##_jname##MethodA(env, obj,   \
            clazz, methodID, args);                                         \
        CHECK_EXIT(env);                                                    \
        return _retok;                                                      \
    }
CALL_NONVIRTUAL(jobject, Object, Object* result, result=, result, 'L');
CALL_NONVIRTUAL(jboolean, Boolean, jboolean result, result=, result, 'Z');
CALL_NONVIRTUAL(jbyte, Byte, jbyte result, result=, result, 'B');
CALL_NONVIRTUAL(jchar, Char, jchar result, result=, result, 'C');
CALL_NONVIRTUAL(jshort, Short, jshort result, result=, result, 'S');
CALL_NONVIRTUAL(jint, Int, jint result, result=, result, 'I');
CALL_NONVIRTUAL(jlong, Long, jlong result, result=, result, 'J');
CALL_NONVIRTUAL(jfloat, Float, jfloat result, result=, result, 'F');
CALL_NONVIRTUAL(jdouble, Double, jdouble result, result=, result, 'D');
CALL_NONVIRTUAL(void, Void, , , , 'V');


#define CALL_STATIC(_ctype, _jname, _retdecl, _retasgn, _retok, _retsig)    \
    static _ctype Check_CallStatic##_jname##Method(JNIEnv* env,             \
        jclass clazz, jmethodID methodID, ...)                              \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_CLASS(env, clazz);                                            \
        CHECK_SIG(env, methodID, _retsig, true);                            \
        _retdecl;                                                           \
        va_list args, tmpArgs;                                              \
        va_start(args, methodID);                                           \
        va_copy(tmpArgs, args);                                             \
        CHECK_METHOD_ARGS_V(env, methodID, tmpArgs);                        \
        va_end(tmpArgs);                                                    \
        _retasgn BASE_ENV(env)->CallStatic##_jname##MethodV(env, clazz,     \
            methodID, args);                                                \
        va_end(args);                                                       \
        CHECK_EXIT(env);                                                    \
        return _retok;                                                      \
    }                                                                       \
    static _ctype Check_CallStatic##_jname##MethodV(JNIEnv* env,            \
        jclass clazz, jmethodID methodID, va_list args)                     \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_CLASS(env, clazz);                                            \
        CHECK_SIG(env, methodID, _retsig, true);                            \
        _retdecl;                                                           \
        va_list tmpArgs;                                                    \
        va_copy(tmpArgs, args);                                             \
        CHECK_METHOD_ARGS_V(env, methodID, tmpArgs);                        \
        va_end(tmpArgs);                                                    \
        _retasgn BASE_ENV(env)->CallStatic##_jname##MethodV(env, clazz,     \
            methodID, args);                                                \
        CHECK_EXIT(env);                                                    \
        return _retok;                                                      \
    }                                                                       \
    static _ctype Check_CallStatic##_jname##MethodA(JNIEnv* env,            \
        jclass clazz, jmethodID methodID, jvalue* args)                     \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_CLASS(env, clazz);                                            \
        CHECK_SIG(env, methodID, _retsig, true);                            \
        _retdecl;                                                           \
        CHECK_METHOD_ARGS_A(env, methodID, args);                           \
        _retasgn BASE_ENV(env)->CallStatic##_jname##MethodA(env, clazz,     \
            methodID, args);                                                \
        CHECK_EXIT(env);                                                    \
        return _retok;                                                      \
    }
CALL_STATIC(jobject, Object, Object* result, result=, result, 'L');
CALL_STATIC(jboolean, Boolean, jboolean result, result=, result, 'Z');
CALL_STATIC(jbyte, Byte, jbyte result, result=, result, 'B');
CALL_STATIC(jchar, Char, jchar result, result=, result, 'C');
CALL_STATIC(jshort, Short, jshort result, result=, result, 'S');
CALL_STATIC(jint, Int, jint result, result=, result, 'I');
CALL_STATIC(jlong, Long, jlong result, result=, result, 'J');
CALL_STATIC(jfloat, Float, jfloat result, result=, result, 'F');
CALL_STATIC(jdouble, Double, jdouble result, result=, result, 'D');
CALL_STATIC(void, Void, , , , 'V');

static jstring Check_NewString(JNIEnv* env, const jchar* unicodeChars,
    jsize len)
{
    CHECK_ENTER(env, kFlag_Default);
    jstring result;
    result = BASE_ENV(env)->NewString(env, unicodeChars, len);
    CHECK_EXIT(env);
    return result;
}

static jsize Check_GetStringLength(JNIEnv* env, jstring string)
{
    CHECK_ENTER(env, kFlag_CritOkay);
    CHECK_STRING(env, string);
    jsize result;
    result = BASE_ENV(env)->GetStringLength(env, string);
    CHECK_EXIT(env);
    return result;
}

static const jchar* Check_GetStringChars(JNIEnv* env, jstring string,
    jboolean* isCopy)
{
    CHECK_ENTER(env, kFlag_CritOkay);
    CHECK_STRING(env, string);
    const jchar* result;
    result = BASE_ENV(env)->GetStringChars(env, string, isCopy);
    if (((JNIEnvExt*)env)->forceDataCopy && result != NULL) {
        // TODO: fix for indirect
        int len = dvmStringLen(string) * 2;
        result = (const jchar*) createGuardedCopy(result, len, false);
        if (isCopy != NULL)
            *isCopy = JNI_TRUE;
    }
    CHECK_EXIT(env);
    return result;
}

static void Check_ReleaseStringChars(JNIEnv* env, jstring string,
    const jchar* chars)
{
    CHECK_ENTER(env, kFlag_Default | kFlag_ExcepOkay);
    CHECK_STRING(env, string);
    CHECK_NON_NULL(env, chars);
    if (((JNIEnvExt*)env)->forceDataCopy) {
        if (!checkGuardedCopy(chars, false)) {
            LOGE("JNI: failed guarded copy check in ReleaseStringChars\n");
            abortMaybe();
            return;
        }
        chars = (const jchar*) freeGuardedCopy((jchar*)chars);
    }
    BASE_ENV(env)->ReleaseStringChars(env, string, chars);
    CHECK_EXIT(env);
}

static jstring Check_NewStringUTF(JNIEnv* env, const char* bytes)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_UTF_STRING(env, bytes, true);
    jstring result;
    result = BASE_ENV(env)->NewStringUTF(env, bytes);
    CHECK_EXIT(env);
    return result;
}

static jsize Check_GetStringUTFLength(JNIEnv* env, jstring string)
{
    CHECK_ENTER(env, kFlag_CritOkay);
    CHECK_STRING(env, string);
    jsize result;
    result = BASE_ENV(env)->GetStringUTFLength(env, string);
    CHECK_EXIT(env);
    return result;
}

static const char* Check_GetStringUTFChars(JNIEnv* env, jstring string,
    jboolean* isCopy)
{
    CHECK_ENTER(env, kFlag_CritOkay);
    CHECK_STRING(env, string);
    const char* result;
    result = BASE_ENV(env)->GetStringUTFChars(env, string, isCopy);
    if (((JNIEnvExt*)env)->forceDataCopy && result != NULL) {
        // TODO: fix for indirect
        int len = dvmStringUtf8ByteLen(string) + 1;
        result = (const char*) createGuardedCopy(result, len, false);
        if (isCopy != NULL)
            *isCopy = JNI_TRUE;
    }
    CHECK_EXIT(env);
    return result;
}

static void Check_ReleaseStringUTFChars(JNIEnv* env, jstring string,
    const char* utf)
{
    CHECK_ENTER(env, kFlag_ExcepOkay);
    CHECK_STRING(env, string);
    CHECK_NON_NULL(env, utf);
    if (((JNIEnvExt*)env)->forceDataCopy) {
        //int len = dvmStringUtf8ByteLen(string) + 1;
        if (!checkGuardedCopy(utf, false)) {
            LOGE("JNI: failed guarded copy check in ReleaseStringUTFChars\n");
            abortMaybe();
            return;
        }
        utf = (const char*) freeGuardedCopy((char*)utf);
    }
    BASE_ENV(env)->ReleaseStringUTFChars(env, string, utf);
    CHECK_EXIT(env);
}

static jsize Check_GetArrayLength(JNIEnv* env, jarray array)
{
    CHECK_ENTER(env, kFlag_CritOkay);
    CHECK_ARRAY(env, array);
    jsize result;
    result = BASE_ENV(env)->GetArrayLength(env, array);
    CHECK_EXIT(env);
    return result;
}

static jobjectArray Check_NewObjectArray(JNIEnv* env, jsize length,
    jclass elementClass, jobject initialElement)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, elementClass);
    CHECK_OBJECT(env, initialElement);
    CHECK_LENGTH_POSITIVE(env, length);
    jobjectArray result;
    result = BASE_ENV(env)->NewObjectArray(env, length, elementClass,
                                            initialElement);
    CHECK_EXIT(env);
    return result;
}

static jobject Check_GetObjectArrayElement(JNIEnv* env, jobjectArray array,
    jsize index)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_ARRAY(env, array);
    jobject result;
    result = BASE_ENV(env)->GetObjectArrayElement(env, array, index);
    CHECK_EXIT(env);
    return result;
}

static void Check_SetObjectArrayElement(JNIEnv* env, jobjectArray array,
    jsize index, jobject value)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_ARRAY(env, array);
    BASE_ENV(env)->SetObjectArrayElement(env, array, index, value);
    CHECK_EXIT(env);
}

#define NEW_PRIMITIVE_ARRAY(_artype, _jname)                                \
    static _artype Check_New##_jname##Array(JNIEnv* env, jsize length)      \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_LENGTH_POSITIVE(env, length);                                 \
        _artype result;                                                     \
        result = BASE_ENV(env)->New##_jname##Array(env, length);            \
        CHECK_EXIT(env);                                                    \
        return result;                                                      \
    }
NEW_PRIMITIVE_ARRAY(jbooleanArray, Boolean);
NEW_PRIMITIVE_ARRAY(jbyteArray, Byte);
NEW_PRIMITIVE_ARRAY(jcharArray, Char);
NEW_PRIMITIVE_ARRAY(jshortArray, Short);
NEW_PRIMITIVE_ARRAY(jintArray, Int);
NEW_PRIMITIVE_ARRAY(jlongArray, Long);
NEW_PRIMITIVE_ARRAY(jfloatArray, Float);
NEW_PRIMITIVE_ARRAY(jdoubleArray, Double);


#define GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname)                        \
    static _ctype* Check_Get##_jname##ArrayElements(JNIEnv* env,            \
        _ctype##Array array, jboolean* isCopy)                              \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_ARRAY(env, array);                                            \
        _ctype* result;                                                     \
        result = BASE_ENV(env)->Get##_jname##ArrayElements(env,             \
            array, isCopy);                                                 \
        if (((JNIEnvExt*)env)->forceDataCopy && result != NULL) {           \
            result = (_ctype*) createGuardedPACopy(env, array, isCopy);     \
        }                                                                   \
        CHECK_EXIT(env);                                                    \
        return result;                                                      \
    }

#define RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname)                    \
    static void Check_Release##_jname##ArrayElements(JNIEnv* env,           \
        _ctype##Array array, _ctype* elems, jint mode)                      \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default | kFlag_ExcepOkay);                  \
        CHECK_ARRAY(env, array);                                            \
        CHECK_NON_NULL(env, elems);                                         \
        CHECK_RELEASE_MODE(env, mode);                                      \
        if (((JNIEnvExt*)env)->forceDataCopy) {                             \
            elems = (_ctype*) releaseGuardedPACopy(env, array, elems, mode);\
        }                                                                   \
        BASE_ENV(env)->Release##_jname##ArrayElements(env,                  \
            array, elems, mode);                                            \
        CHECK_EXIT(env);                                                    \
    }

#define GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname)                          \
    static void Check_Get##_jname##ArrayRegion(JNIEnv* env,                 \
        _ctype##Array array, jsize start, jsize len, _ctype* buf)           \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_ARRAY(env, array);                                            \
        BASE_ENV(env)->Get##_jname##ArrayRegion(env, array, start,          \
            len, buf);                                                      \
        CHECK_EXIT(env);                                                    \
    }

#define SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname)                          \
    static void Check_Set##_jname##ArrayRegion(JNIEnv* env,                 \
        _ctype##Array array, jsize start, jsize len, const _ctype* buf)     \
    {                                                                       \
        CHECK_ENTER(env, kFlag_Default);                                    \
        CHECK_ARRAY(env, array);                                            \
        BASE_ENV(env)->Set##_jname##ArrayRegion(env, array, start,          \
            len, buf);                                                      \
        CHECK_EXIT(env);                                                    \
    }

#define PRIMITIVE_ARRAY_FUNCTIONS(_ctype, _jname, _typechar)                \
    GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname);                           \
    RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname);                       \
    GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname);                             \
    SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname);

/* TODO: verify primitive array type matches call type */
PRIMITIVE_ARRAY_FUNCTIONS(jboolean, Boolean, 'Z');
PRIMITIVE_ARRAY_FUNCTIONS(jbyte, Byte, 'B');
PRIMITIVE_ARRAY_FUNCTIONS(jchar, Char, 'C');
PRIMITIVE_ARRAY_FUNCTIONS(jshort, Short, 'S');
PRIMITIVE_ARRAY_FUNCTIONS(jint, Int, 'I');
PRIMITIVE_ARRAY_FUNCTIONS(jlong, Long, 'J');
PRIMITIVE_ARRAY_FUNCTIONS(jfloat, Float, 'F');
PRIMITIVE_ARRAY_FUNCTIONS(jdouble, Double, 'D');

static jint Check_RegisterNatives(JNIEnv* env, jclass clazz,
    const JNINativeMethod* methods, jint nMethods)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    jint result;
    result = BASE_ENV(env)->RegisterNatives(env, clazz, methods, nMethods);
    CHECK_EXIT(env);
    return result;
}

static jint Check_UnregisterNatives(JNIEnv* env, jclass clazz)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_CLASS(env, clazz);
    jint result;
    result = BASE_ENV(env)->UnregisterNatives(env, clazz);
    CHECK_EXIT(env);
    return result;
}

static jint Check_MonitorEnter(JNIEnv* env, jobject obj)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, obj);
    jint result;
    result = BASE_ENV(env)->MonitorEnter(env, obj);
    CHECK_EXIT(env);
    return result;
}

static jint Check_MonitorExit(JNIEnv* env, jobject obj)
{
    CHECK_ENTER(env, kFlag_Default | kFlag_ExcepOkay);
    CHECK_OBJECT(env, obj);
    jint result;
    result = BASE_ENV(env)->MonitorExit(env, obj);
    CHECK_EXIT(env);
    return result;
}

static jint Check_GetJavaVM(JNIEnv *env, JavaVM **vm)
{
    CHECK_ENTER(env, kFlag_Default);
    jint result;
    result = BASE_ENV(env)->GetJavaVM(env, vm);
    CHECK_EXIT(env);
    return result;
}

static void Check_GetStringRegion(JNIEnv* env, jstring str, jsize start,
    jsize len, jchar* buf)
{
    CHECK_ENTER(env, kFlag_CritOkay);
    CHECK_STRING(env, str);
    BASE_ENV(env)->GetStringRegion(env, str, start, len, buf);
    CHECK_EXIT(env);
}

static void Check_GetStringUTFRegion(JNIEnv* env, jstring str, jsize start,
    jsize len, char* buf)
{
    CHECK_ENTER(env, kFlag_CritOkay);
    CHECK_STRING(env, str);
    BASE_ENV(env)->GetStringUTFRegion(env, str, start, len, buf);
    CHECK_EXIT(env);
}

static void* Check_GetPrimitiveArrayCritical(JNIEnv* env, jarray array,
    jboolean* isCopy)
{
    CHECK_ENTER(env, kFlag_CritGet);
    CHECK_ARRAY(env, array);
    void* result;
    result = BASE_ENV(env)->GetPrimitiveArrayCritical(env, array, isCopy);
    if (((JNIEnvExt*)env)->forceDataCopy && result != NULL) {
        result = createGuardedPACopy(env, array, isCopy);
    }
    CHECK_EXIT(env);
    return result;
}

static void Check_ReleasePrimitiveArrayCritical(JNIEnv* env, jarray array,
    void* carray, jint mode)
{
    CHECK_ENTER(env, kFlag_CritRelease | kFlag_ExcepOkay);
    CHECK_ARRAY(env, array);
    CHECK_NON_NULL(env, carray);
    CHECK_RELEASE_MODE(env, mode);
    if (((JNIEnvExt*)env)->forceDataCopy) {
        carray = releaseGuardedPACopy(env, array, carray, mode);
    }
    BASE_ENV(env)->ReleasePrimitiveArrayCritical(env, array, carray, mode);
    CHECK_EXIT(env);
}

static const jchar* Check_GetStringCritical(JNIEnv* env, jstring string,
    jboolean* isCopy)
{
    CHECK_ENTER(env, kFlag_CritGet);
    CHECK_STRING(env, string);
    const jchar* result;
    result = BASE_ENV(env)->GetStringCritical(env, string, isCopy);
    if (((JNIEnvExt*)env)->forceDataCopy && result != NULL) {
        // TODO: fix for indirect
        int len = dvmStringLen(string) * 2;
        result = (const jchar*) createGuardedCopy(result, len, false);
        if (isCopy != NULL)
            *isCopy = JNI_TRUE;
    }
    CHECK_EXIT(env);
    return result;
}

static void Check_ReleaseStringCritical(JNIEnv* env, jstring string,
    const jchar* carray)
{
    CHECK_ENTER(env, kFlag_CritRelease | kFlag_ExcepOkay);
    CHECK_STRING(env, string);
    CHECK_NON_NULL(env, carray);
    if (((JNIEnvExt*)env)->forceDataCopy) {
        if (!checkGuardedCopy(carray, false)) {
            LOGE("JNI: failed guarded copy check in ReleaseStringCritical\n");
            abortMaybe();
            return;
        }
        carray = (const jchar*) freeGuardedCopy((jchar*)carray);
    }
    BASE_ENV(env)->ReleaseStringCritical(env, string, carray);
    CHECK_EXIT(env);
}

static jweak Check_NewWeakGlobalRef(JNIEnv* env, jobject obj)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, obj);
    jweak result;
    result = BASE_ENV(env)->NewWeakGlobalRef(env, obj);
    CHECK_EXIT(env);
    return result;
}

static void Check_DeleteWeakGlobalRef(JNIEnv* env, jweak obj)
{
    CHECK_ENTER(env, kFlag_Default | kFlag_ExcepOkay);
    CHECK_OBJECT(env, obj);
    BASE_ENV(env)->DeleteWeakGlobalRef(env, obj);
    CHECK_EXIT(env);
}

static jboolean Check_ExceptionCheck(JNIEnv* env)
{
    CHECK_ENTER(env, kFlag_CritOkay | kFlag_ExcepOkay);
    jboolean result;
    result = BASE_ENV(env)->ExceptionCheck(env);
    CHECK_EXIT(env);
    return result;
}

static jobjectRefType Check_GetObjectRefType(JNIEnv* env, jobject obj)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, obj);
    jobjectRefType result;
    result = BASE_ENV(env)->GetObjectRefType(env, obj);
    CHECK_EXIT(env);
    return result;
}

static jobject Check_NewDirectByteBuffer(JNIEnv* env, void* address,
    jlong capacity)
{
    CHECK_ENTER(env, kFlag_Default);
    jobject result;
    if (address == NULL || capacity < 0) {
        LOGW("JNI WARNING: invalid values for address (%p) or capacity (%ld)\n",
            address, (long) capacity);
        abortMaybe();
        return NULL;
    }
    result = BASE_ENV(env)->NewDirectByteBuffer(env, address, capacity);
    CHECK_EXIT(env);
    return result;
}

static void* Check_GetDirectBufferAddress(JNIEnv* env, jobject buf)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, buf);
    void* result = BASE_ENV(env)->GetDirectBufferAddress(env, buf);
    CHECK_EXIT(env);

    /* optional - check result vs. "safe" implementation */
    if (kRedundantDirectBufferTest) {
        jobject platformAddr = NULL;
        void* checkResult = NULL;

        /*
         * Start by determining if the object supports the DirectBuffer
         * interfaces.  Note this does not guarantee that it's a direct buffer.
         */
        if (JNI_FALSE == (*env)->IsInstanceOf(env, buf,
                gDvm.jclassOrgApacheHarmonyNioInternalDirectBuffer))
        {
            goto bail;
        }

        /*
         * Get the PlatformAddress object.
         *
         * If this isn't a direct buffer, platformAddr will be NULL and/or an
         * exception will have been thrown.
         */
        platformAddr = (*env)->CallObjectMethod(env, buf,
            (jmethodID) gDvm.methOrgApacheHarmonyNioInternalDirectBuffer_getEffectiveAddress);

        if ((*env)->ExceptionCheck(env)) {
            (*env)->ExceptionClear(env);
            platformAddr = NULL;
        }
        if (platformAddr == NULL) {
            LOGV("Got request for address of non-direct buffer\n");
            goto bail;
        }

        jclass platformAddrClass = (*env)->FindClass(env,
            "org/apache/harmony/luni/platform/PlatformAddress");
        jmethodID toLongMethod = (*env)->GetMethodID(env, platformAddrClass,
            "toLong", "()J");
        checkResult = (void*)(u4)(*env)->CallLongMethod(env, platformAddr,
                toLongMethod);

    bail:
        if (platformAddr != NULL)
            (*env)->DeleteLocalRef(env, platformAddr);

        if (result != checkResult) {
            LOGW("JNI WARNING: direct buffer result mismatch (%p vs %p)\n",
                result, checkResult);
            abortMaybe();
            /* keep going */
        }
    }

    return result;
}

static jlong Check_GetDirectBufferCapacity(JNIEnv* env, jobject buf)
{
    CHECK_ENTER(env, kFlag_Default);
    CHECK_OBJECT(env, buf);
    /* TODO: verify "buf" is an instance of java.nio.Buffer */
    jlong result = BASE_ENV(env)->GetDirectBufferCapacity(env, buf);
    CHECK_EXIT(env);
    return result;
}


/*
 * ===========================================================================
 *      JNI invocation functions
 * ===========================================================================
 */

static jint Check_DestroyJavaVM(JavaVM* vm)
{
    CHECK_VMENTER(vm, false);
    jint result;
    result = BASE_VM(vm)->DestroyJavaVM(vm);
    CHECK_VMEXIT(vm, false);
    return result;
}

static jint Check_AttachCurrentThread(JavaVM* vm, JNIEnv** p_env,
    void* thr_args)
{
    CHECK_VMENTER(vm, false);
    jint result;
    result = BASE_VM(vm)->AttachCurrentThread(vm, p_env, thr_args);
    CHECK_VMEXIT(vm, true);
    return result;
}

static jint Check_AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env,
    void* thr_args)
{
    CHECK_VMENTER(vm, false);
    jint result;
    result = BASE_VM(vm)->AttachCurrentThreadAsDaemon(vm, p_env, thr_args);
    CHECK_VMEXIT(vm, true);
    return result;
}

static jint Check_DetachCurrentThread(JavaVM* vm)
{
    CHECK_VMENTER(vm, true);
    jint result;
    result = BASE_VM(vm)->DetachCurrentThread(vm);
    CHECK_VMEXIT(vm, false);
    return result;
}

static jint Check_GetEnv(JavaVM* vm, void** env, jint version)
{
    CHECK_VMENTER(vm, true);
    jint result;
    result = BASE_VM(vm)->GetEnv(vm, env, version);
    CHECK_VMEXIT(vm, true);
    return result;
}


/*
 * ===========================================================================
 *      Function tables
 * ===========================================================================
 */

static const struct JNINativeInterface gCheckNativeInterface = {
    NULL,
    NULL,
    NULL,
    NULL,

    Check_GetVersion,

    Check_DefineClass,
    Check_FindClass,

    Check_FromReflectedMethod,
    Check_FromReflectedField,
    Check_ToReflectedMethod,

    Check_GetSuperclass,
    Check_IsAssignableFrom,

    Check_ToReflectedField,

    Check_Throw,
    Check_ThrowNew,
    Check_ExceptionOccurred,
    Check_ExceptionDescribe,
    Check_ExceptionClear,
    Check_FatalError,

    Check_PushLocalFrame,
    Check_PopLocalFrame,

    Check_NewGlobalRef,
    Check_DeleteGlobalRef,
    Check_DeleteLocalRef,
    Check_IsSameObject,
    Check_NewLocalRef,
    Check_EnsureLocalCapacity,

    Check_AllocObject,
    Check_NewObject,
    Check_NewObjectV,
    Check_NewObjectA,

    Check_GetObjectClass,
    Check_IsInstanceOf,

    Check_GetMethodID,

    Check_CallObjectMethod,
    Check_CallObjectMethodV,
    Check_CallObjectMethodA,
    Check_CallBooleanMethod,
    Check_CallBooleanMethodV,
    Check_CallBooleanMethodA,
    Check_CallByteMethod,
    Check_CallByteMethodV,
    Check_CallByteMethodA,
    Check_CallCharMethod,
    Check_CallCharMethodV,
    Check_CallCharMethodA,
    Check_CallShortMethod,
    Check_CallShortMethodV,
    Check_CallShortMethodA,
    Check_CallIntMethod,
    Check_CallIntMethodV,
    Check_CallIntMethodA,
    Check_CallLongMethod,
    Check_CallLongMethodV,
    Check_CallLongMethodA,
    Check_CallFloatMethod,
    Check_CallFloatMethodV,
    Check_CallFloatMethodA,
    Check_CallDoubleMethod,
    Check_CallDoubleMethodV,
    Check_CallDoubleMethodA,
    Check_CallVoidMethod,
    Check_CallVoidMethodV,
    Check_CallVoidMethodA,

    Check_CallNonvirtualObjectMethod,
    Check_CallNonvirtualObjectMethodV,
    Check_CallNonvirtualObjectMethodA,
    Check_CallNonvirtualBooleanMethod,
    Check_CallNonvirtualBooleanMethodV,
    Check_CallNonvirtualBooleanMethodA,
    Check_CallNonvirtualByteMethod,
    Check_CallNonvirtualByteMethodV,
    Check_CallNonvirtualByteMethodA,
    Check_CallNonvirtualCharMethod,
    Check_CallNonvirtualCharMethodV,
    Check_CallNonvirtualCharMethodA,
    Check_CallNonvirtualShortMethod,
    Check_CallNonvirtualShortMethodV,
    Check_CallNonvirtualShortMethodA,
    Check_CallNonvirtualIntMethod,
    Check_CallNonvirtualIntMethodV,
    Check_CallNonvirtualIntMethodA,
    Check_CallNonvirtualLongMethod,
    Check_CallNonvirtualLongMethodV,
    Check_CallNonvirtualLongMethodA,
    Check_CallNonvirtualFloatMethod,
    Check_CallNonvirtualFloatMethodV,
    Check_CallNonvirtualFloatMethodA,
    Check_CallNonvirtualDoubleMethod,
    Check_CallNonvirtualDoubleMethodV,
    Check_CallNonvirtualDoubleMethodA,
    Check_CallNonvirtualVoidMethod,
    Check_CallNonvirtualVoidMethodV,
    Check_CallNonvirtualVoidMethodA,

    Check_GetFieldID,

    Check_GetObjectField,
    Check_GetBooleanField,
    Check_GetByteField,
    Check_GetCharField,
    Check_GetShortField,
    Check_GetIntField,
    Check_GetLongField,
    Check_GetFloatField,
    Check_GetDoubleField,
    Check_SetObjectField,
    Check_SetBooleanField,
    Check_SetByteField,
    Check_SetCharField,
    Check_SetShortField,
    Check_SetIntField,
    Check_SetLongField,
    Check_SetFloatField,
    Check_SetDoubleField,

    Check_GetStaticMethodID,

    Check_CallStaticObjectMethod,
    Check_CallStaticObjectMethodV,
    Check_CallStaticObjectMethodA,
    Check_CallStaticBooleanMethod,
    Check_CallStaticBooleanMethodV,
    Check_CallStaticBooleanMethodA,
    Check_CallStaticByteMethod,
    Check_CallStaticByteMethodV,
    Check_CallStaticByteMethodA,
    Check_CallStaticCharMethod,
    Check_CallStaticCharMethodV,
    Check_CallStaticCharMethodA,
    Check_CallStaticShortMethod,
    Check_CallStaticShortMethodV,
    Check_CallStaticShortMethodA,
    Check_CallStaticIntMethod,
    Check_CallStaticIntMethodV,
    Check_CallStaticIntMethodA,
    Check_CallStaticLongMethod,
    Check_CallStaticLongMethodV,
    Check_CallStaticLongMethodA,
    Check_CallStaticFloatMethod,
    Check_CallStaticFloatMethodV,
    Check_CallStaticFloatMethodA,
    Check_CallStaticDoubleMethod,
    Check_CallStaticDoubleMethodV,
    Check_CallStaticDoubleMethodA,
    Check_CallStaticVoidMethod,
    Check_CallStaticVoidMethodV,
    Check_CallStaticVoidMethodA,

    Check_GetStaticFieldID,

    Check_GetStaticObjectField,
    Check_GetStaticBooleanField,
    Check_GetStaticByteField,
    Check_GetStaticCharField,
    Check_GetStaticShortField,
    Check_GetStaticIntField,
    Check_GetStaticLongField,
    Check_GetStaticFloatField,
    Check_GetStaticDoubleField,

    Check_SetStaticObjectField,
    Check_SetStaticBooleanField,
    Check_SetStaticByteField,
    Check_SetStaticCharField,
    Check_SetStaticShortField,
    Check_SetStaticIntField,
    Check_SetStaticLongField,
    Check_SetStaticFloatField,
    Check_SetStaticDoubleField,

    Check_NewString,

    Check_GetStringLength,
    Check_GetStringChars,
    Check_ReleaseStringChars,

    Check_NewStringUTF,
    Check_GetStringUTFLength,
    Check_GetStringUTFChars,
    Check_ReleaseStringUTFChars,

    Check_GetArrayLength,
    Check_NewObjectArray,
    Check_GetObjectArrayElement,
    Check_SetObjectArrayElement,

    Check_NewBooleanArray,
    Check_NewByteArray,
    Check_NewCharArray,
    Check_NewShortArray,
    Check_NewIntArray,
    Check_NewLongArray,
    Check_NewFloatArray,
    Check_NewDoubleArray,

    Check_GetBooleanArrayElements,
    Check_GetByteArrayElements,
    Check_GetCharArrayElements,
    Check_GetShortArrayElements,
    Check_GetIntArrayElements,
    Check_GetLongArrayElements,
    Check_GetFloatArrayElements,
    Check_GetDoubleArrayElements,

    Check_ReleaseBooleanArrayElements,
    Check_ReleaseByteArrayElements,
    Check_ReleaseCharArrayElements,
    Check_ReleaseShortArrayElements,
    Check_ReleaseIntArrayElements,
    Check_ReleaseLongArrayElements,
    Check_ReleaseFloatArrayElements,
    Check_ReleaseDoubleArrayElements,

    Check_GetBooleanArrayRegion,
    Check_GetByteArrayRegion,
    Check_GetCharArrayRegion,
    Check_GetShortArrayRegion,
    Check_GetIntArrayRegion,
    Check_GetLongArrayRegion,
    Check_GetFloatArrayRegion,
    Check_GetDoubleArrayRegion,
    Check_SetBooleanArrayRegion,
    Check_SetByteArrayRegion,
    Check_SetCharArrayRegion,
    Check_SetShortArrayRegion,
    Check_SetIntArrayRegion,
    Check_SetLongArrayRegion,
    Check_SetFloatArrayRegion,
    Check_SetDoubleArrayRegion,

    Check_RegisterNatives,
    Check_UnregisterNatives,

    Check_MonitorEnter,
    Check_MonitorExit,

    Check_GetJavaVM,

    Check_GetStringRegion,
    Check_GetStringUTFRegion,

    Check_GetPrimitiveArrayCritical,
    Check_ReleasePrimitiveArrayCritical,

    Check_GetStringCritical,
    Check_ReleaseStringCritical,

    Check_NewWeakGlobalRef,
    Check_DeleteWeakGlobalRef,

    Check_ExceptionCheck,

    Check_NewDirectByteBuffer,
    Check_GetDirectBufferAddress,
    Check_GetDirectBufferCapacity,

    Check_GetObjectRefType
};
static const struct JNIInvokeInterface gCheckInvokeInterface = {
    NULL,
    NULL,
    NULL,

    Check_DestroyJavaVM,
    Check_AttachCurrentThread,
    Check_DetachCurrentThread,

    Check_GetEnv,

    Check_AttachCurrentThreadAsDaemon,
};


/*
 * Replace the normal table with the checked table.
 */
void dvmUseCheckedJniEnv(JNIEnvExt* pEnv)
{
    assert(pEnv->funcTable != &gCheckNativeInterface);
    pEnv->baseFuncTable = pEnv->funcTable;
    pEnv->funcTable = &gCheckNativeInterface;
}

/*
 * Replace the normal table with the checked table.
 */
void dvmUseCheckedJniVm(JavaVMExt* pVm)
{
    assert(pVm->funcTable != &gCheckInvokeInterface);
    pVm->baseFuncTable = pVm->funcTable;
    pVm->funcTable = &gCheckInvokeInterface;
}