/*
 * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * Copyright 2003 Wily Technology, Inc.
 */

#include    <jni.h>
#include    <jvm.h>
#include    <jvmti.h>
#include    <stdlib.h>
#include    <string.h>
#include    "JPLISAgent.h"
#include    "JPLISAssert.h"
#include    "Utilities.h"
#include    "Reentrancy.h"
#include    "JavaExceptions.h"

#include    "EncodingSupport.h"
#include    "FileSystemSupport.h"    /* For MAXPATHLEN & uintptr_t */

#include    "sun_instrument_InstrumentationImpl.h"

/*
 *  The JPLISAgent manages the initialization all of the Java programming language Agents.
 *  It also supports the native method bridge between the JPLIS and the JVMTI.
 *  It maintains a single JVMTI Env that all JPL agents share.
 *  It parses command line requests and creates individual Java agents.
 */


/*
 *  private prototypes
 */

/* Allocates an unformatted JPLIS agent data structure. Returns NULL if allocation fails. */
JPLISAgent *
allocateJPLISAgent(jvmtiEnv *       jvmtiEnv);

/* Initializes an already-allocated JPLIS agent data structure. */
JPLISInitializationError
initializeJPLISAgent(   JPLISAgent *    agent,
                        JavaVM *        vm,
                        jvmtiEnv *      jvmtienv,
                        const char *    jarfile,
                        jboolean        printWarning);
/* De-allocates a JPLIS agent data structure. Only used in partial-failure cases at startup;
 * in normal usage the JPLIS agent lives forever
 */
void
deallocateJPLISAgent(   jvmtiEnv *      jvmtienv,
                        JPLISAgent *    agent);

/* Does one-time work to interrogate the JVM about capabilities and cache the answers. */
void
checkCapabilities(JPLISAgent * agent);

/* Takes the elements of the command string (agent class name and options string) and
 * create java strings for them.
 * Returns true if a classname was found. Makes no promises beyond the textual; says nothing about whether
 * the class exists or can be loaded.
 * If return value is true, sets outputClassname to a non-NULL local JNI reference.
 * If return value is true, sets outputOptionsString either to NULL or to a non-NULL local JNI reference.
 * If return value is false, neither output parameter is set.
 */
jboolean
commandStringIntoJavaStrings(  JNIEnv *        jnienv,
                               const char *    classname,
                               const char *    optionsString,
                               jstring *       outputClassname,
                               jstring *       outputOptionsString);

/* Start one Java agent from the supplied parameters.
 * Most of the logic lives in a helper function that lives over in Java code--
 * we pass parameters out to Java and use our own Java helper to actually
 * load the agent and call the premain.
 * Returns true if the Java agent class is loaded and the premain/agentmain method completes
 * with no exceptions, false otherwise.
 */
jboolean
invokeJavaAgentMainMethod( JNIEnv *    jnienv,
                           jobject     instrumentationImpl,
                           jmethodID   agentMainMethod,
                           jstring     className,
                           jstring     optionsString);

/* Once we have loaded the Java agent and called the premain,
 * we can release the copies we have been keeping of the command line
 * data (agent class name and option strings).
 */
void
deallocateCommandLineData(JPLISAgent * agent);

/*
 *  Common support for various class list fetchers.
 */
typedef jvmtiError (*ClassListFetcher)
    (   jvmtiEnv *  jvmtiEnv,
        jobject     classLoader,
        jint *      classCount,
        jclass **   classes);

/* Fetcher that ignores the class loader parameter, and uses the JVMTI to get a list of all classes.
 * Returns a jvmtiError according to the underlying JVMTI service.
 */
jvmtiError
getAllLoadedClassesClassListFetcher(    jvmtiEnv *  jvmtiEnv,
                                        jobject     classLoader,
                                        jint *      classCount,
                                        jclass **   classes);

/* Fetcher that uses the class loader parameter, and uses the JVMTI to get a list of all classes
 * for which the supplied loader is the initiating loader.
 * Returns a jvmtiError according to the underlying JVMTI service.
 */
jvmtiError
getInitiatedClassesClassListFetcher(    jvmtiEnv *  jvmtiEnv,
                                        jobject     classLoader,
                                        jint *      classCount,
                                        jclass **   classes);

/*
 * Common guts for two native methods, which are the same except for the policy for fetching
 * the list of classes.
 * Either returns a local JNI reference to an array of references to java.lang.Class.
 * Can throw, if it does will alter the JNIEnv with an outstanding exception.
 */
jobjectArray
commonGetClassList( JNIEnv *            jnienv,
                    JPLISAgent *        agent,
                    jobject             classLoader,
                    ClassListFetcher    fetcher);


/*
 *  Misc. utilities.
 */

/* Checked exception mapper used by the redefine classes implementation.
 * Allows ClassNotFoundException or UnmodifiableClassException; maps others
 * to InternalError. Can return NULL in an error case.
 */
jthrowable
redefineClassMapper(    JNIEnv *    jnienv,
                        jthrowable  throwableToMap);

/* Turns a buffer of jclass * into a Java array whose elements are java.lang.Class.
 * Can throw, if it does will alter the JNIEnv with an outstanding exception.
 */
jobjectArray
getObjectArrayFromClasses(JNIEnv* jnienv, jclass* classes, jint classCount);


JPLISEnvironment *
getJPLISEnvironment(jvmtiEnv * jvmtienv) {
    JPLISEnvironment * environment  = NULL;
    jvmtiError         jvmtierror   = JVMTI_ERROR_NONE;

    jvmtierror = (*jvmtienv)->GetEnvironmentLocalStorage(
                                            jvmtienv,
                                            (void**)&environment);
    /* can be called from any phase */
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);

    if (jvmtierror == JVMTI_ERROR_NONE) {
        jplis_assert(environment != NULL);
        jplis_assert(environment->mJVMTIEnv == jvmtienv);
    } else {
        environment = NULL;
    }
    return environment;
}

/*
 *  OnLoad processing code.
 */

/*
 *  Creates a new JPLISAgent.
 *  Returns error if the agent cannot be created and initialized.
 *  The JPLISAgent* pointed to by agent_ptr is set to the new broker,
 *  or NULL if an error has occurred.
 */
JPLISInitializationError
createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr, const char * jarfile, jboolean printWarning) {
    JPLISInitializationError initerror       = JPLIS_INIT_ERROR_NONE;
    jvmtiEnv *               jvmtienv        = NULL;
    jint                     jnierror        = JNI_OK;

    *agent_ptr = NULL;
    jnierror = (*vm)->GetEnv(  vm,
                               (void **) &jvmtienv,
                               JVMTI_VERSION_1_1);
    if ( jnierror != JNI_OK ) {
        initerror = JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT;
    } else {
        JPLISAgent * agent = allocateJPLISAgent(jvmtienv);
        if ( agent == NULL ) {
            initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE;
        } else {
            initerror = initializeJPLISAgent(  agent,
                                               vm,
                                               jvmtienv,
                                               jarfile,
                                               printWarning);
            if ( initerror == JPLIS_INIT_ERROR_NONE ) {
                *agent_ptr = agent;
            } else {
                deallocateJPLISAgent(jvmtienv, agent);
            }
        }

        /* don't leak envs */
        if ( initerror != JPLIS_INIT_ERROR_NONE ) {
            jvmtiError jvmtierror = (*jvmtienv)->DisposeEnvironment(jvmtienv);
            /* can be called from any phase */
            jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
        }
    }

    return initerror;
}

/*
 *  Allocates a JPLISAgent. Returns NULL if it cannot be allocated
 */
JPLISAgent *
allocateJPLISAgent(jvmtiEnv * jvmtienv) {
  return (JPLISAgent *) allocate( jvmtienv,
                                    sizeof(JPLISAgent));
}

JPLISInitializationError
initializeJPLISAgent(   JPLISAgent *    agent,
                        JavaVM *        vm,
                        jvmtiEnv *      jvmtienv,
                        const char *    jarfile,
                        jboolean        printWarning) {
    jvmtiError      jvmtierror = JVMTI_ERROR_NONE;
    jvmtiPhase      phase;

    agent->mJVM                                      = vm;
    agent->mNormalEnvironment.mJVMTIEnv              = jvmtienv;
    agent->mNormalEnvironment.mAgent                 = agent;
    agent->mNormalEnvironment.mIsRetransformer       = JNI_FALSE;
    agent->mRetransformEnvironment.mJVMTIEnv         = NULL;        /* NULL until needed */
    agent->mRetransformEnvironment.mAgent            = agent;
    agent->mRetransformEnvironment.mIsRetransformer  = JNI_FALSE;   /* JNI_FALSE until mJVMTIEnv is set */
    agent->mAgentmainCaller                          = NULL;
    agent->mInstrumentationImpl                      = NULL;
    agent->mPremainCaller                            = NULL;
    agent->mTransform                                = NULL;
    agent->mRedefineAvailable                        = JNI_FALSE;   /* assume no for now */
    agent->mRedefineAdded                            = JNI_FALSE;
    agent->mNativeMethodPrefixAvailable              = JNI_FALSE;   /* assume no for now */
    agent->mNativeMethodPrefixAdded                  = JNI_FALSE;
    agent->mAgentClassName                           = NULL;
    agent->mOptionsString                            = NULL;
    agent->mJarfile                                  = jarfile;
    agent->mPrintWarning                             = printWarning;

    /* make sure we can recover either handle in either direction.
     * the agent has a ref to the jvmti; make it mutual
     */
    jvmtierror = (*jvmtienv)->SetEnvironmentLocalStorage(
                                            jvmtienv,
                                            &(agent->mNormalEnvironment));
    /* can be called from any phase */
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);

    /* check what capabilities are available */
    checkCapabilities(agent);

    /* check phase - if live phase then we don't need the VMInit event */
    jvmtierror = (*jvmtienv)->GetPhase(jvmtienv, &phase);
    /* can be called from any phase */
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    if (phase == JVMTI_PHASE_LIVE) {
        return JPLIS_INIT_ERROR_NONE;
    }

    if (phase != JVMTI_PHASE_ONLOAD) {
        /* called too early or called too late; either way bail out */
        return JPLIS_INIT_ERROR_FAILURE;
    }

    /* now turn on the VMInit event */
    if ( jvmtierror == JVMTI_ERROR_NONE ) {
        jvmtiEventCallbacks callbacks;
        memset(&callbacks, 0, sizeof(callbacks));
        callbacks.VMInit = &eventHandlerVMInit;

        jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv,
                                                     &callbacks,
                                                     sizeof(callbacks));
        check_phase_ret_blob(jvmtierror, JPLIS_INIT_ERROR_FAILURE);
        jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    }

    if ( jvmtierror == JVMTI_ERROR_NONE ) {
        jvmtierror = (*jvmtienv)->SetEventNotificationMode(
                                                jvmtienv,
                                                JVMTI_ENABLE,
                                                JVMTI_EVENT_VM_INIT,
                                                NULL /* all threads */);
        check_phase_ret_blob(jvmtierror, JPLIS_INIT_ERROR_FAILURE);
        jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    }

    return (jvmtierror == JVMTI_ERROR_NONE)? JPLIS_INIT_ERROR_NONE : JPLIS_INIT_ERROR_FAILURE;
}

void
deallocateJPLISAgent(jvmtiEnv * jvmtienv, JPLISAgent * agent) {
    deallocate(jvmtienv, agent);
}


JPLISInitializationError
recordCommandLineData(  JPLISAgent *    agent,
                        const char *    agentClassName,
                        const char *    optionsString ) {
    JPLISInitializationError    initerror   = JPLIS_INIT_ERROR_NONE;
    char *      ourCopyOfAgentClassName     = NULL;
    char *      ourCopyOfOptionsString      = NULL;

    /* if no actual params, bail out now */
    if ((agentClassName == NULL) || (*agentClassName == 0)) {
        initerror = JPLIS_INIT_ERROR_AGENT_CLASS_NOT_SPECIFIED;
    } else {
        ourCopyOfAgentClassName = allocate(jvmti(agent), strlen(agentClassName)+1);
        if (ourCopyOfAgentClassName == NULL) {
            initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE;
        } else {
            if (optionsString != NULL) {
                ourCopyOfOptionsString = allocate(jvmti(agent), strlen(optionsString)+1);
                if (ourCopyOfOptionsString == NULL) {
                    deallocate(jvmti(agent), ourCopyOfAgentClassName);
                    initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE;
                }
            }
        }
    }

    if (initerror == JPLIS_INIT_ERROR_NONE) {
        strcpy(ourCopyOfAgentClassName, agentClassName);
        if (optionsString != NULL) {
            strcpy(ourCopyOfOptionsString, optionsString);
        }
        agent->mAgentClassName = ourCopyOfAgentClassName;
        agent->mOptionsString = ourCopyOfOptionsString;
    }

    return initerror;
}

/*
 *  VMInit processing code.
 */


/*
 * If this call fails, the JVM launch will ultimately be aborted,
 * so we don't have to be super-careful to clean up in partial failure
 * cases.
 */
jboolean
processJavaStart(   JPLISAgent *    agent,
                    JNIEnv *        jnienv) {
    jboolean    result;

    /*
     *  OK, Java is up now. We can start everything that needs Java.
     */

    /*
     *  First make our fallback InternalError throwable.
     */
    result = initializeFallbackError(jnienv);
    jplis_assert_msg(result, "fallback init failed");

    /*
     *  Now make the InstrumentationImpl instance.
     */
    if ( result ) {
        result = createInstrumentationImpl(jnienv, agent);
        jplis_assert_msg(result, "instrumentation instance creation failed");
    }


    /*
     *  Register a handler for ClassFileLoadHook (without enabling this event).
     *  Turn off the VMInit handler.
     */
    if ( result ) {
        result = setLivePhaseEventHandlers(agent);
        jplis_assert_msg(result, "setting of live phase VM handlers failed");
    }

    /*
     *  Load the Java agent, and call the premain.
     */
    if ( result ) {
        result = startJavaAgent(agent, jnienv,
                                agent->mAgentClassName, agent->mOptionsString,
                                agent->mPremainCaller);
        jplis_assert_msg(result, "agent load/premain call failed");
    }

    /*
     * Finally surrender all of the tracking data that we don't need any more.
     * If something is wrong, skip it, we will be aborting the JVM anyway.
     */
    if ( result ) {
        deallocateCommandLineData(agent);
    }

    return result;
}

jboolean
startJavaAgent( JPLISAgent *    agent,
                JNIEnv *        jnienv,
                const char *    classname,
                const char *    optionsString,
                jmethodID       agentMainMethod) {
    jboolean    success = JNI_FALSE;
    jstring classNameObject = NULL;
    jstring optionsStringObject = NULL;

    success = commandStringIntoJavaStrings(    jnienv,
                                               classname,
                                               optionsString,
                                               &classNameObject,
                                               &optionsStringObject);
    jplis_assert_msg(success, "commandStringIntoJavaStrings failed");

    if (success) {
        success = invokeJavaAgentMainMethod(   jnienv,
                                               agent->mInstrumentationImpl,
                                               agentMainMethod,
                                               classNameObject,
                                               optionsStringObject);
        jplis_assert_msg(success, "invokeJavaAgentMainMethod failed");
    }

    return success;
}

void
deallocateCommandLineData( JPLISAgent * agent) {
    deallocate(jvmti(agent), (void*)agent->mAgentClassName);
    deallocate(jvmti(agent), (void*)agent->mOptionsString);

    /* zero things out so it is easier to see what is going on */
    agent->mAgentClassName = NULL;
    agent->mOptionsString = NULL;
}

/*
 * Create the java.lang.instrument.Instrumentation instance
 * and access information for it (method IDs, etc)
 */
jboolean
createInstrumentationImpl( JNIEnv *        jnienv,
                           JPLISAgent *    agent) {
    jclass      implClass               = NULL;
    jboolean    errorOutstanding        = JNI_FALSE;
    jobject     resultImpl              = NULL;
    jmethodID   premainCallerMethodID   = NULL;
    jmethodID   agentmainCallerMethodID = NULL;
    jmethodID   transformMethodID       = NULL;
    jmethodID   constructorID           = NULL;
    jobject     localReference          = NULL;

    /* First find the class of our implementation */
    implClass = (*jnienv)->FindClass(   jnienv,
                                        JPLIS_INSTRUMENTIMPL_CLASSNAME);
    errorOutstanding = checkForAndClearThrowable(jnienv);
    errorOutstanding = errorOutstanding || (implClass == NULL);
    jplis_assert_msg(!errorOutstanding, "find class on InstrumentationImpl failed");

    if ( !errorOutstanding ) {
        constructorID = (*jnienv)->GetMethodID( jnienv,
                                                implClass,
                                                JPLIS_INSTRUMENTIMPL_CONSTRUCTOR_METHODNAME,
                                                JPLIS_INSTRUMENTIMPL_CONSTRUCTOR_METHODSIGNATURE);
        errorOutstanding = checkForAndClearThrowable(jnienv);
        errorOutstanding = errorOutstanding || (constructorID == NULL);
        jplis_assert_msg(!errorOutstanding, "find constructor on InstrumentationImpl failed");
        }

    if ( !errorOutstanding ) {
        jlong   peerReferenceAsScalar = (jlong)(intptr_t) agent;
        localReference = (*jnienv)->NewObject(  jnienv,
                                                implClass,
                                                constructorID,
                                                peerReferenceAsScalar,
                                                agent->mRedefineAdded,
                                                agent->mNativeMethodPrefixAdded,
                                                agent->mPrintWarning);
        errorOutstanding = checkForAndClearThrowable(jnienv);
        errorOutstanding = errorOutstanding || (localReference == NULL);
        jplis_assert_msg(!errorOutstanding, "call constructor on InstrumentationImpl failed");
    }

    if ( !errorOutstanding ) {
        resultImpl = (*jnienv)->NewGlobalRef(jnienv, localReference);
        errorOutstanding = checkForAndClearThrowable(jnienv);
        jplis_assert_msg(!errorOutstanding, "copy local ref to global ref");
    }

    /* Now look up the method ID for the pre-main caller (we will need this more than once) */
    if ( !errorOutstanding ) {
        premainCallerMethodID = (*jnienv)->GetMethodID( jnienv,
                                                        implClass,
                                                        JPLIS_INSTRUMENTIMPL_PREMAININVOKER_METHODNAME,
                                                        JPLIS_INSTRUMENTIMPL_PREMAININVOKER_METHODSIGNATURE);
        errorOutstanding = checkForAndClearThrowable(jnienv);
        errorOutstanding = errorOutstanding || (premainCallerMethodID == NULL);
        jplis_assert_msg(!errorOutstanding, "can't find premain invoker methodID");
    }

    /* Now look up the method ID for the agent-main caller */
    if ( !errorOutstanding ) {
        agentmainCallerMethodID = (*jnienv)->GetMethodID( jnienv,
                                                          implClass,
                                                          JPLIS_INSTRUMENTIMPL_AGENTMAININVOKER_METHODNAME,
                                                          JPLIS_INSTRUMENTIMPL_AGENTMAININVOKER_METHODSIGNATURE);
        errorOutstanding = checkForAndClearThrowable(jnienv);
        errorOutstanding = errorOutstanding || (agentmainCallerMethodID == NULL);
        jplis_assert_msg(!errorOutstanding, "can't find agentmain invoker methodID");
    }

    /* Now look up the method ID for the transform method (we will need this constantly) */
    if ( !errorOutstanding ) {
        transformMethodID = (*jnienv)->GetMethodID( jnienv,
                                                    implClass,
                                                    JPLIS_INSTRUMENTIMPL_TRANSFORM_METHODNAME,
                                                    JPLIS_INSTRUMENTIMPL_TRANSFORM_METHODSIGNATURE);
        errorOutstanding = checkForAndClearThrowable(jnienv);
        errorOutstanding = errorOutstanding || (transformMethodID == NULL);
        jplis_assert_msg(!errorOutstanding, "can't find transform methodID");
    }

    if ( !errorOutstanding ) {
        agent->mInstrumentationImpl = resultImpl;
        agent->mPremainCaller       = premainCallerMethodID;
        agent->mAgentmainCaller     = agentmainCallerMethodID;
        agent->mTransform           = transformMethodID;
    }

    return !errorOutstanding;
}

jboolean
commandStringIntoJavaStrings(  JNIEnv *        jnienv,
                               const char *    classname,
                               const char *    optionsString,
                               jstring *       outputClassname,
                               jstring *       outputOptionsString) {
    jstring     classnameJavaString     = NULL;
    jstring     optionsJavaString       = NULL;
    jboolean    errorOutstanding        = JNI_TRUE;

    classnameJavaString = (*jnienv)->NewStringUTF(jnienv, classname);
    errorOutstanding = checkForAndClearThrowable(jnienv);
    jplis_assert_msg(!errorOutstanding, "can't create class name java string");

    if ( !errorOutstanding ) {
        if ( optionsString != NULL) {
            optionsJavaString = (*jnienv)->NewStringUTF(jnienv, optionsString);
            errorOutstanding = checkForAndClearThrowable(jnienv);
            jplis_assert_msg(!errorOutstanding, "can't create options java string");
        }

        if ( !errorOutstanding ) {
            *outputClassname        = classnameJavaString;
            *outputOptionsString    = optionsJavaString;
        }
    }

    return !errorOutstanding;
}


jboolean
invokeJavaAgentMainMethod( JNIEnv *    jnienv,
                           jobject     instrumentationImpl,
                           jmethodID   mainCallingMethod,
                           jstring     className,
                           jstring     optionsString) {
    jboolean errorOutstanding = JNI_FALSE;

    jplis_assert(mainCallingMethod != NULL);
    if ( mainCallingMethod != NULL ) {
        (*jnienv)->CallVoidMethod(  jnienv,
                                    instrumentationImpl,
                                    mainCallingMethod,
                                    className,
                                    optionsString);
        errorOutstanding = checkForThrowable(jnienv);
        if ( errorOutstanding ) {
            logThrowable(jnienv);
            jplis_assert_msg(!errorOutstanding, "Outstanding error when calling method in invokeJavaAgentMainMethod");
        }
        checkForAndClearThrowable(jnienv);
    }
    return !errorOutstanding;
}

jboolean
setLivePhaseEventHandlers(  JPLISAgent * agent) {
    jvmtiEventCallbacks callbacks;
    jvmtiEnv *          jvmtienv = jvmti(agent);
    jvmtiError          jvmtierror;

    /* first swap out the handlers (switch from the VMInit handler, which we do not need,
     * to the ClassFileLoadHook handler, which is what the agents need from now on)
     */
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;

    jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv,
                                                 &callbacks,
                                                 sizeof(callbacks));
    check_phase_ret_false(jvmtierror);
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);


    if ( jvmtierror == JVMTI_ERROR_NONE ) {
        /* turn off VMInit */
        jvmtierror = (*jvmtienv)->SetEventNotificationMode(
                                                    jvmtienv,
                                                    JVMTI_DISABLE,
                                                    JVMTI_EVENT_VM_INIT,
                                                    NULL /* all threads */);
        check_phase_ret_false(jvmtierror);
        jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    }

    return (jvmtierror == JVMTI_ERROR_NONE);
}

/**
 *  Check if the can_redefine_classes capability is available.
 */
void
checkCapabilities(JPLISAgent * agent) {
    jvmtiEnv *          jvmtienv = jvmti(agent);
    jvmtiCapabilities   potentialCapabilities;
    jvmtiError          jvmtierror;

    memset(&potentialCapabilities, 0, sizeof(potentialCapabilities));

    jvmtierror = (*jvmtienv)->GetPotentialCapabilities(jvmtienv, &potentialCapabilities);
    check_phase_ret(jvmtierror);
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);

    if ( jvmtierror == JVMTI_ERROR_NONE ) {
        if ( potentialCapabilities.can_redefine_classes == 1 ) {
            agent->mRedefineAvailable = JNI_TRUE;
        }
        if ( potentialCapabilities.can_set_native_method_prefix == 1 ) {
            agent->mNativeMethodPrefixAvailable = JNI_TRUE;
        }
    }
}

/**
 * Enable native method prefix in one JVM TI environment
 */
void
enableNativeMethodPrefixCapability(jvmtiEnv * jvmtienv) {
    jvmtiCapabilities   desiredCapabilities;
    jvmtiError          jvmtierror;

        jvmtierror = (*jvmtienv)->GetCapabilities(jvmtienv, &desiredCapabilities);
        /* can be called from any phase */
        jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
        desiredCapabilities.can_set_native_method_prefix = 1;
        jvmtierror = (*jvmtienv)->AddCapabilities(jvmtienv, &desiredCapabilities);
        check_phase_ret(jvmtierror);
        jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}


/**
 * Add the can_set_native_method_prefix capability
 */
void
addNativeMethodPrefixCapability(JPLISAgent * agent) {
    if (agent->mNativeMethodPrefixAvailable && !agent->mNativeMethodPrefixAdded) {
        jvmtiEnv * jvmtienv = agent->mNormalEnvironment.mJVMTIEnv;
        enableNativeMethodPrefixCapability(jvmtienv);

        jvmtienv = agent->mRetransformEnvironment.mJVMTIEnv;
        if (jvmtienv != NULL) {
            enableNativeMethodPrefixCapability(jvmtienv);
        }
        agent->mNativeMethodPrefixAdded = JNI_TRUE;
    }
}

/**
 * Add the can_maintain_original_method_order capability (for testing)
 */
void
addOriginalMethodOrderCapability(JPLISAgent * agent) {
    jvmtiEnv *          jvmtienv = jvmti(agent);
    jvmtiCapabilities   desiredCapabilities;
    jvmtiError          jvmtierror;

    jvmtierror = (*jvmtienv)->GetCapabilities(jvmtienv, &desiredCapabilities);
    /* can be called from any phase */
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    desiredCapabilities.can_maintain_original_method_order = 1;
    jvmtierror = (*jvmtienv)->AddCapabilities(jvmtienv, &desiredCapabilities);
    check_phase_ret(jvmtierror);
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}

/**
 * Add the can_redefine_classes capability
 */
void
addRedefineClassesCapability(JPLISAgent * agent) {
    jvmtiEnv *          jvmtienv = jvmti(agent);
    jvmtiCapabilities   desiredCapabilities;
    jvmtiError          jvmtierror;

    if (agent->mRedefineAvailable && !agent->mRedefineAdded) {
        jvmtierror = (*jvmtienv)->GetCapabilities(jvmtienv, &desiredCapabilities);
        /* can be called from any phase */
        jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
        desiredCapabilities.can_redefine_classes = 1;
        jvmtierror = (*jvmtienv)->AddCapabilities(jvmtienv, &desiredCapabilities);
        check_phase_ret(jvmtierror);

        /*
         * With mixed premain/agentmain agents then it's possible that the
         * capability was potentially available in the onload phase but
         * subsequently unavailable in the live phase.
         */
        jplis_assert(jvmtierror == JVMTI_ERROR_NONE ||
                     jvmtierror == JVMTI_ERROR_NOT_AVAILABLE);
        if (jvmtierror == JVMTI_ERROR_NONE) {
            agent->mRedefineAdded = JNI_TRUE;
        }
    }
}

static jobject
getModuleObject(jvmtiEnv*               jvmti,
                jobject                 loaderObject,
                const char*             cname) {
    jvmtiError err = JVMTI_ERROR_NONE;
    jobject moduleObject = NULL;

    /* find last slash in the class name */
    char* last_slash = (cname == NULL) ? NULL : strrchr(cname, '/');
    int len = (last_slash == NULL) ? 0 : (int)(last_slash - cname);
    char* pkg_name_buf = (char*)malloc(len + 1);

    if (pkg_name_buf == NULL) {
        fprintf(stderr, "OOM error in native tmp buffer allocation");
        return NULL;
    }
    if (last_slash != NULL) {
        strncpy(pkg_name_buf, cname, len);
    }
    pkg_name_buf[len] = '\0';

    err = (*jvmti)->GetNamedModule(jvmti, loaderObject, pkg_name_buf, &moduleObject);
    free((void*)pkg_name_buf);
    check_phase_ret_blob(err, NULL);
    jplis_assert_msg(err == JVMTI_ERROR_NONE, "error in the JVMTI GetNamedModule");

    return moduleObject;
}

/*
 *  Support for the JVMTI callbacks
 */

void
transformClassFile(             JPLISAgent *            agent,
                                JNIEnv *                jnienv,
                                jobject                 loaderObject,
                                const char*             name,
                                jclass                  classBeingRedefined,
                                jobject                 protectionDomain,
                                jint                    class_data_len,
                                const unsigned char*    class_data,
                                jint*                   new_class_data_len,
                                unsigned char**         new_class_data,
                                jboolean                is_retransformer) {
    jboolean        errorOutstanding        = JNI_FALSE;
    jstring         classNameStringObject   = NULL;
    jarray          classFileBufferObject   = NULL;
    jarray          transformedBufferObject = NULL;
    jsize           transformedBufferSize   = 0;
    unsigned char * resultBuffer            = NULL;
    jboolean        shouldRun               = JNI_FALSE;

    /* only do this if we aren't already in the middle of processing a class on this thread */
    shouldRun = tryToAcquireReentrancyToken(
                                jvmti(agent),
                                NULL);  /* this thread */

    if ( shouldRun ) {
        /* first marshall all the parameters */
        classNameStringObject = (*jnienv)->NewStringUTF(jnienv,
                                                        name);
        errorOutstanding = checkForAndClearThrowable(jnienv);
        jplis_assert_msg(!errorOutstanding, "can't create name string");

        if ( !errorOutstanding ) {
            classFileBufferObject = (*jnienv)->NewByteArray(jnienv,
                                                            class_data_len);
            errorOutstanding = checkForAndClearThrowable(jnienv);
            jplis_assert_msg(!errorOutstanding, "can't create byte array");
        }

        if ( !errorOutstanding ) {
            jbyte * typedBuffer = (jbyte *) class_data; /* nasty cast, dumb JNI interface, const missing */
                                                        /* The sign cast is safe. The const cast is dumb. */
            (*jnienv)->SetByteArrayRegion(  jnienv,
                                            classFileBufferObject,
                                            0,
                                            class_data_len,
                                            typedBuffer);
            errorOutstanding = checkForAndClearThrowable(jnienv);
            jplis_assert_msg(!errorOutstanding, "can't set byte array region");
        }

        /*  now call the JPL agents to do the transforming */
        /*  potential future optimization: may want to skip this if there are none */
        if ( !errorOutstanding ) {
            jobject moduleObject = NULL;

            if (classBeingRedefined == NULL) {
                moduleObject = getModuleObject(jvmti(agent), loaderObject, name);
            } else {
                // Redefine or retransform, InstrumentationImpl.transform() will use
                // classBeingRedefined.getModule() to get the module.
            }
            jplis_assert(agent->mInstrumentationImpl != NULL);
            jplis_assert(agent->mTransform != NULL);
            transformedBufferObject = (*jnienv)->CallObjectMethod(
                                                jnienv,
                                                agent->mInstrumentationImpl,
                                                agent->mTransform,
                                                moduleObject,
                                                loaderObject,
                                                classNameStringObject,
                                                classBeingRedefined,
                                                protectionDomain,
                                                classFileBufferObject,
                                                is_retransformer);
            errorOutstanding = checkForAndClearThrowable(jnienv);
            jplis_assert_msg(!errorOutstanding, "transform method call failed");
        }

        /* Finally, unmarshall the parameters (if someone touched the buffer, tell the JVM) */
        if ( !errorOutstanding ) {
            if ( transformedBufferObject != NULL ) {
                transformedBufferSize = (*jnienv)->GetArrayLength(  jnienv,
                                                                    transformedBufferObject);
                errorOutstanding = checkForAndClearThrowable(jnienv);
                jplis_assert_msg(!errorOutstanding, "can't get array length");

                if ( !errorOutstanding ) {
                    /* allocate the response buffer with the JVMTI allocate call.
                     *  This is what the JVMTI spec says to do for Class File Load hook responses
                     */
                    jvmtiError  allocError = (*(jvmti(agent)))->Allocate(jvmti(agent),
                                                                             transformedBufferSize,
                                                                             &resultBuffer);
                    errorOutstanding = (allocError != JVMTI_ERROR_NONE);
                    jplis_assert_msg(!errorOutstanding, "can't allocate result buffer");
                }

                if ( !errorOutstanding ) {
                    (*jnienv)->GetByteArrayRegion(  jnienv,
                                                    transformedBufferObject,
                                                    0,
                                                    transformedBufferSize,
                                                    (jbyte *) resultBuffer);
                    errorOutstanding = checkForAndClearThrowable(jnienv);
                    jplis_assert_msg(!errorOutstanding, "can't get byte array region");

                    /* in this case, we will not return the buffer to the JVMTI,
                     * so we need to deallocate it ourselves
                     */
                    if ( errorOutstanding ) {
                        deallocate( jvmti(agent),
                                   (void*)resultBuffer);
                    }
                }

                if ( !errorOutstanding ) {
                    *new_class_data_len = (transformedBufferSize);
                    *new_class_data     = resultBuffer;
                }
            }
        }

        /* release the token */
        releaseReentrancyToken( jvmti(agent),
                                NULL);      /* this thread */

    }

    return;
}

/*
 *  Misc. internal utilities.
 */

/*
 *  The only checked exceptions we can throw are ClassNotFoundException and
 *  UnmodifiableClassException. All others map to InternalError.
 */
jthrowable
redefineClassMapper(    JNIEnv *    jnienv,
                        jthrowable  throwableToMap) {
    jthrowable  mappedThrowable = NULL;

    jplis_assert(isSafeForJNICalls(jnienv));
    jplis_assert(!isUnchecked(jnienv, throwableToMap));

    if ( isInstanceofClassName( jnienv,
                                throwableToMap,
                                "java/lang/ClassNotFoundException") ) {
        mappedThrowable = throwableToMap;
    } else {
        if ( isInstanceofClassName( jnienv,
                                throwableToMap,
                                "java/lang/instrument/UnmodifiableClassException")) {
            mappedThrowable = throwableToMap;
        } else {
            jstring message = NULL;

            message = getMessageFromThrowable(jnienv, throwableToMap);
            mappedThrowable = createInternalError(jnienv, message);
        }
    }

    jplis_assert(isSafeForJNICalls(jnienv));
    return mappedThrowable;
}

jobjectArray
getObjectArrayFromClasses(JNIEnv* jnienv, jclass* classes, jint classCount) {
    jclass          classArrayClass = NULL;
    jobjectArray    localArray      = NULL;
    jint            classIndex      = 0;
    jboolean        errorOccurred   = JNI_FALSE;

    /* get the class array class */
    classArrayClass = (*jnienv)->FindClass(jnienv, "java/lang/Class");
    errorOccurred = checkForThrowable(jnienv);

    if (!errorOccurred) {
        jplis_assert_msg(classArrayClass != NULL, "FindClass returned null class");

        /* create the array for the classes */
        localArray = (*jnienv)->NewObjectArray(jnienv, classCount, classArrayClass, NULL);
        errorOccurred = checkForThrowable(jnienv);

        if (!errorOccurred) {
            jplis_assert_msg(localArray != NULL, "NewObjectArray returned null array");

            /* now copy refs to all the classes and put them into the array */
            for (classIndex = 0; classIndex < classCount; classIndex++) {
                /* put class into array */
                (*jnienv)->SetObjectArrayElement(jnienv, localArray, classIndex, classes[classIndex]);
                errorOccurred = checkForThrowable(jnienv);

                if (errorOccurred) {
                    localArray = NULL;
                    break;
                }
            }
        }
    }

    return localArray;
}


/* Return the environment with the retransformation capability.
 * Create it if it doesn't exist.
 * Return NULL if it can't be created.
 */
jvmtiEnv *
retransformableEnvironment(JPLISAgent * agent) {
    jvmtiEnv *          retransformerEnv     = NULL;
    jint                jnierror             = JNI_OK;
    jvmtiCapabilities   desiredCapabilities;
    jvmtiEventCallbacks callbacks;
    jvmtiError          jvmtierror;

    if (agent->mRetransformEnvironment.mJVMTIEnv != NULL) {
        return agent->mRetransformEnvironment.mJVMTIEnv;
    }
    jnierror = (*agent->mJVM)->GetEnv(  agent->mJVM,
                               (void **) &retransformerEnv,
                               JVMTI_VERSION_1_1);
    if ( jnierror != JNI_OK ) {
        return NULL;
    }
    jvmtierror = (*retransformerEnv)->GetCapabilities(retransformerEnv, &desiredCapabilities);
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    desiredCapabilities.can_retransform_classes = 1;
    if (agent->mNativeMethodPrefixAdded) {
        desiredCapabilities.can_set_native_method_prefix = 1;
    }

    jvmtierror = (*retransformerEnv)->AddCapabilities(retransformerEnv, &desiredCapabilities);
    if (jvmtierror != JVMTI_ERROR_NONE) {
         /* cannot get the capability, dispose of the retransforming environment */
        jvmtierror = (*retransformerEnv)->DisposeEnvironment(retransformerEnv);
        jplis_assert(jvmtierror == JVMTI_ERROR_NOT_AVAILABLE);
        return NULL;
    }
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;

    jvmtierror = (*retransformerEnv)->SetEventCallbacks(retransformerEnv,
                                                        &callbacks,
                                                        sizeof(callbacks));
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    if (jvmtierror == JVMTI_ERROR_NONE) {
        // install the retransforming environment
        agent->mRetransformEnvironment.mJVMTIEnv = retransformerEnv;
        agent->mRetransformEnvironment.mIsRetransformer = JNI_TRUE;

        // Make it for ClassFileLoadHook handling
        jvmtierror = (*retransformerEnv)->SetEnvironmentLocalStorage(
                                                       retransformerEnv,
                                                       &(agent->mRetransformEnvironment));
        jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
        if (jvmtierror == JVMTI_ERROR_NONE) {
            return retransformerEnv;
        }
    }
    return NULL;
}


/*
 *  Underpinnings for native methods
 */

jboolean
isModifiableClass(JNIEnv * jnienv, JPLISAgent * agent, jclass clazz) {
    jvmtiEnv *          jvmtienv = jvmti(agent);
    jvmtiError          jvmtierror;
    jboolean            is_modifiable = JNI_FALSE;

    jvmtierror = (*jvmtienv)->IsModifiableClass( jvmtienv,
                                                 clazz,
                                                 &is_modifiable);
    check_phase_ret_false(jvmtierror);
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);

    return is_modifiable;
}

jboolean
isRetransformClassesSupported(JNIEnv * jnienv, JPLISAgent * agent) {
    return agent->mRetransformEnvironment.mIsRetransformer;
}

void
setHasTransformers(JNIEnv * jnienv, JPLISAgent * agent, jboolean has) {
    jvmtiEnv *          jvmtienv = jvmti(agent);
    jvmtiError          jvmtierror;

    jplis_assert(jvmtienv != NULL);
    jvmtierror = (*jvmtienv)->SetEventNotificationMode(
                                            jvmtienv,
                                            has? JVMTI_ENABLE : JVMTI_DISABLE,
                                            JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
                                            NULL /* all threads */);
    check_phase_ret(jvmtierror);
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}

void
setHasRetransformableTransformers(JNIEnv * jnienv, JPLISAgent * agent, jboolean has) {
    jvmtiEnv *          retransformerEnv     = retransformableEnvironment(agent);
    jvmtiError          jvmtierror;

    jplis_assert(retransformerEnv != NULL);
    jvmtierror = (*retransformerEnv)->SetEventNotificationMode(
                                                    retransformerEnv,
                                                    has? JVMTI_ENABLE : JVMTI_DISABLE,
                                                    JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
                                                    NULL /* all threads */);
    check_phase_ret(jvmtierror);
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}

void
retransformClasses(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray classes) {
    jvmtiEnv *  retransformerEnv     = retransformableEnvironment(agent);
    jboolean    errorOccurred        = JNI_FALSE;
    jvmtiError  errorCode            = JVMTI_ERROR_NONE;
    jsize       numClasses           = 0;
    jclass *    classArray           = NULL;

    /* This is supposed to be checked by caller, but just to be sure */
    if (retransformerEnv == NULL) {
        jplis_assert(retransformerEnv != NULL);
        errorOccurred = JNI_TRUE;
        errorCode = JVMTI_ERROR_MUST_POSSESS_CAPABILITY;
    }

    /* This was supposed to be checked by caller too */
    if (!errorOccurred && classes == NULL) {
        jplis_assert(classes != NULL);
        errorOccurred = JNI_TRUE;
        errorCode = JVMTI_ERROR_NULL_POINTER;
    }

    if (!errorOccurred) {
        numClasses = (*jnienv)->GetArrayLength(jnienv, classes);
        errorOccurred = checkForThrowable(jnienv);
        jplis_assert(!errorOccurred);

        if (!errorOccurred && numClasses == 0) {
            jplis_assert(numClasses != 0);
            errorOccurred = JNI_TRUE;
            errorCode = JVMTI_ERROR_NULL_POINTER;
        }
    }

    if (!errorOccurred) {
        classArray = (jclass *) allocate(retransformerEnv,
                                         numClasses * sizeof(jclass));
        errorOccurred = (classArray == NULL);
        jplis_assert(!errorOccurred);
        if (errorOccurred) {
            errorCode = JVMTI_ERROR_OUT_OF_MEMORY;
        }
    }

    if (!errorOccurred) {
        jint index;
        for (index = 0; index < numClasses; index++) {
            classArray[index] = (*jnienv)->GetObjectArrayElement(jnienv, classes, index);
            errorOccurred = checkForThrowable(jnienv);
            jplis_assert(!errorOccurred);
            if (errorOccurred) {
                break;
            }

            if (classArray[index] == NULL) {
                jplis_assert(classArray[index] != NULL);
                errorOccurred = JNI_TRUE;
                errorCode = JVMTI_ERROR_NULL_POINTER;
                break;
            }
        }
    }

    if (!errorOccurred) {
        errorCode = (*retransformerEnv)->RetransformClasses(retransformerEnv,
                                                            numClasses, classArray);
        errorOccurred = (errorCode != JVMTI_ERROR_NONE);
    }

    /* Give back the buffer if we allocated it.  Throw any exceptions after.
     */
    if (classArray != NULL) {
        deallocate(retransformerEnv, (void*)classArray);
    }

    /* Return back if we executed the JVMTI API in a wrong phase
     */
    check_phase_ret(errorCode);

    if (errorCode != JVMTI_ERROR_NONE) {
        createAndThrowThrowableFromJVMTIErrorCode(jnienv, errorCode);
    }

    mapThrownThrowableIfNecessary(jnienv, redefineClassMapper);
}

/*
 *  Java code must not call this with a null list or a zero-length list.
 */
void
redefineClasses(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray classDefinitions) {
    jvmtiEnv*   jvmtienv                        = jvmti(agent);
    jboolean    errorOccurred                   = JNI_FALSE;
    jclass      classDefClass                   = NULL;
    jmethodID   getDefinitionClassMethodID      = NULL;
    jmethodID   getDefinitionClassFileMethodID  = NULL;
    jvmtiClassDefinition* classDefs             = NULL;
    jbyteArray* targetFiles                     = NULL;
    jsize       numDefs                         = 0;

    jplis_assert(classDefinitions != NULL);

    numDefs = (*jnienv)->GetArrayLength(jnienv, classDefinitions);
    errorOccurred = checkForThrowable(jnienv);
    jplis_assert(!errorOccurred);

    if (!errorOccurred) {
        jplis_assert(numDefs > 0);
        /* get method IDs for methods to call on class definitions */
        classDefClass = (*jnienv)->FindClass(jnienv, "java/lang/instrument/ClassDefinition");
        errorOccurred = checkForThrowable(jnienv);
        jplis_assert(!errorOccurred);
    }

    if (!errorOccurred) {
        getDefinitionClassMethodID = (*jnienv)->GetMethodID(    jnienv,
                                                classDefClass,
                                                "getDefinitionClass",
                                                "()Ljava/lang/Class;");
        errorOccurred = checkForThrowable(jnienv);
        jplis_assert(!errorOccurred);
    }

    if (!errorOccurred) {
        getDefinitionClassFileMethodID = (*jnienv)->GetMethodID(    jnienv,
                                                    classDefClass,
                                                    "getDefinitionClassFile",
                                                    "()[B");
        errorOccurred = checkForThrowable(jnienv);
        jplis_assert(!errorOccurred);
    }

    if (!errorOccurred) {
        classDefs = (jvmtiClassDefinition *) allocate(
                                                jvmtienv,
                                                numDefs * sizeof(jvmtiClassDefinition));
        errorOccurred = (classDefs == NULL);
        jplis_assert(!errorOccurred);
        if ( errorOccurred ) {
            createAndThrowThrowableFromJVMTIErrorCode(jnienv, JVMTI_ERROR_OUT_OF_MEMORY);
        }

        else {
            /*
             * We have to save the targetFile values that we compute so
             * that we can release the class_bytes arrays that are
             * returned by GetByteArrayElements(). In case of a JNI
             * error, we can't (easily) recompute the targetFile values
             * and we still want to free any memory we allocated.
             */
            targetFiles = (jbyteArray *) allocate(jvmtienv,
                                                  numDefs * sizeof(jbyteArray));
            errorOccurred = (targetFiles == NULL);
            jplis_assert(!errorOccurred);
            if ( errorOccurred ) {
                deallocate(jvmtienv, (void*)classDefs);
                createAndThrowThrowableFromJVMTIErrorCode(jnienv,
                    JVMTI_ERROR_OUT_OF_MEMORY);
            }
            else {
                jint i, j;

                // clear classDefs so we can correctly free memory during errors
                memset(classDefs, 0, numDefs * sizeof(jvmtiClassDefinition));

                for (i = 0; i < numDefs; i++) {
                    jclass      classDef    = NULL;

                    classDef = (*jnienv)->GetObjectArrayElement(jnienv, classDefinitions, i);
                    errorOccurred = checkForThrowable(jnienv);
                    jplis_assert(!errorOccurred);
                    if (errorOccurred) {
                        break;
                    }

                    classDefs[i].klass = (*jnienv)->CallObjectMethod(jnienv, classDef, getDefinitionClassMethodID);
                    errorOccurred = checkForThrowable(jnienv);
                    jplis_assert(!errorOccurred);
                    if (errorOccurred) {
                        break;
                    }

                    targetFiles[i] = (*jnienv)->CallObjectMethod(jnienv, classDef, getDefinitionClassFileMethodID);
                    errorOccurred = checkForThrowable(jnienv);
                    jplis_assert(!errorOccurred);
                    if (errorOccurred) {
                        break;
                    }

                    classDefs[i].class_byte_count = (*jnienv)->GetArrayLength(jnienv, targetFiles[i]);
                    errorOccurred = checkForThrowable(jnienv);
                    jplis_assert(!errorOccurred);
                    if (errorOccurred) {
                        break;
                    }

                    /*
                     * Allocate class_bytes last so we don't have to free
                     * memory on a partial row error.
                     */
                    classDefs[i].class_bytes = (unsigned char*)(*jnienv)->GetByteArrayElements(jnienv, targetFiles[i], NULL);
                    errorOccurred = checkForThrowable(jnienv);
                    jplis_assert(!errorOccurred);
                    if (errorOccurred) {
                        break;
                    }
                }

                if (!errorOccurred) {
                    jvmtiError  errorCode = JVMTI_ERROR_NONE;
                    errorCode = (*jvmtienv)->RedefineClasses(jvmtienv, numDefs, classDefs);
                    if (errorCode == JVMTI_ERROR_WRONG_PHASE) {
                        /* insulate caller from the wrong phase error */
                        errorCode = JVMTI_ERROR_NONE;
                    } else {
                        errorOccurred = (errorCode != JVMTI_ERROR_NONE);
                        if ( errorOccurred ) {
                            createAndThrowThrowableFromJVMTIErrorCode(jnienv, errorCode);
                        }
                    }
                }

                /*
                 * Cleanup memory that we allocated above. If we had a
                 * JNI error, a JVM/TI error or no errors, index 'i'
                 * tracks how far we got in processing the classDefs
                 * array. Note:  ReleaseByteArrayElements() is safe to
                 * call with a JNI exception pending.
                 */
                for (j = 0; j < i; j++) {
                    if ((jbyte *)classDefs[j].class_bytes != NULL) {
                        (*jnienv)->ReleaseByteArrayElements(jnienv,
                            targetFiles[j], (jbyte *)classDefs[j].class_bytes,
                            0 /* copy back and free */);
                        /*
                         * Only check for error if we didn't already have one
                         * so we don't overwrite errorOccurred.
                         */
                        if (!errorOccurred) {
                            errorOccurred = checkForThrowable(jnienv);
                            jplis_assert(!errorOccurred);
                        }
                    }
                }
                deallocate(jvmtienv, (void*)targetFiles);
                deallocate(jvmtienv, (void*)classDefs);
            }
        }
    }

    mapThrownThrowableIfNecessary(jnienv, redefineClassMapper);
}

/* Cheesy sharing. ClassLoader may be null. */
jobjectArray
commonGetClassList( JNIEnv *            jnienv,
                    JPLISAgent *        agent,
                    jobject             classLoader,
                    ClassListFetcher    fetcher) {
    jvmtiEnv *      jvmtienv        = jvmti(agent);
    jboolean        errorOccurred   = JNI_FALSE;
    jvmtiError      jvmtierror      = JVMTI_ERROR_NONE;
    jint            classCount      = 0;
    jclass *        classes         = NULL;
    jobjectArray    localArray      = NULL;

    /* retrieve the classes from the JVMTI agent */
    jvmtierror = (*fetcher)( jvmtienv,
                        classLoader,
                        &classCount,
                        &classes);
    check_phase_ret_blob(jvmtierror, localArray);
    errorOccurred = (jvmtierror != JVMTI_ERROR_NONE);
    jplis_assert(!errorOccurred);

    if ( errorOccurred ) {
        createAndThrowThrowableFromJVMTIErrorCode(jnienv, jvmtierror);
    } else {
        localArray = getObjectArrayFromClasses( jnienv,
                                                classes,
                                                classCount);
        errorOccurred = checkForThrowable(jnienv);
        jplis_assert(!errorOccurred);

        /* do this whether or not we saw a problem */
        deallocate(jvmtienv, (void*)classes);
    }

    mapThrownThrowableIfNecessary(jnienv, mapAllCheckedToInternalErrorMapper);
    return localArray;

}

jvmtiError
getAllLoadedClassesClassListFetcher(    jvmtiEnv *  jvmtienv,
                                        jobject     classLoader,
                                        jint *      classCount,
                                        jclass **   classes) {
    return (*jvmtienv)->GetLoadedClasses(jvmtienv, classCount, classes);
}

jobjectArray
getAllLoadedClasses(JNIEnv * jnienv, JPLISAgent * agent) {
    return commonGetClassList(  jnienv,
                                agent,
                                NULL,
                                getAllLoadedClassesClassListFetcher);
}

jvmtiError
getInitiatedClassesClassListFetcher(    jvmtiEnv *  jvmtienv,
                                        jobject     classLoader,
                                        jint *      classCount,
                                        jclass **   classes) {
    return (*jvmtienv)->GetClassLoaderClasses(jvmtienv, classLoader, classCount, classes);
}


jobjectArray
getInitiatedClasses(JNIEnv * jnienv, JPLISAgent * agent, jobject classLoader) {
    return commonGetClassList(  jnienv,
                                agent,
                                classLoader,
                                getInitiatedClassesClassListFetcher);
}

jlong
getObjectSize(JNIEnv * jnienv, JPLISAgent * agent, jobject objectToSize) {
    jvmtiEnv *  jvmtienv    = jvmti(agent);
    jlong       objectSize  = -1;
    jvmtiError  jvmtierror  = JVMTI_ERROR_NONE;

    jvmtierror = (*jvmtienv)->GetObjectSize(jvmtienv, objectToSize, &objectSize);
    check_phase_ret_0(jvmtierror);
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    if ( jvmtierror != JVMTI_ERROR_NONE ) {
        createAndThrowThrowableFromJVMTIErrorCode(jnienv, jvmtierror);
    }

    mapThrownThrowableIfNecessary(jnienv, mapAllCheckedToInternalErrorMapper);
    return objectSize;
}

void
appendToClassLoaderSearch(JNIEnv * jnienv, JPLISAgent * agent, jstring jarFile, jboolean isBootLoader)
{
    jvmtiEnv *  jvmtienv    = jvmti(agent);
    jboolean    errorOutstanding;
    jvmtiError  jvmtierror;
    const char* utf8Chars;
    jsize       utf8Len;
    jboolean    isCopy;
    char        platformChars[MAXPATHLEN];
    int         platformLen;

    utf8Len = (*jnienv)->GetStringUTFLength(jnienv, jarFile);
    errorOutstanding = checkForAndClearThrowable(jnienv);

    if (!errorOutstanding) {
        utf8Chars = (*jnienv)->GetStringUTFChars(jnienv, jarFile, &isCopy);
        errorOutstanding = checkForAndClearThrowable(jnienv);

        if (!errorOutstanding && utf8Chars != NULL) {
            /*
             * JVMTI spec'ed to use modified UTF8. At this time this is not implemented
             * the platform encoding is used.
             */
            platformLen = convertUtf8ToPlatformString((char*)utf8Chars, utf8Len, platformChars, MAXPATHLEN);
            if (platformLen < 0) {
                createAndThrowInternalError(jnienv);
                (*jnienv)->ReleaseStringUTFChars(jnienv, jarFile, utf8Chars);
                return;
            }

            (*jnienv)->ReleaseStringUTFChars(jnienv, jarFile, utf8Chars);
            errorOutstanding = checkForAndClearThrowable(jnienv);

            if (!errorOutstanding) {

                if (isBootLoader) {
                    jvmtierror = (*jvmtienv)->AddToBootstrapClassLoaderSearch(jvmtienv, platformChars);
                } else {
                    jvmtierror = (*jvmtienv)->AddToSystemClassLoaderSearch(jvmtienv, platformChars);
                }
                check_phase_ret(jvmtierror);

                if ( jvmtierror != JVMTI_ERROR_NONE ) {
                    createAndThrowThrowableFromJVMTIErrorCode(jnienv, jvmtierror);
                }
            }
        }
    }

    mapThrownThrowableIfNecessary(jnienv, mapAllCheckedToInternalErrorMapper);
}

/*
 *  Set the prefixes used to wrap native methods (so they can be instrumented).
 *  Each transform can set a prefix, any that have been set come in as prefixArray.
 *  Convert them in native strings in a native array then call JVM TI.
 *  One a given call, this function handles either the prefixes for retransformable
 *  transforms or for normal transforms.
 */
void
setNativeMethodPrefixes(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray prefixArray,
                        jboolean isRetransformable) {
    jvmtiEnv*   jvmtienv;
    jvmtiError  err                             = JVMTI_ERROR_NONE;
    jsize       arraySize;
    jboolean    errorOccurred                   = JNI_FALSE;

    jplis_assert(prefixArray != NULL);

    if (isRetransformable) {
        jvmtienv = agent->mRetransformEnvironment.mJVMTIEnv;
    } else {
        jvmtienv = agent->mNormalEnvironment.mJVMTIEnv;
    }
    arraySize = (*jnienv)->GetArrayLength(jnienv, prefixArray);
    errorOccurred = checkForThrowable(jnienv);
    jplis_assert(!errorOccurred);

    if (!errorOccurred) {
        /* allocate the native to hold the native prefixes */
        const char** prefixes = (const char**) allocate(jvmtienv,
                                                        arraySize * sizeof(char*));
        /* since JNI ReleaseStringUTFChars needs the jstring from which the native
         * string was allocated, we store them in a parallel array */
        jstring* originForRelease = (jstring*) allocate(jvmtienv,
                                                        arraySize * sizeof(jstring));
        errorOccurred = (prefixes == NULL || originForRelease == NULL);
        jplis_assert(!errorOccurred);
        if ( errorOccurred ) {
            createAndThrowThrowableFromJVMTIErrorCode(jnienv, JVMTI_ERROR_OUT_OF_MEMORY);
        }
        else {
            jint inx = 0;
            jint i;
            for (i = 0; i < arraySize; i++) {
                jstring      prefixStr  = NULL;
                const char*  prefix;
                jsize        prefixLen;
                jboolean     isCopy;

                prefixStr = (jstring) ((*jnienv)->GetObjectArrayElement(jnienv,
                                                                        prefixArray, i));
                errorOccurred = checkForThrowable(jnienv);
                jplis_assert(!errorOccurred);
                if (errorOccurred) {
                    break;
                }
                if (prefixStr == NULL) {
                    continue;
                }

                prefixLen = (*jnienv)->GetStringUTFLength(jnienv, prefixStr);
                errorOccurred = checkForThrowable(jnienv);
                jplis_assert(!errorOccurred);
                if (errorOccurred) {
                    break;
                }

                if (prefixLen > 0) {
                    prefix = (*jnienv)->GetStringUTFChars(jnienv, prefixStr, &isCopy);
                    errorOccurred = checkForThrowable(jnienv);
                    jplis_assert(!errorOccurred);
                    if (!errorOccurred && prefix != NULL) {
                        prefixes[inx] = prefix;
                        originForRelease[inx] = prefixStr;
                        ++inx;
                    }
                }
            }

            err = (*jvmtienv)->SetNativeMethodPrefixes(jvmtienv, inx, (char**)prefixes);
            /* can be called from any phase */
            jplis_assert(err == JVMTI_ERROR_NONE);

            for (i = 0; i < inx; i++) {
              (*jnienv)->ReleaseStringUTFChars(jnienv, originForRelease[i], prefixes[i]);
            }
        }
        deallocate(jvmtienv, (void*)prefixes);
        deallocate(jvmtienv, (void*)originForRelease);
    }
}

jstring
jarFile(JNIEnv * jnienv, JPLISAgent * agent) {
    return (*jnienv)->NewStringUTF(jnienv, agent->mJarfile);
}
