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

/*
 * Class loading, including bootstrap class loader, linking, and
 * initialization.
 */

#define LOG_CLASS_LOADING 0

#include "Dalvik.h"
#include "libdex/DexClass.h"

#include <stdlib.h>
#include <stddef.h>
#include <sys/stat.h>

#if LOG_CLASS_LOADING
#include <unistd.h>
#include <pthread.h>
#include <cutils/process_name.h>
#include <sys/types.h>
#endif

/*
Notes on Linking and Verification

The basic way to retrieve a class is to load it, make sure its superclass
and interfaces are available, prepare its fields, and return it.  This gets
a little more complicated when multiple threads can be trying to retrieve
the class simultaneously, requiring that we use the class object's monitor
to keep things orderly.

The linking (preparing, resolving) of a class can cause us to recursively
load superclasses and interfaces.  Barring circular references (e.g. two
classes that are superclasses of each other), this will complete without
the loader attempting to access the partially-linked class.

With verification, the situation is different.  If we try to verify
every class as we load it, we quickly run into trouble.  Even the lowly
java.lang.Object requires CloneNotSupportedException; follow the list
of referenced classes and you can head down quite a trail.  The trail
eventually leads back to Object, which is officially not fully-formed yet.

The VM spec (specifically, v2 5.4.1) notes that classes pulled in during
verification do not need to be prepared or verified.  This means that we
are allowed to have loaded but unverified classes.  It further notes that
the class must be verified before it is initialized, which allows us to
defer verification for all classes until class init.  You can't execute
code or access fields in an uninitialized class, so this is safe.

It also allows a more peaceful coexistence between verified and
unverifiable code.  If class A refers to B, and B has a method that
refers to a bogus class C, should we allow class A to be verified?
If A only exercises parts of B that don't use class C, then there is
nothing wrong with running code in A.  We can fully verify both A and B,
and allow execution to continue until B causes initialization of C.  The
VerifyError is thrown close to the point of use.

This gets a little weird with java.lang.Class, which is the only class
that can be instantiated before it is initialized.  We have to force
initialization right after the class is created, because by definition we
have instances of it on the heap, and somebody might get a class object and
start making virtual calls on it.  We can end up going recursive during
verification of java.lang.Class, but we avoid that by checking to see if
verification is already in progress before we try to initialize it.
*/

/*
Notes on class loaders and interaction with optimization / verification

In what follows, "pre-verification" and "optimization" are the steps
performed by the dexopt command, which attempts to verify and optimize
classes as part of unpacking jar files and storing the DEX data in the
dalvik-cache directory.  These steps are performed by loading the DEX
files directly, without any assistance from ClassLoader instances.

When we pre-verify and optimize a class in a DEX file, we make some
assumptions about where the class loader will go to look for classes.
If we can't guarantee those assumptions, e.g. because a class ("AppClass")
references something not defined in the bootstrap jars or the AppClass jar,
we can't pre-verify or optimize the class.

The VM doesn't define the behavior of user-defined class loaders.
For example, suppose application class AppClass, loaded by UserLoader,
has a method that creates a java.lang.String.  The first time
AppClass.stringyMethod tries to do something with java.lang.String, it
asks UserLoader to find it.  UserLoader is expected to defer to its parent
loader, but isn't required to.  UserLoader might provide a replacement
for String.

We can run into trouble if we pre-verify AppClass with the assumption that
java.lang.String will come from core.jar, and don't verify this assumption
at runtime.  There are two places that an alternate implementation of
java.lang.String can come from: the AppClass jar, or from some other jar
that UserLoader knows about.  (Someday UserLoader will be able to generate
some bytecode and call DefineClass, but not yet.)

To handle the first situation, the pre-verifier will explicitly check for
conflicts between the class being optimized/verified and the bootstrap
classes.  If an app jar contains a class that has the same package and
class name as a class in a bootstrap jar, the verification resolver refuses
to find either, which will block pre-verification and optimization on
classes that reference ambiguity.  The VM will postpone verification of
the app class until first load.

For the second situation, we need to ensure that all references from a
pre-verified class are satisified by the class' jar or earlier bootstrap
jars.  In concrete terms: when resolving a reference to NewClass,
which was caused by a reference in class AppClass, we check to see if
AppClass was pre-verified.  If so, we require that NewClass comes out
of either the AppClass jar or one of the jars in the bootstrap path.
(We may not control the class loaders, but we do manage the DEX files.
We can verify that it's either (loader==null && dexFile==a_boot_dex)
or (loader==UserLoader && dexFile==AppClass.dexFile).  Classes from
DefineClass can't be pre-verified, so this doesn't apply.)

This should ensure that you can't "fake out" the pre-verifier by creating
a user-defined class loader that replaces system classes.  It should
also ensure that you can write such a loader and have it work in the
expected fashion; all you lose is some performance due to "just-in-time
verification" and the lack of DEX optimizations.

There is a "back door" of sorts in the class resolution check, due to
the fact that the "class ref" entries are shared between the bytecode
and meta-data references (e.g. annotations and exception handler lists).
The class references in annotations have no bearing on class verification,
so when a class does an annotation query that causes a class reference
index to be resolved, we don't want to fail just because the calling
class was pre-verified and the resolved class is in some random DEX file.
The successful resolution adds the class to the "resolved classes" table,
so when optimized bytecode references it we don't repeat the resolve-time
check.  We can avoid this by not updating the "resolved classes" table
when the class reference doesn't come out of something that has been
checked by the verifier, but that has a nonzero performance impact.
Since the ultimate goal of this test is to catch an unusual situation
(user-defined class loaders redefining core classes), the added caution
may not be worth the performance hit.
*/

/*
 * Class serial numbers start at this value.  We use a nonzero initial
 * value so they stand out in binary dumps (e.g. hprof output).
 */
#define INITIAL_CLASS_SERIAL_NUMBER 0x50000000

/*
 * Constant used to size an auxillary class object data structure.
 * For optimum memory use this should be equal to or slightly larger than
 * the number of classes loaded when the zygote finishes initializing.
 */
#define ZYGOTE_CLASS_CUTOFF 2304

static ClassPathEntry* processClassPath(const char* pathStr, bool isBootstrap);
static void freeCpeArray(ClassPathEntry* cpe);

static ClassObject* findClassFromLoaderNoInit(
    const char* descriptor, Object* loader);
static ClassObject* findClassNoInit(const char* descriptor, Object* loader,\
    DvmDex* pDvmDex);
static ClassObject* loadClassFromDex(DvmDex* pDvmDex,
    const DexClassDef* pClassDef, Object* loader);
static void loadMethodFromDex(ClassObject* clazz, const DexMethod* pDexMethod,\
    Method* meth);
static int computeJniArgInfo(const DexProto* proto);
static void loadSFieldFromDex(ClassObject* clazz,
    const DexField* pDexSField, StaticField* sfield);
static void loadIFieldFromDex(ClassObject* clazz,
    const DexField* pDexIField, InstField* field);
static void freeMethodInnards(Method* meth);
static bool createVtable(ClassObject* clazz);
static bool createIftable(ClassObject* clazz);
static bool insertMethodStubs(ClassObject* clazz);
static bool computeFieldOffsets(ClassObject* clazz);
static void throwEarlierClassFailure(ClassObject* clazz);

#if LOG_CLASS_LOADING
/*
 * Logs information about a class loading with given timestamp.
 *
 * TODO: In the case where we fail in dvmLinkClass() and log the class as closing (type='<'),
 * it would probably be better to use a new type code to indicate the failure.  This change would
 * require a matching change in the parser and analysis code in frameworks/base/tools/preload.
 */
static void logClassLoadWithTime(char type, ClassObject* clazz, u8 time) {
    pid_t ppid = getppid();
    pid_t pid = getpid();
    unsigned int tid = (unsigned int) pthread_self();

    LOG(LOG_INFO, "PRELOAD", "%c%d:%d:%d:%s:%d:%s:%lld\n", type, ppid, pid, tid,
        get_process_name(), (int) clazz->classLoader, clazz->descriptor,
        time);
}

/*
 * Logs information about a class loading.
 */
static void logClassLoad(char type, ClassObject* clazz) {
    logClassLoadWithTime(type, clazz, dvmGetThreadCpuTimeNsec());
}
#endif

/*
 * Some LinearAlloc unit tests.
 */
static void linearAllocTests()
{
    char* fiddle;
    int try = 1;

    switch (try) {
    case 0:
        fiddle = dvmLinearAlloc(NULL, 3200-28);
        dvmLinearReadOnly(NULL, fiddle);
        break;
    case 1:
        fiddle = dvmLinearAlloc(NULL, 3200-24);
        dvmLinearReadOnly(NULL, fiddle);
        break;
    case 2:
        fiddle = dvmLinearAlloc(NULL, 3200-20);
        dvmLinearReadOnly(NULL, fiddle);
        break;
    case 3:
        fiddle = dvmLinearAlloc(NULL, 3200-16);
        dvmLinearReadOnly(NULL, fiddle);
        break;
    case 4:
        fiddle = dvmLinearAlloc(NULL, 3200-12);
        dvmLinearReadOnly(NULL, fiddle);
        break;
    }
    fiddle = dvmLinearAlloc(NULL, 896);
    dvmLinearReadOnly(NULL, fiddle);
    fiddle = dvmLinearAlloc(NULL, 20);      // watch addr of this alloc
    dvmLinearReadOnly(NULL, fiddle);

    fiddle = dvmLinearAlloc(NULL, 1);
    fiddle[0] = 'q';
    dvmLinearReadOnly(NULL, fiddle);
    fiddle = dvmLinearAlloc(NULL, 4096);
    fiddle[0] = 'x';
    fiddle[4095] = 'y';
    dvmLinearReadOnly(NULL, fiddle);
    dvmLinearFree(NULL, fiddle);
    fiddle = dvmLinearAlloc(NULL, 0);
    dvmLinearReadOnly(NULL, fiddle);
    fiddle = dvmLinearRealloc(NULL, fiddle, 12);
    fiddle[11] = 'z';
    dvmLinearReadOnly(NULL, fiddle);
    fiddle = dvmLinearRealloc(NULL, fiddle, 5);
    dvmLinearReadOnly(NULL, fiddle);
    fiddle = dvmLinearAlloc(NULL, 17001);
    fiddle[0] = 'x';
    fiddle[17000] = 'y';
    dvmLinearReadOnly(NULL, fiddle);

    char* str = dvmLinearStrdup(NULL, "This is a test!");
    LOGI("GOT: '%s'\n", str);

    dvmLinearAllocDump(NULL);
    dvmLinearFree(NULL, str);
}

/*
 * Initialize the bootstrap class loader.
 *
 * Call this after the bootclasspath string has been finalized.
 */
bool dvmClassStartup(void)
{
    ClassObject* unlinkedClass;

    /* make this a requirement -- don't currently support dirs in path */
    if (strcmp(gDvm.bootClassPathStr, ".") == 0) {
        LOGE("ERROR: must specify non-'.' bootclasspath\n");
        return false;
    }

    gDvm.loadedClasses =
        dvmHashTableCreate(256, (HashFreeFunc) dvmFreeClassInnards);

    gDvm.pBootLoaderAlloc = dvmLinearAllocCreate(NULL);
    if (gDvm.pBootLoaderAlloc == NULL)
        return false;

    if (false) {
        linearAllocTests();
        exit(0);
    }

    /*
     * Class serial number.  We start with a high value to make it distinct
     * in binary dumps (e.g. hprof).
     */
    gDvm.classSerialNumber = INITIAL_CLASS_SERIAL_NUMBER;

    /* Set up the table we'll use for tracking initiating loaders for
     * early classes.
     * If it's NULL, we just fall back to the InitiatingLoaderList in the
     * ClassObject, so it's not fatal to fail this allocation.
     */
    gDvm.initiatingLoaderList =
        calloc(ZYGOTE_CLASS_CUTOFF, sizeof(InitiatingLoaderList));

    /* This placeholder class is used while a ClassObject is
     * loading/linking so those not in the know can still say
     * "obj->clazz->...".
     */
    unlinkedClass = &gDvm.unlinkedJavaLangClassObject;

    memset(unlinkedClass, 0, sizeof(*unlinkedClass));

    /* Set obj->clazz to NULL so anyone who gets too interested
     * in the fake class will crash.
     */
    DVM_OBJECT_INIT(&unlinkedClass->obj, NULL);
    unlinkedClass->descriptor = "!unlinkedClass";
    dvmSetClassSerialNumber(unlinkedClass);

    gDvm.unlinkedJavaLangClass = unlinkedClass;

    /*
     * Process the bootstrap class path.  This means opening the specified
     * DEX or Jar files and possibly running them through the optimizer.
     */
    assert(gDvm.bootClassPath == NULL);
    processClassPath(gDvm.bootClassPathStr, true);

    if (gDvm.bootClassPath == NULL)
        return false;

    return true;
}

/*
 * Clean up.
 */
void dvmClassShutdown(void)
{
    int i;

    /* discard all system-loaded classes */
    dvmHashTableFree(gDvm.loadedClasses);
    gDvm.loadedClasses = NULL;

    /* discard primitive classes created for arrays */
    for (i = 0; i < PRIM_MAX; i++)
        dvmFreeClassInnards(gDvm.primitiveClass[i]);

    /* this closes DEX files, JAR files, etc. */
    freeCpeArray(gDvm.bootClassPath);
    gDvm.bootClassPath = NULL;

    dvmLinearAllocDestroy(NULL);

    free(gDvm.initiatingLoaderList);
}


/*
 * ===========================================================================
 *      Bootstrap class loader
 * ===========================================================================
 */

/*
 * Dump the contents of a ClassPathEntry array.
 */
static void dumpClassPath(const ClassPathEntry* cpe)
{
    int idx = 0;

    while (cpe->kind != kCpeLastEntry) {
        const char* kindStr;

        switch (cpe->kind) {
        case kCpeDir:       kindStr = "dir";    break;
        case kCpeJar:       kindStr = "jar";    break;
        case kCpeDex:       kindStr = "dex";    break;
        default:            kindStr = "???";    break;
        }

        LOGI("  %2d: type=%s %s %p\n", idx, kindStr, cpe->fileName, cpe->ptr);
        if (CALC_CACHE_STATS && cpe->kind == kCpeJar) {
            JarFile* pJarFile = (JarFile*) cpe->ptr;
            DvmDex* pDvmDex = dvmGetJarFileDex(pJarFile);
            dvmDumpAtomicCacheStats(pDvmDex->pInterfaceCache);
        }

        cpe++;
        idx++;
    }
}

/*
 * Dump the contents of the bootstrap class path.
 */
void dvmDumpBootClassPath(void)
{
    dumpClassPath(gDvm.bootClassPath);
}

/*
 * Returns "true" if the class path contains the specified path.
 */
bool dvmClassPathContains(const ClassPathEntry* cpe, const char* path)
{
    while (cpe->kind != kCpeLastEntry) {
        if (strcmp(cpe->fileName, path) == 0)
            return true;

        cpe++;
    }
    return false;
}

/*
 * Free an array of ClassPathEntry structs.
 *
 * We release the contents of each entry, then free the array itself.
 */
static void freeCpeArray(ClassPathEntry* cpe)
{
    ClassPathEntry* cpeStart = cpe;

    if (cpe == NULL)
        return;

    while (cpe->kind != kCpeLastEntry) {
        switch (cpe->kind) {
        case kCpeJar:
            /* free JarFile */
            dvmJarFileFree((JarFile*) cpe->ptr);
            break;
        case kCpeDex:
            /* free RawDexFile */
            dvmRawDexFileFree((RawDexFile*) cpe->ptr);
            break;
        default:
            /* e.g. kCpeDir */
            assert(cpe->ptr == NULL);
            break;
        }

        free(cpe->fileName);
        cpe++;
    }

    free(cpeStart);
}

/*
 * Prepare a ClassPathEntry struct, which at this point only has a valid
 * filename.  We need to figure out what kind of file it is, and for
 * everything other than directories we need to open it up and see
 * what's inside.
 */
static bool prepareCpe(ClassPathEntry* cpe, bool isBootstrap)
{
    JarFile* pJarFile = NULL;
    RawDexFile* pRawDexFile = NULL;
    struct stat sb;
    int cc;

    cc = stat(cpe->fileName, &sb);
    if (cc < 0) {
        LOGW("Unable to stat classpath element '%s'\n", cpe->fileName);
        return false;
    }
    if (S_ISDIR(sb.st_mode)) {
        /*
         * The directory will usually have .class files in subdirectories,
         * which may be a few levels down.  Doing a recursive scan and
         * caching the results would help us avoid hitting the filesystem
         * on misses.  Whether or not this is of measureable benefit
         * depends on a number of factors, but most likely it is not
         * worth the effort (especially since most of our stuff will be
         * in DEX or JAR).
         */
        cpe->kind = kCpeDir;
        assert(cpe->ptr == NULL);
        return true;
    }

    if (dvmJarFileOpen(cpe->fileName, NULL, &pJarFile, isBootstrap) == 0) {
        cpe->kind = kCpeJar;
        cpe->ptr = pJarFile;
        return true;
    }

    // TODO: do we still want to support "raw" DEX files in the classpath?
    if (dvmRawDexFileOpen(cpe->fileName, NULL, &pRawDexFile, isBootstrap) == 0)
    {
        cpe->kind = kCpeDex;
        cpe->ptr = pRawDexFile;
        return true;
    }

    return false;
}

/*
 * Convert a colon-separated list of directories, Zip files, and DEX files
 * into an array of ClassPathEntry structs.
 *
 * If we're unable to load a bootstrap class path entry, we fail.  This
 * is necessary to preserve the dependencies implied by optimized DEX files
 * (e.g. if the same class appears in multiple places).
 *
 * During normal startup we fail if there are no entries, because we won't
 * get very far without the basic language support classes, but if we're
 * optimizing a DEX file we allow it.
 */
static ClassPathEntry* processClassPath(const char* pathStr, bool isBootstrap)
{
    ClassPathEntry* cpe = NULL;
    char* mangle;
    char* cp;
    const char* end;
    int idx, count;

    assert(pathStr != NULL);

    mangle = strdup(pathStr);

    /*
     * Run through and essentially strtok() the string.  Get a count of
     * the #of elements while we're at it.
     *
     * If the path was constructed strangely (e.g. ":foo::bar:") this will
     * over-allocate, which isn't ideal but is mostly harmless.
     */
    count = 1;
    for (cp = mangle; *cp != '\0'; cp++) {
        if (*cp == ':') {   /* separates two entries */
            count++;
            *cp = '\0';
        }
    }
    end = cp;

    /*
     * Allocate storage.  We over-alloc by one so we can set an "end" marker.
     */
    cpe = (ClassPathEntry*) calloc(count+1, sizeof(ClassPathEntry));

    /*
     * Set the global pointer so the DEX file dependency stuff can find it.
     */
    gDvm.bootClassPath = cpe;

    /*
     * Go through a second time, pulling stuff out.
     */
    cp = mangle;
    idx = 0;
    while (cp < end) {
        if (*cp == '\0') {
            /* leading, trailing, or doubled ':'; ignore it */
        } else {
            ClassPathEntry tmp;
            tmp.kind = kCpeUnknown;
            tmp.fileName = strdup(cp);
            tmp.ptr = NULL;

            /* drop an end marker here so DEX loader can walk unfinished list */
            cpe[idx].kind = kCpeLastEntry;
            cpe[idx].fileName = NULL;
            cpe[idx].ptr = NULL;

            if (!prepareCpe(&tmp, isBootstrap)) {
                LOGD("Failed on '%s' (boot=%d)\n", tmp.fileName, isBootstrap);
                /* drop from list and continue on */
                free(tmp.fileName);

                if (isBootstrap || gDvm.optimizing) {
                    /* if boot path entry or we're optimizing, this is fatal */
                    free(cpe);
                    cpe = NULL;
                    goto bail;
                }
            } else {
                /* copy over, pointers and all */
                if (tmp.fileName[0] != '/')
                    LOGW("Non-absolute bootclasspath entry '%s'\n",
                        tmp.fileName);
                cpe[idx] = tmp;
                idx++;
            }
        }

        cp += strlen(cp) +1;
    }
    assert(idx <= count);
    if (idx == 0 && !gDvm.optimizing) {
        LOGE("ERROR: no valid entries found in bootclasspath '%s'\n", pathStr);
        free(cpe);
        cpe = NULL;
        goto bail;
    }

    LOGVV("  (filled %d of %d slots)\n", idx, count);

    /* put end marker in over-alloc slot */
    cpe[idx].kind = kCpeLastEntry;
    cpe[idx].fileName = NULL;
    cpe[idx].ptr = NULL;

    //dumpClassPath(cpe);

bail:
    free(mangle);
    gDvm.bootClassPath = cpe;
    return cpe;
}

/*
 * Search the DEX files we loaded from the bootstrap class path for a DEX
 * file that has the class with the matching descriptor.
 *
 * Returns the matching DEX file and DexClassDef entry if found, otherwise
 * returns NULL.
 */
static DvmDex* searchBootPathForClass(const char* descriptor,
    const DexClassDef** ppClassDef)
{
    const ClassPathEntry* cpe = gDvm.bootClassPath;
    const DexClassDef* pFoundDef = NULL;
    DvmDex* pFoundFile = NULL;

    LOGVV("+++ class '%s' not yet loaded, scanning bootclasspath...\n",
        descriptor);

    while (cpe->kind != kCpeLastEntry) {
        //LOGV("+++  checking '%s' (%d)\n", cpe->fileName, cpe->kind);

        switch (cpe->kind) {
        case kCpeDir:
            LOGW("Directory entries ('%s') not supported in bootclasspath\n",
                cpe->fileName);
            break;
        case kCpeJar:
            {
                JarFile* pJarFile = (JarFile*) cpe->ptr;
                const DexClassDef* pClassDef;
                DvmDex* pDvmDex;

                pDvmDex = dvmGetJarFileDex(pJarFile);
                pClassDef = dexFindClass(pDvmDex->pDexFile, descriptor);
                if (pClassDef != NULL) {
                    /* found */
                    pFoundDef = pClassDef;
                    pFoundFile = pDvmDex;
                    goto found;
                }
            }
            break;
        case kCpeDex:
            {
                RawDexFile* pRawDexFile = (RawDexFile*) cpe->ptr;
                const DexClassDef* pClassDef;
                DvmDex* pDvmDex;

                pDvmDex = dvmGetRawDexFileDex(pRawDexFile);
                pClassDef = dexFindClass(pDvmDex->pDexFile, descriptor);
                if (pClassDef != NULL) {
                    /* found */
                    pFoundDef = pClassDef;
                    pFoundFile = pDvmDex;
                    goto found;
                }
            }
            break;
        default:
            LOGE("Unknown kind %d\n", cpe->kind);
            assert(false);
            break;
        }

        cpe++;
    }

    /*
     * Special handling during verification + optimization.
     *
     * The DEX optimizer needs to load classes from the DEX file it's working
     * on.  Rather than trying to insert it into the bootstrap class path
     * or synthesizing a class loader to manage it, we just make it available
     * here.  It logically comes after all existing entries in the bootstrap
     * class path.
     */
    if (gDvm.bootClassPathOptExtra != NULL) {
        const DexClassDef* pClassDef;

        pClassDef =
            dexFindClass(gDvm.bootClassPathOptExtra->pDexFile, descriptor);
        if (pClassDef != NULL) {
            /* found */
            pFoundDef = pClassDef;
            pFoundFile = gDvm.bootClassPathOptExtra;
        }
    }

found:
    *ppClassDef = pFoundDef;
    return pFoundFile;
}

/*
 * Set the "extra" DEX, which becomes a de facto member of the bootstrap
 * class set.
 */
void dvmSetBootPathExtraDex(DvmDex* pDvmDex)
{
    gDvm.bootClassPathOptExtra = pDvmDex;
}


/*
 * Return the #of entries in the bootstrap class path.
 *
 * (Used for ClassLoader.getResources().)
 */
int dvmGetBootPathSize(void)
{
    const ClassPathEntry* cpe = gDvm.bootClassPath;

    while (cpe->kind != kCpeLastEntry)
        cpe++;

    return cpe - gDvm.bootClassPath;
}

/*
 * Find a resource with the specified name in entry N of the boot class path.
 *
 * We return a newly-allocated String of one of these forms:
 *   file://path/name
 *   jar:file://path!/name
 * Where "path" is the bootstrap class path entry and "name" is the string
 * passed into this method.  "path" needs to be an absolute path (starting
 * with '/'); if it's not we'd need to "absolutify" it as part of forming
 * the URL string.
 */
StringObject* dvmGetBootPathResource(const char* name, int idx)
{
    const int kUrlOverhead = 13;        // worst case for Jar URL
    const ClassPathEntry* cpe = gDvm.bootClassPath;
    StringObject* urlObj = NULL;

    LOGV("+++ searching for resource '%s' in %d(%s)\n",
        name, idx, cpe[idx].fileName);

    /* we could use direct array index, but I don't entirely trust "idx" */
    while (idx-- && cpe->kind != kCpeLastEntry)
        cpe++;
    if (cpe->kind == kCpeLastEntry) {
        assert(false);
        return NULL;
    }

    char urlBuf[strlen(name) + strlen(cpe->fileName) + kUrlOverhead +1];

    switch (cpe->kind) {
    case kCpeDir:
        sprintf(urlBuf, "file://%s/%s", cpe->fileName, name);
        if (access(urlBuf+7, F_OK) != 0)
            goto bail;
        break;
    case kCpeJar:
        {
            JarFile* pJarFile = (JarFile*) cpe->ptr;
            if (dexZipFindEntry(&pJarFile->archive, name) == NULL)
                goto bail;
            sprintf(urlBuf, "jar:file://%s!/%s", cpe->fileName, name);
        }
        break;
    case kCpeDex:
        LOGV("No resources in DEX files\n");
        goto bail;
    default:
        assert(false);
        goto bail;
    }

    LOGV("+++ using URL='%s'\n", urlBuf);
    urlObj = dvmCreateStringFromCstr(urlBuf, ALLOC_DEFAULT);

bail:
    return urlObj;
}


/*
 * ===========================================================================
 *      Class list management
 * ===========================================================================
 */

/* search for these criteria in the Class hash table */
typedef struct ClassMatchCriteria {
    const char* descriptor;
    Object*     loader;
} ClassMatchCriteria;

#define kInitLoaderInc  4       /* must be power of 2 */

static InitiatingLoaderList *dvmGetInitiatingLoaderList(ClassObject* clazz)
{
    assert(clazz->serialNumber > INITIAL_CLASS_SERIAL_NUMBER);
    int classIndex = clazz->serialNumber-INITIAL_CLASS_SERIAL_NUMBER;
    if (gDvm.initiatingLoaderList != NULL &&
        classIndex < ZYGOTE_CLASS_CUTOFF) {
        return &(gDvm.initiatingLoaderList[classIndex]);
    } else {
        return &(clazz->initiatingLoaderList);
    }
}

/*
 * Determine if "loader" appears in clazz' initiating loader list.
 *
 * The class hash table lock must be held when calling here, since
 * it's also used when updating a class' initiating loader list.
 *
 * TODO: switch to some sort of lock-free data structure so we don't have
 * to grab the lock to do a lookup.  Among other things, this would improve
 * the speed of compareDescriptorClasses().
 */
bool dvmLoaderInInitiatingList(const ClassObject* clazz, const Object* loader)
{
    /*
     * The bootstrap class loader can't be just an initiating loader for
     * anything (it's always the defining loader if the class is visible
     * to it).  We don't put defining loaders in the initiating list.
     */
    if (loader == NULL)
        return false;

    /*
     * Scan the list for a match.  The list is expected to be short.
     */
    /* Cast to remove the const from clazz, but use const loaderList */
    ClassObject* nonConstClazz = (ClassObject*) clazz;
    const InitiatingLoaderList *loaderList =
        dvmGetInitiatingLoaderList(nonConstClazz);
    int i;
    for (i = loaderList->initiatingLoaderCount-1; i >= 0; --i) {
        if (loaderList->initiatingLoaders[i] == loader) {
            //LOGI("+++ found initiating match %p in %s\n",
            //    loader, clazz->descriptor);
            return true;
        }
    }
    return false;
}

/*
 * Add "loader" to clazz's initiating loader set, unless it's the defining
 * class loader.
 *
 * In the common case this will be a short list, so we don't need to do
 * anything too fancy here.
 *
 * This locks gDvm.loadedClasses for synchronization, so don't hold it
 * when calling here.
 */
void dvmAddInitiatingLoader(ClassObject* clazz, Object* loader)
{
    if (loader != clazz->classLoader) {
        assert(loader != NULL);

        LOGVV("Adding %p to '%s' init list\n", loader, clazz->descriptor);
        dvmHashTableLock(gDvm.loadedClasses);

        /*
         * Make sure nobody snuck in.  The penalty for adding twice is
         * pretty minor, and probably outweighs the O(n^2) hit for
         * checking before every add, so we may not want to do this.
         */
        //if (dvmLoaderInInitiatingList(clazz, loader)) {
        //    LOGW("WOW: simultaneous add of initiating class loader\n");
        //    goto bail_unlock;
        //}

        /*
         * The list never shrinks, so we just keep a count of the
         * number of elements in it, and reallocate the buffer when
         * we run off the end.
         *
         * The pointer is initially NULL, so we *do* want to call realloc
         * when count==0.
         */
        InitiatingLoaderList *loaderList = dvmGetInitiatingLoaderList(clazz);
        if ((loaderList->initiatingLoaderCount & (kInitLoaderInc-1)) == 0) {
            Object** newList;

            newList = (Object**) realloc(loaderList->initiatingLoaders,
                        (loaderList->initiatingLoaderCount + kInitLoaderInc)
                         * sizeof(Object*));
            if (newList == NULL) {
                /* this is mainly a cache, so it's not the EotW */
                assert(false);
                goto bail_unlock;
            }
            loaderList->initiatingLoaders = newList;

            //LOGI("Expanded init list to %d (%s)\n",
            //    loaderList->initiatingLoaderCount+kInitLoaderInc,
            //    clazz->descriptor);
        }
        loaderList->initiatingLoaders[loaderList->initiatingLoaderCount++] =
            loader;

bail_unlock:
        dvmHashTableUnlock(gDvm.loadedClasses);
    }
}

/*
 * (This is a dvmHashTableLookup callback.)
 *
 * Entries in the class hash table are stored as { descriptor, d-loader }
 * tuples.  If the hashed class descriptor matches the requested descriptor,
 * and the hashed defining class loader matches the requested class
 * loader, we're good.  If only the descriptor matches, we check to see if the
 * loader is in the hashed class' initiating loader list.  If so, we
 * can return "true" immediately and skip some of the loadClass melodrama.
 *
 * The caller must lock the hash table before calling here.
 *
 * Returns 0 if a matching entry is found, nonzero otherwise.
 */
static int hashcmpClassByCrit(const void* vclazz, const void* vcrit)
{
    const ClassObject* clazz = (const ClassObject*) vclazz;
    const ClassMatchCriteria* pCrit = (const ClassMatchCriteria*) vcrit;
    bool match;

    match = (strcmp(clazz->descriptor, pCrit->descriptor) == 0 &&
             (clazz->classLoader == pCrit->loader ||
              (pCrit->loader != NULL &&
               dvmLoaderInInitiatingList(clazz, pCrit->loader)) ));
    //if (match)
    //    LOGI("+++ %s %p matches existing %s %p\n",
    //        pCrit->descriptor, pCrit->loader,
    //        clazz->descriptor, clazz->classLoader);
    return !match;
}

/*
 * Like hashcmpClassByCrit, but passing in a fully-formed ClassObject
 * instead of a ClassMatchCriteria.
 */
static int hashcmpClassByClass(const void* vclazz, const void* vaddclazz)
{
    const ClassObject* clazz = (const ClassObject*) vclazz;
    const ClassObject* addClazz = (const ClassObject*) vaddclazz;
    bool match;

    match = (strcmp(clazz->descriptor, addClazz->descriptor) == 0 &&
             (clazz->classLoader == addClazz->classLoader ||
              (addClazz->classLoader != NULL &&
               dvmLoaderInInitiatingList(clazz, addClazz->classLoader)) ));
    return !match;
}

/*
 * Search through the hash table to find an entry with a matching descriptor
 * and an initiating class loader that matches "loader".
 *
 * The table entries are hashed on descriptor only, because they're unique
 * on *defining* class loader, not *initiating* class loader.  This isn't
 * great, because it guarantees we will have to probe when multiple
 * class loaders are used.
 *
 * Note this does NOT try to load a class; it just finds a class that
 * has already been loaded.
 *
 * If "unprepOkay" is set, this will return classes that have been added
 * to the hash table but are not yet fully loaded and linked.  Otherwise,
 * such classes are ignored.  (The only place that should set "unprepOkay"
 * is findClassNoInit(), which will wait for the prep to finish.)
 *
 * Returns NULL if not found.
 */
ClassObject* dvmLookupClass(const char* descriptor, Object* loader,
    bool unprepOkay)
{
    ClassMatchCriteria crit;
    void* found;
    u4 hash;

    crit.descriptor = descriptor;
    crit.loader = loader;
    hash = dvmComputeUtf8Hash(descriptor);

    LOGVV("threadid=%d: dvmLookupClass searching for '%s' %p\n",
        dvmThreadSelf()->threadId, descriptor, loader);

    dvmHashTableLock(gDvm.loadedClasses);
    found = dvmHashTableLookup(gDvm.loadedClasses, hash, &crit,
                hashcmpClassByCrit, false);
    dvmHashTableUnlock(gDvm.loadedClasses);

    /*
     * The class has been added to the hash table but isn't ready for use.
     * We're going to act like we didn't see it, so that the caller will
     * go through the full "find class" path, which includes locking the
     * object and waiting until it's ready.  We could do that lock/wait
     * here, but this is an extremely rare case, and it's simpler to have
     * the wait-for-class code centralized.
     */
    if (found != NULL && !unprepOkay && !dvmIsClassLinked(found)) {
        LOGV("Ignoring not-yet-ready %s, using slow path\n",
            ((ClassObject*)found)->descriptor);
        found = NULL;
    }

    return (ClassObject*) found;
}

/*
 * Add a new class to the hash table.
 *
 * The class is considered "new" if it doesn't match on both the class
 * descriptor and the defining class loader.
 *
 * TODO: we should probably have separate hash tables for each
 * ClassLoader. This could speed up dvmLookupClass and
 * other common operations. It does imply a VM-visible data structure
 * for each ClassLoader object with loaded classes, which we don't
 * have yet.
 */
bool dvmAddClassToHash(ClassObject* clazz)
{
    void* found;
    u4 hash;

    hash = dvmComputeUtf8Hash(clazz->descriptor);

    dvmHashTableLock(gDvm.loadedClasses);
    found = dvmHashTableLookup(gDvm.loadedClasses, hash, clazz,
                hashcmpClassByClass, true);
    dvmHashTableUnlock(gDvm.loadedClasses);

    LOGV("+++ dvmAddClassToHash '%s' %p (isnew=%d) --> %p\n",
        clazz->descriptor, clazz->classLoader,
        (found == (void*) clazz), clazz);

    //dvmCheckClassTablePerf();

    /* can happen if two threads load the same class simultaneously */
    return (found == (void*) clazz);
}

#if 0
/*
 * Compute hash value for a class.
 */
u4 hashcalcClass(const void* item)
{
    return dvmComputeUtf8Hash(((const ClassObject*) item)->descriptor);
}

/*
 * Check the performance of the "loadedClasses" hash table.
 */
void dvmCheckClassTablePerf(void)
{
    dvmHashTableLock(gDvm.loadedClasses);
    dvmHashTableProbeCount(gDvm.loadedClasses, hashcalcClass,
        hashcmpClassByClass);
    dvmHashTableUnlock(gDvm.loadedClasses);
}
#endif

/*
 * Remove a class object from the hash table.
 */
static void removeClassFromHash(ClassObject* clazz)
{
    LOGV("+++ removeClassFromHash '%s'\n", clazz->descriptor);

    u4 hash = dvmComputeUtf8Hash(clazz->descriptor);

    dvmHashTableLock(gDvm.loadedClasses);
    if (!dvmHashTableRemove(gDvm.loadedClasses, hash, clazz))
        LOGW("Hash table remove failed on class '%s'\n", clazz->descriptor);
    dvmHashTableUnlock(gDvm.loadedClasses);
}


/*
 * ===========================================================================
 *      Class creation
 * ===========================================================================
 */

/*
 * Set clazz->serialNumber to the next available value.
 *
 * This usually happens *very* early in class creation, so don't expect
 * anything else in the class to be ready.
 */
void dvmSetClassSerialNumber(ClassObject* clazz)
{
    u4 oldValue, newValue;

    assert(clazz->serialNumber == 0);

    do {
        oldValue = gDvm.classSerialNumber;
        newValue = oldValue + 1;
    } while (!ATOMIC_CMP_SWAP(&gDvm.classSerialNumber, oldValue, newValue));

    clazz->serialNumber = (u4) oldValue;
}


/*
 * Find the named class (by descriptor), using the specified
 * initiating ClassLoader.
 *
 * The class will be loaded and initialized if it has not already been.
 * If necessary, the superclass will be loaded.
 *
 * If the class can't be found, returns NULL with an appropriate exception
 * raised.
 */
ClassObject* dvmFindClass(const char* descriptor, Object* loader)
{
    ClassObject* clazz;

    clazz = dvmFindClassNoInit(descriptor, loader);
    if (clazz != NULL && clazz->status < CLASS_INITIALIZED) {
        /* initialize class */
        if (!dvmInitClass(clazz)) {
            /* init failed; leave it in the list, marked as bad */
            assert(dvmCheckException(dvmThreadSelf()));
            assert(clazz->status == CLASS_ERROR);
            return NULL;
        }
    }

    return clazz;
}

/*
 * Find the named class (by descriptor), using the specified
 * initiating ClassLoader.
 *
 * The class will be loaded if it has not already been, as will its
 * superclass.  It will not be initialized.
 *
 * If the class can't be found, returns NULL with an appropriate exception
 * raised.
 */
ClassObject* dvmFindClassNoInit(const char* descriptor,
        Object* loader)
{
    assert(descriptor != NULL);
    //assert(loader != NULL);

    LOGVV("FindClassNoInit '%s' %p\n", descriptor, loader);

    if (*descriptor == '[') {
        /*
         * Array class.  Find in table, generate if not found.
         */
        return dvmFindArrayClass(descriptor, loader);
    } else {
        /*
         * Regular class.  Find in table, load if not found.
         */
        if (loader != NULL) {
            return findClassFromLoaderNoInit(descriptor, loader);
        } else {
            return dvmFindSystemClassNoInit(descriptor);
        }
    }
}

/*
 * Load the named class (by descriptor) from the specified class
 * loader.  This calls out to let the ClassLoader object do its thing.
 *
 * Returns with NULL and an exception raised on error.
 */
static ClassObject* findClassFromLoaderNoInit(const char* descriptor,
    Object* loader)
{
    //LOGI("##### findClassFromLoaderNoInit (%s,%p)\n",
    //        descriptor, loader);

    Thread* self = dvmThreadSelf();
    ClassObject* clazz;

    assert(loader != NULL);

    /*
     * Do we already have it?
     *
     * The class loader code does the "is it already loaded" check as
     * well.  However, this call is much faster than calling through
     * interpreted code.  Doing this does mean that in the common case
     * (365 out of 420 calls booting the sim) we're doing the
     * lookup-by-descriptor twice.  It appears this is still a win, so
     * I'm keeping it in.
     */
    clazz = dvmLookupClass(descriptor, loader, false);
    if (clazz != NULL) {
        LOGVV("Already loaded: %s %p\n", descriptor, loader);
        return clazz;
    } else {
        LOGVV("Not already loaded: %s %p\n", descriptor, loader);
    }

    char* dotName = NULL;
    StringObject* nameObj = NULL;
    Object* excep;
    Method* loadClass;

    /* convert "Landroid/debug/Stuff;" to "android.debug.Stuff" */
    dotName = dvmDescriptorToDot(descriptor);
    if (dotName == NULL) {
        dvmThrowException("Ljava/lang/OutOfMemoryError;", NULL);
        goto bail;
    }
    nameObj = dvmCreateStringFromCstr(dotName, ALLOC_DEFAULT);
    if (nameObj == NULL) {
        assert(dvmCheckException(self));
        goto bail;
    }

    // TODO: cache the vtable offset
    loadClass = dvmFindVirtualMethodHierByDescriptor(loader->clazz, "loadClass",
                 "(Ljava/lang/String;)Ljava/lang/Class;");
    if (loadClass == NULL) {
        LOGW("Couldn't find loadClass in ClassLoader\n");
        goto bail;
    }

#ifdef WITH_PROFILER
    dvmMethodTraceClassPrepBegin();
#endif

    /*
     * Invoke loadClass().  This will probably result in a couple of
     * exceptions being thrown, because the ClassLoader.loadClass()
     * implementation eventually calls VMClassLoader.loadClass to see if
     * the bootstrap class loader can find it before doing its own load.
     */
    LOGVV("--- Invoking loadClass(%s, %p)\n", dotName, loader);
    JValue result;
    dvmCallMethod(self, loadClass, loader, &result, nameObj);
    clazz = (ClassObject*) result.l;

#ifdef WITH_PROFILER
    dvmMethodTraceClassPrepEnd();
#endif

    excep = dvmGetException(self);
    if (excep != NULL) {
#if DVM_SHOW_EXCEPTION >= 2
        LOGD("NOTE: loadClass '%s' %p threw exception %s\n",
            dotName, loader, excep->clazz->descriptor);
#endif
        dvmAddTrackedAlloc(excep, self);
        dvmClearException(self);
        dvmThrowChainedExceptionWithClassMessage(
            "Ljava/lang/NoClassDefFoundError;", descriptor, excep);
        dvmReleaseTrackedAlloc(excep, self);
        clazz = NULL;
        goto bail;
    } else {
        assert(clazz != NULL);
    }

    dvmAddInitiatingLoader(clazz, loader);

    LOGVV("--- Successfully loaded %s %p (thisldr=%p clazz=%p)\n",
        descriptor, clazz->classLoader, loader, clazz);

bail:
    dvmReleaseTrackedAlloc((Object*)nameObj, NULL);
    free(dotName);
    return clazz;
}

/*
 * Load the named class (by descriptor) from the specified DEX file.
 * Used by class loaders to instantiate a class object from a
 * VM-managed DEX.
 */
ClassObject* dvmDefineClass(DvmDex* pDvmDex, const char* descriptor,
    Object* classLoader)
{
    assert(pDvmDex != NULL);

    return findClassNoInit(descriptor, classLoader, pDvmDex);
}


/*
 * Find the named class (by descriptor), scanning through the
 * bootclasspath if it hasn't already been loaded.
 *
 * "descriptor" looks like "Landroid/debug/Stuff;".
 *
 * Uses NULL as the defining class loader.
 */
ClassObject* dvmFindSystemClass(const char* descriptor)
{
    ClassObject* clazz;

    clazz = dvmFindSystemClassNoInit(descriptor);
    if (clazz != NULL && clazz->status < CLASS_INITIALIZED) {
        /* initialize class */
        if (!dvmInitClass(clazz)) {
            /* init failed; leave it in the list, marked as bad */
            assert(dvmCheckException(dvmThreadSelf()));
            assert(clazz->status == CLASS_ERROR);
            return NULL;
        }
    }

    return clazz;
}

/*
 * Find the named class (by descriptor), searching for it in the
 * bootclasspath.
 *
 * On failure, this returns NULL with an exception raised.
 */
ClassObject* dvmFindSystemClassNoInit(const char* descriptor)
{
    return findClassNoInit(descriptor, NULL, NULL);
}

/*
 * Find the named class (by descriptor). If it's not already loaded,
 * we load it and link it, but don't execute <clinit>. (The VM has
 * specific limitations on which events can cause initialization.)
 *
 * If "pDexFile" is NULL, we will search the bootclasspath for an entry.
 *
 * On failure, this returns NULL with an exception raised.
 *
 * TODO: we need to return an indication of whether we loaded the class or
 * used an existing definition.  If somebody deliberately tries to load a
 * class twice in the same class loader, they should get a LinkageError,
 * but inadvertent simultaneous class references should "just work".
 */
static ClassObject* findClassNoInit(const char* descriptor, Object* loader,
    DvmDex* pDvmDex)
{
    Thread* self = dvmThreadSelf();
    ClassObject* clazz;
#ifdef WITH_PROFILER
    bool profilerNotified = false;
#endif

    if (loader != NULL) {
        LOGVV("#### findClassNoInit(%s,%p,%p)\n", descriptor, loader,
            pDvmDex->pDexFile);
    }

    /*
     * We don't expect an exception to be raised at this point.  The
     * exception handling code is good about managing this.  This *can*
     * happen if a JNI lookup fails and the JNI code doesn't do any
     * error checking before doing another class lookup, so we may just
     * want to clear this and restore it on exit.  If we don't, some kinds
     * of failures can't be detected without rearranging other stuff.
     *
     * Most often when we hit this situation it means that something is
     * broken in the VM or in JNI code, so I'm keeping it in place (and
     * making it an informative abort rather than an assert).
     */
    if (dvmCheckException(self)) {
        LOGE("Class lookup %s attemped while exception %s pending\n",
            descriptor, dvmGetException(self)->clazz->descriptor);
        dvmDumpAllThreads(false);
        dvmAbort();
    }

    clazz = dvmLookupClass(descriptor, loader, true);
    if (clazz == NULL) {
        const DexClassDef* pClassDef;

#ifdef WITH_PROFILER
        dvmMethodTraceClassPrepBegin();
        profilerNotified = true;
#endif

#if LOG_CLASS_LOADING
        u8 startTime = dvmGetThreadCpuTimeNsec();
#endif

        if (pDvmDex == NULL) {
            assert(loader == NULL);     /* shouldn't be here otherwise */
            pDvmDex = searchBootPathForClass(descriptor, &pClassDef);
        } else {
            pClassDef = dexFindClass(pDvmDex->pDexFile, descriptor);
        }

        if (pDvmDex == NULL || pClassDef == NULL) {
            dvmThrowExceptionWithClassMessage(
                "Ljava/lang/NoClassDefFoundError;", descriptor);
            goto bail;
        }

        /* found a match, try to load it */
        clazz = loadClassFromDex(pDvmDex, pClassDef, loader);
        if (dvmCheckException(self)) {
            /* class was found but had issues */
            dvmReleaseTrackedAlloc((Object*) clazz, NULL);
            goto bail;
        }

        /*
         * Lock the class while we link it so other threads must wait for us
         * to finish.  Set the "initThreadId" so we can identify recursive
         * invocation.
         */
        dvmLockObject(self, (Object*) clazz);
        clazz->initThreadId = self->threadId;

        /*
         * Add to hash table so lookups succeed.
         *
         * [Are circular references possible when linking a class?]
         */
        assert(clazz->classLoader == loader);
        if (!dvmAddClassToHash(clazz)) {
            /*
             * Another thread must have loaded the class after we
             * started but before we finished.  Discard what we've
             * done and leave some hints for the GC.
             *
             * (Yes, this happens.)
             */
            //LOGW("WOW: somebody loaded %s simultaneously\n", descriptor);
            clazz->initThreadId = 0;
            dvmUnlockObject(self, (Object*) clazz);

            /* Let the GC free the class.
             */
            assert(clazz->obj.clazz == gDvm.unlinkedJavaLangClass);
            dvmReleaseTrackedAlloc((Object*) clazz, NULL);

            /* Grab the winning class.
             */
            clazz = dvmLookupClass(descriptor, loader, true);
            assert(clazz != NULL);
            goto got_class;
        }
        dvmReleaseTrackedAlloc((Object*) clazz, NULL);

#if LOG_CLASS_LOADING
        logClassLoadWithTime('>', clazz, startTime);
#endif
        /*
         * Prepare and resolve.
         */
        if (!dvmLinkClass(clazz, false)) {
            assert(dvmCheckException(self));

            /* Make note of the error and clean up the class.
             */
            removeClassFromHash(clazz);
            clazz->status = CLASS_ERROR;
            dvmFreeClassInnards(clazz);

            /* Let any waiters know.
             */
            clazz->initThreadId = 0;
            dvmObjectNotifyAll(self, (Object*) clazz);
            dvmUnlockObject(self, (Object*) clazz);

#if LOG_CLASS_LOADING
            LOG(LOG_INFO, "DVMLINK FAILED FOR CLASS ", "%s in %s\n",
                clazz->descriptor, get_process_name());

            /*
             * TODO: It would probably be better to use a new type code here (instead of '<') to
             * indicate the failure.  This change would require a matching change in the parser
             * and analysis code in frameworks/base/tools/preload.
             */
            logClassLoad('<', clazz);
#endif
            clazz = NULL;
            if (gDvm.optimizing) {
                /* happens with "external" libs */
                LOGV("Link of class '%s' failed\n", descriptor);
            } else {
                LOGW("Link of class '%s' failed\n", descriptor);
            }
            goto bail;
        }
        dvmObjectNotifyAll(self, (Object*) clazz);
        dvmUnlockObject(self, (Object*) clazz);

        /*
         * Add class stats to global counters.
         *
         * TODO: these should probably be atomic ops.
         */
        gDvm.numLoadedClasses++;
        gDvm.numDeclaredMethods +=
            clazz->virtualMethodCount + clazz->directMethodCount;
        gDvm.numDeclaredInstFields += clazz->ifieldCount;
        gDvm.numDeclaredStaticFields += clazz->sfieldCount;

        /*
         * Cache pointers to basic classes.  We want to use these in
         * various places, and it's easiest to initialize them on first
         * use rather than trying to force them to initialize (startup
         * ordering makes it weird).
         */
        if (gDvm.classJavaLangObject == NULL &&
            strcmp(descriptor, "Ljava/lang/Object;") == 0)
        {
            /* It should be impossible to get here with anything
             * but the bootclasspath loader.
             */
            assert(loader == NULL);
            gDvm.classJavaLangObject = clazz;
        }

#if LOG_CLASS_LOADING
        logClassLoad('<', clazz);
#endif

    } else {
got_class:
        if (!dvmIsClassLinked(clazz) && clazz->status != CLASS_ERROR) {
            /*
             * We can race with other threads for class linking.  We should
             * never get here recursively; doing so indicates that two
             * classes have circular dependencies.
             *
             * One exception: we force discovery of java.lang.Class in
             * dvmLinkClass(), and Class has Object as its superclass.  So
             * if the first thing we ever load is Object, we will init
             * Object->Class->Object.  The easiest way to avoid this is to
             * ensure that Object is never the first thing we look up, so
             * we get Foo->Class->Object instead.
             */
            dvmLockObject(self, (Object*) clazz);
            if (!dvmIsClassLinked(clazz) &&
                clazz->initThreadId == self->threadId)
            {
                LOGW("Recursive link on class %s\n", clazz->descriptor);
                dvmUnlockObject(self, (Object*) clazz);
                dvmThrowExceptionWithClassMessage(
                    "Ljava/lang/ClassCircularityError;", clazz->descriptor);
                clazz = NULL;
                goto bail;
            }
            //LOGI("WAITING  for '%s' (owner=%d)\n",
            //    clazz->descriptor, clazz->initThreadId);
            while (!dvmIsClassLinked(clazz) && clazz->status != CLASS_ERROR) {
                dvmObjectWait(self, (Object*) clazz, 0, 0, false);
            }
            dvmUnlockObject(self, (Object*) clazz);
        }
        if (clazz->status == CLASS_ERROR) {
            /*
             * Somebody else tried to load this and failed.  We need to raise
             * an exception and report failure.
             */
            throwEarlierClassFailure(clazz);
            clazz = NULL;
            goto bail;
        }
    }

    /* check some invariants */
    assert(dvmIsClassLinked(clazz));
    assert(gDvm.classJavaLangClass != NULL);
    assert(clazz->obj.clazz == gDvm.classJavaLangClass);
    if (clazz != gDvm.classJavaLangObject) {
        if (clazz->super == NULL) {
            LOGE("Non-Object has no superclass (gDvm.classJavaLangObject=%p)\n",
                gDvm.classJavaLangObject);
            dvmAbort();
        }
    }
    if (!dvmIsInterfaceClass(clazz)) {
        //LOGI("class=%s vtableCount=%d, virtualMeth=%d\n",
        //    clazz->descriptor, clazz->vtableCount,
        //    clazz->virtualMethodCount);
        assert(clazz->vtableCount >= clazz->virtualMethodCount);
    }

    /*
     * Normally class objects are initialized before we instantiate them,
     * but we can't do that with java.lang.Class (chicken, meet egg).  We
     * do it explicitly here.
     *
     * The verifier could call here to find Class while verifying Class,
     * so we need to check for CLASS_VERIFYING as well as !initialized.
     */
    if (clazz == gDvm.classJavaLangClass && !dvmIsClassInitialized(clazz) &&
        !(clazz->status == CLASS_VERIFYING))
    {
        LOGV("+++ explicitly initializing %s\n", clazz->descriptor);
        dvmInitClass(clazz);
    }

bail:
#ifdef WITH_PROFILER
    if (profilerNotified)
        dvmMethodTraceClassPrepEnd();
#endif
    assert(clazz != NULL || dvmCheckException(self));
    return clazz;
}

/*
 * Helper for loadClassFromDex, which takes a DexClassDataHeader and
 * encoded data pointer in addition to the other arguments.
 */
static ClassObject* loadClassFromDex0(DvmDex* pDvmDex,
    const DexClassDef* pClassDef, const DexClassDataHeader* pHeader,
    const u1* pEncodedData, Object* classLoader)
{
    ClassObject* newClass = NULL;
    const DexFile* pDexFile;
    const char* descriptor;
    int i;

    pDexFile = pDvmDex->pDexFile;
    descriptor = dexGetClassDescriptor(pDexFile, pClassDef);

    /*
     * Make sure the aren't any "bonus" flags set, since we use them for
     * runtime state.
     */
    if ((pClassDef->accessFlags & ~EXPECTED_FILE_FLAGS) != 0) {
        LOGW("Invalid file flags in class %s: %04x\n",
            descriptor, pClassDef->accessFlags);
        return NULL;
    }

    /*
     * Allocate storage for the class object on the GC heap, so that other
     * objects can have references to it.  We bypass the usual mechanism
     * (allocObject), because we don't have all the bits and pieces yet.
     *
     * Note that we assume that java.lang.Class does not override
     * finalize().
     */
    newClass = (ClassObject*) dvmMalloc(sizeof(*newClass), ALLOC_DEFAULT);
    if (newClass == NULL)
        return NULL;

    /* Until the class is loaded and linked, use a placeholder
     * obj->clazz value as a hint to the GC.  We don't want
     * the GC trying to scan the object while it's full of Idx
     * values.  Also, the real java.lang.Class may not exist
     * yet.
     */
    DVM_OBJECT_INIT(&newClass->obj, gDvm.unlinkedJavaLangClass);

    dvmSetClassSerialNumber(newClass);
    newClass->descriptor = descriptor;
    assert(newClass->descriptorAlloc == NULL);
    newClass->accessFlags = pClassDef->accessFlags;
    newClass->classLoader = classLoader;
    newClass->pDvmDex = pDvmDex;
    newClass->primitiveType = PRIM_NOT;

    /*
     * Stuff the superclass index into the object pointer field.  The linker
     * pulls it out and replaces it with a resolved ClassObject pointer.
     * I'm doing it this way (rather than having a dedicated superclassIdx
     * field) to save a few bytes of overhead per class.
     *
     * newClass->super is not traversed or freed by dvmFreeClassInnards, so
     * this is safe.
     */
    assert(sizeof(u4) == sizeof(ClassObject*));
    newClass->super = (ClassObject*) pClassDef->superclassIdx;

    /*
     * Stuff class reference indices into the pointer fields.
     *
     * The elements of newClass->interfaces are not traversed or freed by
     * dvmFreeClassInnards, so this is GC-safe.
     */
    const DexTypeList* pInterfacesList;
    pInterfacesList = dexGetInterfacesList(pDexFile, pClassDef);
    if (pInterfacesList != NULL) {
        newClass->interfaceCount = pInterfacesList->size;
        newClass->interfaces = (ClassObject**) dvmLinearAlloc(classLoader,
                newClass->interfaceCount * sizeof(ClassObject*));

        for (i = 0; i < newClass->interfaceCount; i++) {
            const DexTypeItem* pType = dexGetTypeItem(pInterfacesList, i);
            newClass->interfaces[i] = (ClassObject*)(u4) pType->typeIdx;
        }
        dvmLinearReadOnly(classLoader, newClass->interfaces);
    }

    /* load field definitions */

    /*
     * TODO: consider over-allocating the class object and appending the
     * static field info onto the end.  It's fixed-size and known at alloc
     * time.  This would save a couple of native heap allocations, but it
     * would also make heap compaction more difficult because we pass Field
     * pointers around internally.
     */

    if (pHeader->staticFieldsSize != 0) {
        /* static fields stay on system heap; field data isn't "write once" */
        int count = (int) pHeader->staticFieldsSize;
        u4 lastIndex = 0;
        DexField field;

        newClass->sfieldCount = count;
        newClass->sfields =
            (StaticField*) calloc(count, sizeof(StaticField));
        for (i = 0; i < count; i++) {
            dexReadClassDataField(&pEncodedData, &field, &lastIndex);
            loadSFieldFromDex(newClass, &field, &newClass->sfields[i]);
        }
    }

    if (pHeader->instanceFieldsSize != 0) {
        int count = (int) pHeader->instanceFieldsSize;
        u4 lastIndex = 0;
        DexField field;

        newClass->ifieldCount = count;
        newClass->ifields = (InstField*) dvmLinearAlloc(classLoader,
                count * sizeof(InstField));
        for (i = 0; i < count; i++) {
            dexReadClassDataField(&pEncodedData, &field, &lastIndex);
            loadIFieldFromDex(newClass, &field, &newClass->ifields[i]);
        }
        dvmLinearReadOnly(classLoader, newClass->ifields);
    }

    /* load method definitions */

    if (pHeader->directMethodsSize != 0) {
        int count = (int) pHeader->directMethodsSize;
        u4 lastIndex = 0;
        DexMethod method;

        newClass->directMethodCount = count;
        newClass->directMethods = (Method*) dvmLinearAlloc(classLoader,
                count * sizeof(Method));
        for (i = 0; i < count; i++) {
            dexReadClassDataMethod(&pEncodedData, &method, &lastIndex);
            loadMethodFromDex(newClass, &method, &newClass->directMethods[i]);
        }
        dvmLinearReadOnly(classLoader, newClass->directMethods);
    }

    if (pHeader->virtualMethodsSize != 0) {
        int count = (int) pHeader->virtualMethodsSize;
        u4 lastIndex = 0;
        DexMethod method;

        newClass->virtualMethodCount = count;
        newClass->virtualMethods = (Method*) dvmLinearAlloc(classLoader,
                count * sizeof(Method));
        for (i = 0; i < count; i++) {
            dexReadClassDataMethod(&pEncodedData, &method, &lastIndex);
            loadMethodFromDex(newClass, &method, &newClass->virtualMethods[i]);
        }
        dvmLinearReadOnly(classLoader, newClass->virtualMethods);
    }

    newClass->sourceFile = dexGetSourceFile(pDexFile, pClassDef);
    newClass->status = CLASS_LOADED;

    /* caller must call dvmReleaseTrackedAlloc */
    return newClass;
}

/*
 * Try to load the indicated class from the specified DEX file.
 *
 * This is effectively loadClass()+defineClass() for a DexClassDef.  The
 * loading was largely done when we crunched through the DEX.
 *
 * Returns NULL on failure.  If we locate the class but encounter an error
 * while processing it, an appropriate exception is thrown.
 */
static ClassObject* loadClassFromDex(DvmDex* pDvmDex,
    const DexClassDef* pClassDef, Object* classLoader)
{
    ClassObject* result;
    DexClassDataHeader header;
    const u1* pEncodedData;
    const DexFile* pDexFile;

    assert((pDvmDex != NULL) && (pClassDef != NULL));
    pDexFile = pDvmDex->pDexFile;

    if (gDvm.verboseClass) {
        LOGV("CLASS: loading '%s'...\n",
            dexGetClassDescriptor(pDexFile, pClassDef));
    }

    pEncodedData = dexGetClassData(pDexFile, pClassDef);

    if (pEncodedData != NULL) {
        dexReadClassDataHeader(&pEncodedData, &header);
    } else {
        // Provide an all-zeroes header for the rest of the loading.
        memset(&header, 0, sizeof(header));
    }

    result = loadClassFromDex0(pDvmDex, pClassDef, &header, pEncodedData,
            classLoader);

    if (gDvm.verboseClass && (result != NULL)) {
        LOGI("[Loaded %s from DEX %p (cl=%p)]\n",
            result->descriptor, pDvmDex, classLoader);
    }

    return result;
}

/*
 * Free anything in a ClassObject that was allocated on the system heap.
 *
 * The ClassObject itself is allocated on the GC heap, so we leave it for
 * the garbage collector.
 *
 * NOTE: this may be called with a partially-constructed object.
 * NOTE: there is no particular ordering imposed, so don't go poking at
 * superclasses.
 */
void dvmFreeClassInnards(ClassObject* clazz)
{
    void *tp;
    int i;

    if (clazz == NULL)
        return;

    assert(clazz->obj.clazz == gDvm.classJavaLangClass ||
           clazz->obj.clazz == gDvm.unlinkedJavaLangClass);

    /* Guarantee that dvmFreeClassInnards can be called on a given
     * class multiple times by clearing things out as we free them.
     * We don't make any attempt at real atomicity here; higher
     * levels need to make sure that no two threads can free the
     * same ClassObject at the same time.
     *
     * TODO: maybe just make it so the GC will never free the
     * innards of an already-freed class.
     *
     * TODO: this #define isn't MT-safe -- the compiler could rearrange it.
     */
#define NULL_AND_FREE(p) \
    do { \
        if ((p) != NULL) { \
            tp = (p); \
            (p) = NULL; \
            free(tp); \
        } \
    } while (0)
#define NULL_AND_LINEAR_FREE(p) \
    do { \
        if ((p) != NULL) { \
            tp = (p); \
            (p) = NULL; \
            dvmLinearFree(clazz->classLoader, tp); \
        } \
    } while (0)

    /* arrays just point at Object's vtable; don't free vtable in this case.
     * dvmIsArrayClass() checks clazz->descriptor, so we have to do this check
     * before freeing the name.
     */
    clazz->vtableCount = -1;
    if (dvmIsArrayClass(clazz)) {
        clazz->vtable = NULL;
    } else {
        NULL_AND_LINEAR_FREE(clazz->vtable);
    }

    clazz->descriptor = NULL;
    NULL_AND_FREE(clazz->descriptorAlloc);

    if (clazz->directMethods != NULL) {
        Method *directMethods = clazz->directMethods;
        int directMethodCount = clazz->directMethodCount;
        clazz->directMethods = NULL;
        clazz->directMethodCount = -1;
        for (i = 0; i < directMethodCount; i++) {
            freeMethodInnards(&directMethods[i]);
        }
        dvmLinearFree(clazz->classLoader, directMethods);
    }
    if (clazz->virtualMethods != NULL) {
        Method *virtualMethods = clazz->virtualMethods;
        int virtualMethodCount = clazz->virtualMethodCount;
        clazz->virtualMethodCount = -1;
        clazz->virtualMethods = NULL;
        for (i = 0; i < virtualMethodCount; i++) {
            freeMethodInnards(&virtualMethods[i]);
        }
        dvmLinearFree(clazz->classLoader, virtualMethods);
    }

    InitiatingLoaderList *loaderList = dvmGetInitiatingLoaderList(clazz);
    loaderList->initiatingLoaderCount = -1;
    NULL_AND_FREE(loaderList->initiatingLoaders);

    clazz->interfaceCount = -1;
    NULL_AND_LINEAR_FREE(clazz->interfaces);

    clazz->iftableCount = -1;
    NULL_AND_LINEAR_FREE(clazz->iftable);

    clazz->ifviPoolCount = -1;
    NULL_AND_LINEAR_FREE(clazz->ifviPool);

    clazz->sfieldCount = -1;
    NULL_AND_FREE(clazz->sfields);

    clazz->ifieldCount = -1;
    NULL_AND_LINEAR_FREE(clazz->ifields);

#undef NULL_AND_FREE
#undef NULL_AND_LINEAR_FREE
}

/*
 * Free anything in a Method that was allocated on the system heap.
 */
static void freeMethodInnards(Method* meth)
{
#if 0
    free(meth->exceptions);
    free(meth->lines);
    free(meth->locals);
#else
    // TODO: call dvmFreeRegisterMap() if meth->registerMap was allocated
    //       on the system heap
    UNUSED_PARAMETER(meth);
#endif
}

/*
 * Clone a Method, making new copies of anything that will be freed up
 * by freeMethodInnards().
 */
static void cloneMethod(Method* dst, const Method* src)
{
    memcpy(dst, src, sizeof(Method));
#if 0
    /* for current usage, these are never set, so no need to implement copy */
    assert(dst->exceptions == NULL);
    assert(dst->lines == NULL);
    assert(dst->locals == NULL);
#endif
}

/*
 * Pull the interesting pieces out of a DexMethod.
 *
 * The DEX file isn't going anywhere, so we don't need to make copies of
 * the code area.
 */
static void loadMethodFromDex(ClassObject* clazz, const DexMethod* pDexMethod,
    Method* meth)
{
    DexFile* pDexFile = clazz->pDvmDex->pDexFile;
    const DexMethodId* pMethodId;
    const DexCode* pDexCode;

    pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);

    meth->name = dexStringById(pDexFile, pMethodId->nameIdx);
    dexProtoSetFromMethodId(&meth->prototype, pDexFile, pMethodId);
    meth->shorty = dexProtoGetShorty(&meth->prototype);
    meth->accessFlags = pDexMethod->accessFlags;
    meth->clazz = clazz;
    meth->jniArgInfo = 0;

    if (dvmCompareNameDescriptorAndMethod("finalize", "()V", meth) == 0) {
        SET_CLASS_FLAG(clazz, CLASS_ISFINALIZABLE);
    }

    pDexCode = dexGetCode(pDexFile, pDexMethod);
    if (pDexCode != NULL) {
        /* integer constants, copy over for faster access */
        meth->registersSize = pDexCode->registersSize;
        meth->insSize = pDexCode->insSize;
        meth->outsSize = pDexCode->outsSize;

        /* pointer to code area */
        meth->insns = pDexCode->insns;
    } else {
        /*
         * We don't have a DexCode block, but we still want to know how
         * much space is needed for the arguments (so we don't have to
         * compute it later).  We also take this opportunity to compute
         * JNI argument info.
         *
         * We do this for abstract methods as well, because we want to
         * be able to substitute our exception-throwing "stub" in.
         */
        int argsSize = dvmComputeMethodArgsSize(meth);
        if (!dvmIsStaticMethod(meth))
            argsSize++;
        meth->registersSize = meth->insSize = argsSize;
        assert(meth->outsSize == 0);
        assert(meth->insns == NULL);

        if (dvmIsNativeMethod(meth)) {
            meth->nativeFunc = dvmResolveNativeMethod;
            meth->jniArgInfo = computeJniArgInfo(&meth->prototype);
        }
    }
}

/*
 * jniArgInfo (32-bit int) layout:
 *   SRRRHHHH HHHHHHHH HHHHHHHH HHHHHHHH
 *
 *   S - if set, do things the hard way (scan the signature)
 *   R - return-type enumeration
 *   H - target-specific hints
 *
 * This info is used at invocation time by dvmPlatformInvoke.  In most
 * cases, the target-specific hints allow dvmPlatformInvoke to avoid
 * having to fully parse the signature.
 *
 * The return-type bits are always set, even if target-specific hint bits
 * are unavailable.
 */
static int computeJniArgInfo(const DexProto* proto)
{
    const char* sig = dexProtoGetShorty(proto);
    int returnType, padFlags, jniArgInfo;
    char sigByte;
    int stackOffset, padMask;
    u4 hints;

    /* The first shorty character is the return type. */
    switch (*(sig++)) {
    case 'V':
        returnType = DALVIK_JNI_RETURN_VOID;
        break;
    case 'F':
        returnType = DALVIK_JNI_RETURN_FLOAT;
        break;
    case 'D':
        returnType = DALVIK_JNI_RETURN_DOUBLE;
        break;
    case 'J':
        returnType = DALVIK_JNI_RETURN_S8;
        break;
    default:
        returnType = DALVIK_JNI_RETURN_S4;
        break;
    }

    jniArgInfo = returnType << DALVIK_JNI_RETURN_SHIFT;

    hints = dvmPlatformInvokeHints(proto);

    if (hints & DALVIK_JNI_NO_ARG_INFO) {
        jniArgInfo |= DALVIK_JNI_NO_ARG_INFO;
    } else {
        assert((hints & DALVIK_JNI_RETURN_MASK) == 0);
        jniArgInfo |= hints;
    }

    return jniArgInfo;
}

/*
 * Load information about a static field.
 *
 * This also "prepares" static fields by initializing them
 * to their "standard default values".
 */
static void loadSFieldFromDex(ClassObject* clazz,
    const DexField* pDexSField, StaticField* sfield)
{
    DexFile* pDexFile = clazz->pDvmDex->pDexFile;
    const DexFieldId* pFieldId;

    pFieldId = dexGetFieldId(pDexFile, pDexSField->fieldIdx);

    sfield->field.clazz = clazz;
    sfield->field.name = dexStringById(pDexFile, pFieldId->nameIdx);
    sfield->field.signature = dexStringByTypeIdx(pDexFile, pFieldId->typeIdx);
    sfield->field.accessFlags = pDexSField->accessFlags;

    /* Static object field values are set to "standard default values"
     * (null or 0) until the class is initialized.  We delay loading
     * constant values from the class until that time.
     */
    //sfield->value.j = 0;
    assert(sfield->value.j == 0LL);     // cleared earlier with calloc

#ifdef PROFILE_FIELD_ACCESS
    sfield->field.gets = sfield->field.puts = 0;
#endif
}

/*
 * Load information about an instance field.
 */
static void loadIFieldFromDex(ClassObject* clazz,
    const DexField* pDexIField, InstField* ifield)
{
    DexFile* pDexFile = clazz->pDvmDex->pDexFile;
    const DexFieldId* pFieldId;

    pFieldId = dexGetFieldId(pDexFile, pDexIField->fieldIdx);

    ifield->field.clazz = clazz;
    ifield->field.name = dexStringById(pDexFile, pFieldId->nameIdx);
    ifield->field.signature = dexStringByTypeIdx(pDexFile, pFieldId->typeIdx);
    ifield->field.accessFlags = pDexIField->accessFlags;
#ifndef NDEBUG
    assert(ifield->byteOffset == 0);    // cleared earlier with calloc
    ifield->byteOffset = -1;    // make it obvious if we fail to set later
#endif

#ifdef PROFILE_FIELD_ACCESS
    ifield->field.gets = ifield->field.puts = 0;
#endif
}

/*
 * Cache java.lang.ref.Reference fields and methods.
 */
static bool precacheReferenceOffsets(ClassObject *clazz)
{
    Method *meth;
    int i;

    /* We trick the GC object scanner by not counting
     * java.lang.ref.Reference.referent as an object
     * field.  It will get explicitly scanned as part
     * of the reference-walking process.
     *
     * Find the object field named "referent" and put it
     * just after the list of object reference fields.
     */
    dvmLinearReadWrite(clazz->classLoader, clazz->ifields);
    for (i = 0; i < clazz->ifieldRefCount; i++) {
        InstField *pField = &clazz->ifields[i];
        if (strcmp(pField->field.name, "referent") == 0) {
            int targetIndex;

            /* Swap this field with the last object field.
             */
            targetIndex = clazz->ifieldRefCount - 1;
            if (i != targetIndex) {
                InstField *swapField = &clazz->ifields[targetIndex];
                InstField tmpField;
                int tmpByteOffset;

                /* It's not currently strictly necessary
                 * for the fields to be in byteOffset order,
                 * but it's more predictable that way.
                 */
                tmpByteOffset = swapField->byteOffset;
                swapField->byteOffset = pField->byteOffset;
                pField->byteOffset = tmpByteOffset;

                tmpField = *swapField;
                *swapField = *pField;
                *pField = tmpField;
            }

            /* One fewer object field (wink wink).
             */
            clazz->ifieldRefCount--;
            i--;        /* don't trip "didn't find it" test if field was last */
            break;
        }
    }
    dvmLinearReadOnly(clazz->classLoader, clazz->ifields);
    if (i == clazz->ifieldRefCount) {
        LOGE("Unable to reorder 'referent' in %s\n", clazz->descriptor);
        return false;
    }

    /* Cache pretty much everything about Reference so that
     * we don't need to call interpreted code when clearing/enqueueing
     * references.  This is fragile, so we'll be paranoid.
     */
    gDvm.classJavaLangRefReference = clazz;

    gDvm.offJavaLangRefReference_referent =
        dvmFindFieldOffset(gDvm.classJavaLangRefReference,
                "referent", "Ljava/lang/Object;");
    assert(gDvm.offJavaLangRefReference_referent >= 0);

    gDvm.offJavaLangRefReference_queue =
        dvmFindFieldOffset(gDvm.classJavaLangRefReference,
                "queue", "Ljava/lang/ref/ReferenceQueue;");
    assert(gDvm.offJavaLangRefReference_queue >= 0);

    gDvm.offJavaLangRefReference_queueNext =
        dvmFindFieldOffset(gDvm.classJavaLangRefReference,
                "queueNext", "Ljava/lang/ref/Reference;");
    assert(gDvm.offJavaLangRefReference_queueNext >= 0);

    gDvm.offJavaLangRefReference_vmData =
        dvmFindFieldOffset(gDvm.classJavaLangRefReference,
                "vmData", "I");
    assert(gDvm.offJavaLangRefReference_vmData >= 0);

#if FANCY_REFERENCE_SUBCLASS
    meth = dvmFindVirtualMethodByDescriptor(clazz, "clear", "()V");
    assert(meth != NULL);
    gDvm.voffJavaLangRefReference_clear = meth->methodIndex;

    meth = dvmFindVirtualMethodByDescriptor(clazz, "enqueue", "()Z");
    assert(meth != NULL);
    gDvm.voffJavaLangRefReference_enqueue = meth->methodIndex;
#else
    /* enqueueInternal() is private and thus a direct method. */
    meth = dvmFindDirectMethodByDescriptor(clazz, "enqueueInternal", "()Z");
    assert(meth != NULL);
    gDvm.methJavaLangRefReference_enqueueInternal = meth;
#endif

    return true;
}


/*
 * Link (prepare and resolve).  Verification is deferred until later.
 *
 * This converts symbolic references into pointers.  It's independent of
 * the source file format.
 *
 * If "classesResolved" is false, we assume that superclassIdx and
 * interfaces[] are holding class reference indices rather than pointers.
 * The class references will be resolved during link.  (This is done when
 * loading from DEX to avoid having to create additional storage to pass
 * the indices around.)
 *
 * Returns "false" with an exception pending on failure.
 */
bool dvmLinkClass(ClassObject* clazz, bool classesResolved)
{
    u4 superclassIdx = 0;
    bool okay = false;
    bool resolve_okay;
    int numInterfacesResolved = 0;
    int i;

    if (gDvm.verboseClass)
        LOGV("CLASS: linking '%s'...\n", clazz->descriptor);

    /* "Resolve" the class.
     *
     * At this point, clazz's reference fields contain Dex
     * file indices instead of direct object references.
     * We need to translate those indices into real references,
     * while making sure that the GC doesn't sweep any of
     * the referenced objects.
     *
     * The GC will avoid scanning this object as long as
     * clazz->obj.clazz is gDvm.unlinkedJavaLangClass.
     * Once clazz is ready, we'll replace clazz->obj.clazz
     * with gDvm.classJavaLangClass to let the GC know
     * to look at it.
     */
    assert(clazz->obj.clazz == gDvm.unlinkedJavaLangClass);

    /* It's important that we take care of java.lang.Class
     * first.  If we were to do this after looking up the
     * superclass (below), Class wouldn't be ready when
     * java.lang.Object needed it.
     *
     * Note that we don't set clazz->obj.clazz yet.
     */
    if (gDvm.classJavaLangClass == NULL) {
        if (clazz->classLoader == NULL &&
            strcmp(clazz->descriptor, "Ljava/lang/Class;") == 0)
        {
            gDvm.classJavaLangClass = clazz;
        } else {
            gDvm.classJavaLangClass =
                dvmFindSystemClassNoInit("Ljava/lang/Class;");
            if (gDvm.classJavaLangClass == NULL) {
                /* should have thrown one */
                assert(dvmCheckException(dvmThreadSelf()));
                goto bail;
            }
        }
    }
    assert(gDvm.classJavaLangClass != NULL);

    /*
     * Resolve all Dex indices so we can hand the ClassObject
     * over to the GC.  If we fail at any point, we need to remove
     * any tracked references to avoid leaking memory.
     */

    /*
     * All classes have a direct superclass, except for java/lang/Object.
     */
    if (!classesResolved) {
        superclassIdx = (u4) clazz->super;          /* unpack temp store */
        clazz->super = NULL;
    }
    if (strcmp(clazz->descriptor, "Ljava/lang/Object;") == 0) {
        assert(!classesResolved);
        if (superclassIdx != kDexNoIndex) {
            /* TODO: is this invariant true for all java/lang/Objects,
             * regardless of the class loader?  For now, assume it is.
             */
            dvmThrowException("Ljava/lang/ClassFormatError;",
                "java.lang.Object has a superclass");
            goto bail;
        }

        /* Don't finalize objects whose classes use the
         * default (empty) Object.finalize().
         */
        CLEAR_CLASS_FLAG(clazz, CLASS_ISFINALIZABLE);
    } else {
        if (!classesResolved) {
            if (superclassIdx == kDexNoIndex) {
                dvmThrowException("Ljava/lang/LinkageError;",
                    "no superclass defined");
                goto bail;
            }
            clazz->super = dvmResolveClass(clazz, superclassIdx, false);
            if (clazz->super == NULL) {
                assert(dvmCheckException(dvmThreadSelf()));
                if (gDvm.optimizing) {
                    /* happens with "external" libs */
                    LOGV("Unable to resolve superclass of %s (%d)\n",
                        clazz->descriptor, superclassIdx);
                } else {
                    LOGW("Unable to resolve superclass of %s (%d)\n",
                        clazz->descriptor, superclassIdx);
                }
                goto bail;
            }
        }
        /* verify */
        if (dvmIsFinalClass(clazz->super)) {
            LOGW("Superclass of '%s' is final '%s'\n",
                clazz->descriptor, clazz->super->descriptor);
            dvmThrowException("Ljava/lang/IncompatibleClassChangeError;",
                "superclass is final");
            goto bail;
        } else if (dvmIsInterfaceClass(clazz->super)) {
            LOGW("Superclass of '%s' is interface '%s'\n",
                clazz->descriptor, clazz->super->descriptor);
            dvmThrowException("Ljava/lang/IncompatibleClassChangeError;",
                "superclass is an interface");
            goto bail;
        } else if (!dvmCheckClassAccess(clazz, clazz->super)) {
            LOGW("Superclass of '%s' (%s) is not accessible\n",
                clazz->descriptor, clazz->super->descriptor);
            dvmThrowException("Ljava/lang/IllegalAccessError;",
                "superclass not accessible");
            goto bail;
        }

        /* Don't let the GC reclaim the superclass.
         * TODO: shouldn't be needed; remove when things stabilize
         */
        dvmAddTrackedAlloc((Object *)clazz->super, NULL);

        /* Inherit finalizability from the superclass.  If this
         * class also overrides finalize(), its CLASS_ISFINALIZABLE
         * bit will already be set.
         */
        if (IS_CLASS_FLAG_SET(clazz->super, CLASS_ISFINALIZABLE)) {
            SET_CLASS_FLAG(clazz, CLASS_ISFINALIZABLE);
        }

        /* See if this class descends from java.lang.Reference
         * and set the class flags appropriately.
         */
        if (IS_CLASS_FLAG_SET(clazz->super, CLASS_ISREFERENCE)) {
            u4 superRefFlags;

            /* We've already determined the reference type of this
             * inheritance chain.  Inherit reference-ness from the superclass.
             */
            superRefFlags = GET_CLASS_FLAG_GROUP(clazz->super,
                    CLASS_ISREFERENCE |
                    CLASS_ISWEAKREFERENCE |
                    CLASS_ISPHANTOMREFERENCE);
            SET_CLASS_FLAG(clazz, superRefFlags);
        } else if (clazz->classLoader == NULL &&
                clazz->super->classLoader == NULL &&
                strcmp(clazz->super->descriptor,
                       "Ljava/lang/ref/Reference;") == 0)
        {
            u4 refFlags;

            /* This class extends Reference, which means it should
             * be one of the magic Soft/Weak/PhantomReference classes.
             */
            refFlags = CLASS_ISREFERENCE;
            if (strcmp(clazz->descriptor,
                       "Ljava/lang/ref/SoftReference;") == 0)
            {
                /* Only CLASS_ISREFERENCE is set for soft references.
                 */
            } else if (strcmp(clazz->descriptor,
                       "Ljava/lang/ref/WeakReference;") == 0)
            {
                refFlags |= CLASS_ISWEAKREFERENCE;
            } else if (strcmp(clazz->descriptor,
                       "Ljava/lang/ref/PhantomReference;") == 0)
            {
                refFlags |= CLASS_ISPHANTOMREFERENCE;
            } else {
                /* No-one else is allowed to inherit directly
                 * from Reference.
                 */
//xxx is this the right exception?  better than an assertion.
                dvmThrowException("Ljava/lang/LinkageError;",
                    "illegal inheritance from Reference");
                goto bail;
            }

            /* The class should not have any reference bits set yet.
             */
            assert(GET_CLASS_FLAG_GROUP(clazz,
                    CLASS_ISREFERENCE |
                    CLASS_ISWEAKREFERENCE |
                    CLASS_ISPHANTOMREFERENCE) == 0);

            SET_CLASS_FLAG(clazz, refFlags);
        }
    }

    if (!classesResolved && clazz->interfaceCount > 0) {
        /*
         * Resolve the interfaces implemented directly by this class.  We
         * stuffed the class index into the interface pointer slot.
         */
        dvmLinearReadWrite(clazz->classLoader, clazz->interfaces);
        for (i = 0; i < clazz->interfaceCount; i++) {
            u4 interfaceIdx;

            interfaceIdx = (u4) clazz->interfaces[i];   /* unpack temp store */
            assert(interfaceIdx != kDexNoIndex);

            clazz->interfaces[i] = dvmResolveClass(clazz, interfaceIdx, false);
            if (clazz->interfaces[i] == NULL) {
                const DexFile* pDexFile = clazz->pDvmDex->pDexFile;

                assert(dvmCheckException(dvmThreadSelf()));
                dvmLinearReadOnly(clazz->classLoader, clazz->interfaces);

                const char* classDescriptor;
                classDescriptor = dexStringByTypeIdx(pDexFile, interfaceIdx);
                if (gDvm.optimizing) {
                    /* happens with "external" libs */
                    LOGV("Failed resolving %s interface %d '%s'\n",
                        clazz->descriptor, interfaceIdx, classDescriptor);
                } else {
                    LOGI("Failed resolving %s interface %d '%s'\n",
                        clazz->descriptor, interfaceIdx, classDescriptor);
                }
                goto bail_during_resolve;
            }

            /* are we allowed to implement this interface? */
            if (!dvmCheckClassAccess(clazz, clazz->interfaces[i])) {
                dvmLinearReadOnly(clazz->classLoader, clazz->interfaces);
                LOGW("Interface '%s' is not accessible to '%s'\n",
                    clazz->interfaces[i]->descriptor, clazz->descriptor);
                dvmThrowException("Ljava/lang/IllegalAccessError;",
                    "interface not accessible");
                goto bail_during_resolve;
            }

            /* Don't let the GC reclaim the interface class.
             * TODO: shouldn't be needed; remove when things stabilize
             */
            dvmAddTrackedAlloc((Object *)clazz->interfaces[i], NULL);
            numInterfacesResolved++;

            LOGVV("+++  found interface '%s'\n",
                clazz->interfaces[i]->descriptor);
        }
        dvmLinearReadOnly(clazz->classLoader, clazz->interfaces);
    }

    /*
     * The ClassObject is now in a GC-able state.  We let the GC
     * realize this by punching in the real class type, which is
     * always java.lang.Class.
     *
     * After this line, clazz will be fair game for the GC.
     * Every field that the GC will look at must now be valid:
     * - clazz->super
     * - class->classLoader
     * - clazz->sfields
     * - clazz->interfaces
     */
    clazz->obj.clazz = gDvm.classJavaLangClass;

    if (false) {
bail_during_resolve:
        resolve_okay = false;
    } else {
        resolve_okay = true;
    }

    /*
     * Now that the GC can scan the ClassObject, we can let
     * go of the explicit references we were holding onto.
     *
     * Either that or we failed, in which case we need to
     * release the references so we don't leak memory.
     */
    if (clazz->super != NULL) {
        dvmReleaseTrackedAlloc((Object *)clazz->super, NULL);
    }
    for (i = 0; i < numInterfacesResolved; i++) {
        dvmReleaseTrackedAlloc((Object *)clazz->interfaces[i], NULL);
    }

    if (!resolve_okay) {
        //LOGW("resolve_okay is false\n");
        goto bail;
    }

    /*
     * Populate vtable.
     */
    if (dvmIsInterfaceClass(clazz)) {
        /* no vtable; just set the method indices */
        int count = clazz->virtualMethodCount;

        if (count != (u2) count) {
            LOGE("Too many methods (%d) in interface '%s'\n", count,
                 clazz->descriptor);
            goto bail;
        }

        dvmLinearReadWrite(clazz->classLoader, clazz->virtualMethods);

        for (i = 0; i < count; i++)
            clazz->virtualMethods[i].methodIndex = (u2) i;

        dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);
    } else {
        if (!createVtable(clazz)) {
            LOGW("failed creating vtable\n");
            goto bail;
        }
    }

    /*
     * Populate interface method tables.  Can alter the vtable.
     */
    if (!createIftable(clazz))
        goto bail;

    /*
     * Insert special-purpose "stub" method implementations.
     */
    if (!insertMethodStubs(clazz))
        goto bail;

    /*
     * Compute instance field offsets and, hence, the size of the object.
     */
    if (!computeFieldOffsets(clazz))
        goto bail;

    /*
     * Cache fields and methods from java/lang/ref/Reference and
     * java/lang/Class.  This has to happen after computeFieldOffsets().
     */
    if (clazz->classLoader == NULL) {
        if (strcmp(clazz->descriptor, "Ljava/lang/ref/Reference;") == 0) {
            if (!precacheReferenceOffsets(clazz)) {
                LOGE("failed pre-caching Reference offsets\n");
                dvmThrowException("Ljava/lang/InternalError;", NULL);
                goto bail;
            }
        } else if (clazz == gDvm.classJavaLangClass) {
            gDvm.offJavaLangClass_pd = dvmFindFieldOffset(clazz, "pd",
                "Ljava/security/ProtectionDomain;");
            if (gDvm.offJavaLangClass_pd <= 0) {
                LOGE("ERROR: unable to find 'pd' field in Class\n");
                dvmAbort();     /* we're not going to get much farther */
                //goto bail;
            }
        }
    }

    /*
     * Done!
     */
    if (IS_CLASS_FLAG_SET(clazz, CLASS_ISPREVERIFIED))
        clazz->status = CLASS_VERIFIED;
    else
        clazz->status = CLASS_RESOLVED;
    okay = true;
    if (gDvm.verboseClass)
        LOGV("CLASS: linked '%s'\n", clazz->descriptor);

    /*
     * We send CLASS_PREPARE events to the debugger from here.  The
     * definition of "preparation" is creating the static fields for a
     * class and initializing them to the standard default values, but not
     * executing any code (that comes later, during "initialization").
     *
     * We did the static prep in loadSFieldFromDex() while loading the class.
     *
     * The class has been prepared and resolved but possibly not yet verified
     * at this point.
     */
    if (gDvm.debuggerActive) {
        dvmDbgPostClassPrepare(clazz);
    }

bail:
    if (!okay) {
        clazz->status = CLASS_ERROR;
        if (!dvmCheckException(dvmThreadSelf())) {
            dvmThrowException("Ljava/lang/VirtualMachineError;", NULL);
        }
    }
    return okay;
}

/*
 * Create the virtual method table.
 *
 * The top part of the table is a copy of the table from our superclass,
 * with our local methods overriding theirs.  The bottom part of the table
 * has any new methods we defined.
 */
static bool createVtable(ClassObject* clazz)
{
    bool result = false;
    int maxCount;
    int i;

    if (clazz->super != NULL) {
        //LOGI("SUPER METHODS %d %s->%s\n", clazz->super->vtableCount,
        //    clazz->descriptor, clazz->super->descriptor);
    }

    /* the virtual methods we define, plus the superclass vtable size */
    maxCount = clazz->virtualMethodCount;
    if (clazz->super != NULL) {
        maxCount += clazz->super->vtableCount;
    } else {
        /* TODO: is this invariant true for all java/lang/Objects,
         * regardless of the class loader?  For now, assume it is.
         */
        assert(strcmp(clazz->descriptor, "Ljava/lang/Object;") == 0);
    }
    //LOGD("+++ max vmethods for '%s' is %d\n", clazz->descriptor, maxCount);

    /*
     * Over-allocate the table, then realloc it down if necessary.  So
     * long as we don't allocate anything in between we won't cause
     * fragmentation, and reducing the size should be unlikely to cause
     * a buffer copy.
     */
    dvmLinearReadWrite(clazz->classLoader, clazz->virtualMethods);
    clazz->vtable = (Method**) dvmLinearAlloc(clazz->classLoader,
                        sizeof(Method*) * maxCount);
    if (clazz->vtable == NULL)
        goto bail;

    if (clazz->super != NULL) {
        int actualCount;

        memcpy(clazz->vtable, clazz->super->vtable,
            sizeof(*(clazz->vtable)) * clazz->super->vtableCount);
        actualCount = clazz->super->vtableCount;

        /*
         * See if any of our virtual methods override the superclass.
         */
        for (i = 0; i < clazz->virtualMethodCount; i++) {
            Method* localMeth = &clazz->virtualMethods[i];
            int si;

            for (si = 0; si < clazz->super->vtableCount; si++) {
                Method* superMeth = clazz->vtable[si];

                if (dvmCompareMethodNamesAndProtos(localMeth, superMeth) == 0)
                {
                    /* verify */
                    if (dvmIsFinalMethod(superMeth)) {
                        LOGW("Method %s.%s overrides final %s.%s\n",
                            localMeth->clazz->descriptor, localMeth->name,
                            superMeth->clazz->descriptor, superMeth->name);
                        goto bail;
                    }
                    clazz->vtable[si] = localMeth;
                    localMeth->methodIndex = (u2) si;
                    //LOGV("+++   override %s.%s (slot %d)\n",
                    //    clazz->descriptor, localMeth->name, si);
                    break;
                }
            }

            if (si == clazz->super->vtableCount) {
                /* not an override, add to end */
                clazz->vtable[actualCount] = localMeth;
                localMeth->methodIndex = (u2) actualCount;
                actualCount++;

                //LOGV("+++   add method %s.%s\n",
                //    clazz->descriptor, localMeth->name);
            }
        }

        if (actualCount != (u2) actualCount) {
            LOGE("Too many methods (%d) in class '%s'\n", actualCount,
                 clazz->descriptor);
            goto bail;
        }

        assert(actualCount <= maxCount);

        if (actualCount < maxCount) {
            assert(clazz->vtable != NULL);
            dvmLinearReadOnly(clazz->classLoader, clazz->vtable);
            clazz->vtable = dvmLinearRealloc(clazz->classLoader, clazz->vtable,
                sizeof(*(clazz->vtable)) * actualCount);
            if (clazz->vtable == NULL) {
                LOGE("vtable realloc failed\n");
                goto bail;
            } else {
                LOGVV("+++  reduced vtable from %d to %d\n",
                    maxCount, actualCount);
            }
        }

        clazz->vtableCount = actualCount;
    } else {
        /* java/lang/Object case */
        int count = clazz->virtualMethodCount;
        if (count != (u2) count) {
            LOGE("Too many methods (%d) in base class '%s'\n", count,
                 clazz->descriptor);
            goto bail;
        }

        for (i = 0; i < count; i++) {
            clazz->vtable[i] = &clazz->virtualMethods[i];
            clazz->virtualMethods[i].methodIndex = (u2) i;
        }
        clazz->vtableCount = clazz->virtualMethodCount;
    }

    result = true;

bail:
    dvmLinearReadOnly(clazz->classLoader, clazz->vtable);
    dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);
    return result;
}

/*
 * Create and populate "iftable".
 *
 * The set of interfaces we support is the combination of the interfaces
 * we implement directly and those implemented by our superclass.  Each
 * interface can have one or more "superinterfaces", which we must also
 * support.  For speed we flatten the tree out.
 *
 * We might be able to speed this up when there are lots of interfaces
 * by merge-sorting the class pointers and binary-searching when removing
 * duplicates.  We could also drop the duplicate removal -- it's only
 * there to reduce the memory footprint.
 *
 * Because of "Miranda methods", this may reallocate clazz->virtualMethods.
 *
 * Returns "true" on success.
 */
static bool createIftable(ClassObject* clazz)
{
    bool result = false;
    bool zapIftable = false;
    bool zapVtable = false;
    bool zapIfvipool = false;
    int ifCount, superIfCount, idx;
    int i;

    if (clazz->super != NULL)
        superIfCount = clazz->super->iftableCount;
    else
        superIfCount = 0;

    ifCount = superIfCount;
    ifCount += clazz->interfaceCount;
    for (i = 0; i < clazz->interfaceCount; i++)
        ifCount += clazz->interfaces[i]->iftableCount;

    LOGVV("INTF: class '%s' direct w/supra=%d super=%d total=%d\n",
        clazz->descriptor, ifCount - superIfCount, superIfCount, ifCount);

    if (ifCount == 0) {
        assert(clazz->iftableCount == 0);
        assert(clazz->iftable == NULL);
        result = true;
        goto bail;
    }

    /*
     * Create a table with enough space for all interfaces, and copy the
     * superclass' table in.
     */
    clazz->iftable = (InterfaceEntry*) dvmLinearAlloc(clazz->classLoader,
                        sizeof(InterfaceEntry) * ifCount);
    zapIftable = true;
    memset(clazz->iftable, 0x00, sizeof(InterfaceEntry) * ifCount);
    if (superIfCount != 0) {
        memcpy(clazz->iftable, clazz->super->iftable,
            sizeof(InterfaceEntry) * superIfCount);
    }

    /*
     * Create a flattened interface hierarchy of our immediate interfaces.
     */
    idx = superIfCount;

    for (i = 0; i < clazz->interfaceCount; i++) {
        ClassObject* interf;
        int j;

        interf = clazz->interfaces[i];
        assert(interf != NULL);

        /* make sure this is still an interface class */
        if (!dvmIsInterfaceClass(interf)) {
            LOGW("Class '%s' implements non-interface '%s'\n",
                clazz->descriptor, interf->descriptor);
            dvmThrowExceptionWithClassMessage(
                "Ljava/lang/IncompatibleClassChangeError;",
                clazz->descriptor);
            goto bail;
        }

        /* add entry for this interface */
        clazz->iftable[idx++].clazz = interf;

        /* add entries for the interface's superinterfaces */
        for (j = 0; j < interf->iftableCount; j++) {
            clazz->iftable[idx++].clazz = interf->iftable[j].clazz;
        }
    }

    assert(idx == ifCount);

    if (false) {
        /*
         * Remove anything redundant from our recent additions.  Note we have
         * to traverse the recent adds when looking for duplicates, because
         * it's possible the recent additions are self-redundant.  This
         * reduces the memory footprint of classes with lots of inherited
         * interfaces.
         *
         * (I don't know if this will cause problems later on when we're trying
         * to find a static field.  It looks like the proper search order is
         * (1) current class, (2) interfaces implemented by current class,
         * (3) repeat with superclass.  A field implemented by an interface
         * and by a superclass might come out wrong if the superclass also
         * implements the interface.  The javac compiler will reject the
         * situation as ambiguous, so the concern is somewhat artificial.)
         *
         * UPDATE: this makes ReferenceType.Interfaces difficult to implement,
         * because it wants to return just the interfaces declared to be
         * implemented directly by the class.  I'm excluding this code for now.
         */
        for (i = superIfCount; i < ifCount; i++) {
            int j;

            for (j = 0; j < ifCount; j++) {
                if (i == j)
                    continue;
                if (clazz->iftable[i].clazz == clazz->iftable[j].clazz) {
                    LOGVV("INTF: redundant interface %s in %s\n",
                        clazz->iftable[i].clazz->descriptor,
                        clazz->descriptor);

                    if (i != ifCount-1)
                        memmove(&clazz->iftable[i], &clazz->iftable[i+1],
                            (ifCount - i -1) * sizeof(InterfaceEntry));
                    ifCount--;
                    i--;        // adjust for i++ above
                    break;
                }
            }
        }
        LOGVV("INTF: class '%s' nodupes=%d\n", clazz->descriptor, ifCount);
    } // if (false)

    clazz->iftableCount = ifCount;

    /*
     * If we're an interface, we don't need the vtable pointers, so
     * we're done.  If this class doesn't implement an interface that our
     * superclass doesn't have, then we again have nothing to do.
     */
    if (dvmIsInterfaceClass(clazz) || superIfCount == ifCount) {
        //dvmDumpClass(clazz, kDumpClassFullDetail);
        result = true;
        goto bail;
    }

    /*
     * When we're handling invokeinterface, we probably have an object
     * whose type is an interface class rather than a concrete class.  We
     * need to convert the method reference into a vtable index.  So, for
     * every entry in "iftable", we create a list of vtable indices.
     *
     * Because our vtable encompasses the superclass vtable, we can use
     * the vtable indices from our superclass for all of the interfaces
     * that weren't directly implemented by us.
     *
     * Each entry in "iftable" has a pointer to the start of its set of
     * vtable offsets.  The iftable entries in the superclass point to
     * storage allocated in the superclass, and the iftable entries added
     * for this class point to storage allocated in this class.  "iftable"
     * is flat for fast access in a class and all of its subclasses, but
     * "ifviPool" is only created for the topmost implementor.
     */
    int poolSize = 0;
    for (i = superIfCount; i < ifCount; i++) {
        /*
         * Note it's valid for an interface to have no methods (e.g.
         * java/io/Serializable).
         */
        LOGVV("INTF: pool: %d from %s\n",
            clazz->iftable[i].clazz->virtualMethodCount,
            clazz->iftable[i].clazz->descriptor);
        poolSize += clazz->iftable[i].clazz->virtualMethodCount;
    }

    if (poolSize == 0) {
        LOGVV("INTF: didn't find any new interfaces with methods\n");
        result = true;
        goto bail;
    }

    clazz->ifviPoolCount = poolSize;
    clazz->ifviPool = (int*) dvmLinearAlloc(clazz->classLoader,
                        poolSize * sizeof(int*));
    zapIfvipool = true;

    /*
     * Fill in the vtable offsets for the interfaces that weren't part of
     * our superclass.
     */
    int poolOffset = 0;
    Method** mirandaList = NULL;
    int mirandaCount = 0, mirandaAlloc = 0;

    for (i = superIfCount; i < ifCount; i++) {
        ClassObject* interface;
        int methIdx;

        clazz->iftable[i].methodIndexArray = clazz->ifviPool + poolOffset;
        interface = clazz->iftable[i].clazz;
        poolOffset += interface->virtualMethodCount;    // end here

        /*
         * For each method listed in the interface's method list, find the
         * matching method in our class's method list.  We want to favor the
         * subclass over the superclass, which just requires walking
         * back from the end of the vtable.  (This only matters if the
         * superclass defines a private method and this class redefines
         * it -- otherwise it would use the same vtable slot.  In Dalvik
         * those don't end up in the virtual method table, so it shouldn't
         * matter which direction we go.  We walk it backward anyway.)
         *
         *
         * Suppose we have the following arrangement:
         *   public interface MyInterface
         *     public boolean inInterface();
         *   public abstract class MirandaAbstract implements MirandaInterface
         *     //public abstract boolean inInterface(); // not declared!
         *     public boolean inAbstract() { stuff }    // in vtable
         *   public class MirandClass extends MirandaAbstract
         *     public boolean inInterface() { stuff }
         *     public boolean inAbstract() { stuff }    // in vtable
         *
         * The javac compiler happily compiles MirandaAbstract even though
         * it doesn't declare all methods from its interface.  When we try
         * to set up a vtable for MirandaAbstract, we find that we don't
         * have an slot for inInterface.  To prevent this, we synthesize
         * abstract method declarations in MirandaAbstract.
         *
         * We have to expand vtable and update some things that point at it,
         * so we accumulate the method list and do it all at once below.
         */
        for (methIdx = 0; methIdx < interface->virtualMethodCount; methIdx++) {
            Method* imeth = &interface->virtualMethods[methIdx];
            int j;

            IF_LOGVV() {
                char* desc = dexProtoCopyMethodDescriptor(&imeth->prototype);
                LOGVV("INTF:  matching '%s' '%s'\n", imeth->name, desc);
                free(desc);
            }

            for (j = clazz->vtableCount-1; j >= 0; j--) {
                if (dvmCompareMethodNamesAndProtos(imeth, clazz->vtable[j])
                    == 0)
                {
                    LOGVV("INTF:   matched at %d\n", j);
                    if (!dvmIsPublicMethod(clazz->vtable[j])) {
                        LOGW("Implementation of %s.%s is not public\n",
                            clazz->descriptor, clazz->vtable[j]->name);
                        dvmThrowException("Ljava/lang/IllegalAccessError;",
                            "interface implementation not public");
                        goto bail;
                    }
                    clazz->iftable[i].methodIndexArray[methIdx] = j;
                    break;
                }
            }
            if (j < 0) {
                IF_LOGV() {
                    char* desc =
                        dexProtoCopyMethodDescriptor(&imeth->prototype);
                    LOGV("No match for '%s' '%s' in '%s' (creating miranda)\n",
                            imeth->name, desc, clazz->descriptor);
                    free(desc);
                }
                //dvmThrowException("Ljava/lang/RuntimeException;", "Miranda!");
                //return false;

                if (mirandaCount == mirandaAlloc) {
                    mirandaAlloc += 8;
                    if (mirandaList == NULL) {
                        mirandaList = dvmLinearAlloc(clazz->classLoader,
                                        mirandaAlloc * sizeof(Method*));
                    } else {
                        dvmLinearReadOnly(clazz->classLoader, mirandaList);
                        mirandaList = dvmLinearRealloc(clazz->classLoader,
                                mirandaList, mirandaAlloc * sizeof(Method*));
                    }
                    assert(mirandaList != NULL);    // mem failed + we leaked
                }

                /*
                 * These may be redundant (e.g. method with same name and
                 * signature declared in two interfaces implemented by the
                 * same abstract class).  We can squeeze the duplicates
                 * out here.
                 */
                int mir;
                for (mir = 0; mir < mirandaCount; mir++) {
                    if (dvmCompareMethodNamesAndProtos(
                            mirandaList[mir], imeth) == 0)
                    {
                        IF_LOGVV() {
                            char* desc = dexProtoCopyMethodDescriptor(
                                    &imeth->prototype);
                            LOGVV("MIRANDA dupe: %s and %s %s%s\n",
                                mirandaList[mir]->clazz->descriptor,
                                imeth->clazz->descriptor,
                                imeth->name, desc);
                            free(desc);
                        }
                        break;
                    }
                }

                /* point the iftable at a phantom slot index */
                clazz->iftable[i].methodIndexArray[methIdx] =
                    clazz->vtableCount + mir;
                LOGVV("MIRANDA: %s points at slot %d\n",
                    imeth->name, clazz->vtableCount + mir);

                /* if non-duplicate among Mirandas, add to Miranda list */
                if (mir == mirandaCount) {
                    //LOGV("MIRANDA: holding '%s' in slot %d\n",
                    //    imeth->name, mir);
                    mirandaList[mirandaCount++] = imeth;
                }
            }
        }
    }

    if (mirandaCount != 0) {
        Method* newVirtualMethods;
        Method* meth;
        int oldMethodCount, oldVtableCount;

        for (i = 0; i < mirandaCount; i++) {
            LOGVV("MIRANDA %d: %s.%s\n", i,
                mirandaList[i]->clazz->descriptor, mirandaList[i]->name);
        }

        /*
         * We found methods in one or more interfaces for which we do not
         * have vtable entries.  We have to expand our virtualMethods
         * table (which might be empty) to hold some new entries.
         */
        if (clazz->virtualMethods == NULL) {
            newVirtualMethods = (Method*) dvmLinearAlloc(clazz->classLoader,
                sizeof(Method) * (clazz->virtualMethodCount + mirandaCount));
        } else {
            //dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);
            newVirtualMethods = (Method*) dvmLinearRealloc(clazz->classLoader,
                clazz->virtualMethods,
                sizeof(Method) * (clazz->virtualMethodCount + mirandaCount));
        }
        if (newVirtualMethods != clazz->virtualMethods) {
            /*
             * Table was moved in memory.  We have to run through the
             * vtable and fix the pointers.  The vtable entries might be
             * pointing at superclasses, so we flip it around: run through
             * all locally-defined virtual methods, and fix their entries
             * in the vtable.  (This would get really messy if sub-classes
             * had already been loaded.)
             *
             * Reminder: clazz->virtualMethods and clazz->virtualMethodCount
             * hold the virtual methods declared by this class.  The
             * method's methodIndex is the vtable index, and is the same
             * for all sub-classes (and all super classes in which it is
             * defined).  We're messing with these because the Miranda
             * stuff makes it look like the class actually has an abstract
             * method declaration in it.
             */
            LOGVV("MIRANDA fixing vtable pointers\n");
            dvmLinearReadWrite(clazz->classLoader, clazz->vtable);
            Method* meth = newVirtualMethods;
            for (i = 0; i < clazz->virtualMethodCount; i++, meth++)
                clazz->vtable[meth->methodIndex] = meth;
            dvmLinearReadOnly(clazz->classLoader, clazz->vtable);
        }

        oldMethodCount = clazz->virtualMethodCount;
        clazz->virtualMethods = newVirtualMethods;
        clazz->virtualMethodCount += mirandaCount;

        dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);

        /*
         * We also have to expand the vtable.
         */
        assert(clazz->vtable != NULL);
        clazz->vtable = (Method**) dvmLinearRealloc(clazz->classLoader,
                        clazz->vtable,
                        sizeof(Method*) * (clazz->vtableCount + mirandaCount));
        if (clazz->vtable == NULL) {
            assert(false);
            goto bail;
        }
        zapVtable = true;

        oldVtableCount = clazz->vtableCount;
        clazz->vtableCount += mirandaCount;

        /*
         * Now we need to create the fake methods.  We clone the abstract
         * method definition from the interface and then replace a few
         * things.
         */
        meth = clazz->virtualMethods + oldMethodCount;
        for (i = 0; i < mirandaCount; i++, meth++) {
            dvmLinearReadWrite(clazz->classLoader, clazz->virtualMethods);
            cloneMethod(meth, mirandaList[i]);
            meth->clazz = clazz;
            meth->accessFlags |= ACC_MIRANDA;
            meth->methodIndex = (u2) (oldVtableCount + i);
            dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);

            /* point the new vtable entry at the new method */
            clazz->vtable[oldVtableCount + i] = meth;
        }

        dvmLinearReadOnly(clazz->classLoader, mirandaList);
        dvmLinearFree(clazz->classLoader, mirandaList);

    }

    /*
     * TODO?
     * Sort the interfaces by number of declared methods.  All we really
     * want is to get the interfaces with zero methods at the end of the
     * list, so that when we walk through the list during invoke-interface
     * we don't examine interfaces that can't possibly be useful.
     *
     * The set will usually be small, so a simple insertion sort works.
     *
     * We have to be careful not to change the order of two interfaces
     * that define the same method.  (Not a problem if we only move the
     * zero-method interfaces to the end.)
     *
     * PROBLEM:
     * If we do this, we will no longer be able to identify super vs.
     * current class interfaces by comparing clazz->super->iftableCount.  This
     * breaks anything that only wants to find interfaces declared directly
     * by the class (dvmFindStaticFieldHier, ReferenceType.Interfaces,
     * dvmDbgOutputAllInterfaces, etc).  Need to provide a workaround.
     *
     * We can sort just the interfaces implemented directly by this class,
     * but that doesn't seem like it would provide much of an advantage.  I'm
     * not sure this is worthwhile.
     *
     * (This has been made largely obsolete by the interface cache mechanism.)
     */

    //dvmDumpClass(clazz);

    result = true;

bail:
    if (zapIftable)
        dvmLinearReadOnly(clazz->classLoader, clazz->iftable);
    if (zapVtable)
        dvmLinearReadOnly(clazz->classLoader, clazz->vtable);
    if (zapIfvipool)
        dvmLinearReadOnly(clazz->classLoader, clazz->ifviPool);
    return result;
}


/*
 * Provide "stub" implementations for methods without them.
 *
 * Currently we provide an implementation for all abstract methods that
 * throws an AbstractMethodError exception.  This allows us to avoid an
 * explicit check for abstract methods in every virtual call.
 *
 * NOTE: for Miranda methods, the method declaration is a clone of what
 * was found in the interface class.  That copy may already have had the
 * function pointer filled in, so don't be surprised if it's not NULL.
 *
 * NOTE: this sets the "native" flag, giving us an "abstract native" method,
 * which is nonsensical.  Need to make sure that this doesn't escape the
 * VM.  We can either mask it out in reflection calls, or copy "native"
 * into the high 16 bits of accessFlags and check that internally.
 */
static bool insertMethodStubs(ClassObject* clazz)
{
    dvmLinearReadWrite(clazz->classLoader, clazz->virtualMethods);

    Method* meth;
    int i;

    meth = clazz->virtualMethods;
    for (i = 0; i < clazz->virtualMethodCount; i++, meth++) {
        if (dvmIsAbstractMethod(meth)) {
            assert(meth->insns == NULL);
            assert(meth->nativeFunc == NULL ||
                meth->nativeFunc == (DalvikBridgeFunc)dvmAbstractMethodStub);

            meth->accessFlags |= ACC_NATIVE;
            meth->nativeFunc = (DalvikBridgeFunc) dvmAbstractMethodStub;
        }
    }

    dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);
    return true;
}


/*
 * Swap two instance fields.
 */
static inline void swapField(InstField* pOne, InstField* pTwo)
{
    InstField swap;

    LOGVV("  --- swap '%s' and '%s'\n", pOne->field.name, pTwo->field.name);
    swap = *pOne;
    *pOne = *pTwo;
    *pTwo = swap;
}

/*
 * Assign instance fields to u4 slots.
 *
 * The top portion of the instance field area is occupied by the superclass
 * fields, the bottom by the fields for this class.
 *
 * "long" and "double" fields occupy two adjacent slots.  On some
 * architectures, 64-bit quantities must be 64-bit aligned, so we need to
 * arrange fields (or introduce padding) to ensure this.  We assume the
 * fields of the topmost superclass (i.e. Object) are 64-bit aligned, so
 * we can just ensure that the offset is "even".  To avoid wasting space,
 * we want to move non-reference 32-bit fields into gaps rather than
 * creating pad words.
 *
 * In the worst case we will waste 4 bytes, but because objects are
 * allocated on >= 64-bit boundaries, those bytes may well be wasted anyway
 * (assuming this is the most-derived class).
 *
 * Pad words are not represented in the field table, so the field table
 * itself does not change size.
 *
 * The number of field slots determines the size of the object, so we
 * set that here too.
 *
 * This function feels a little more complicated than I'd like, but it
 * has the property of moving the smallest possible set of fields, which
 * should reduce the time required to load a class.
 *
 * NOTE: reference fields *must* come first, or precacheReferenceOffsets()
 * will break.
 */
static bool computeFieldOffsets(ClassObject* clazz)
{
    int fieldOffset;
    int i, j;

    dvmLinearReadWrite(clazz->classLoader, clazz->ifields);

    if (clazz->super != NULL)
        fieldOffset = clazz->super->objectSize;
    else
        fieldOffset = offsetof(DataObject, instanceData);

    LOGVV("--- computeFieldOffsets '%s'\n", clazz->descriptor);

    //LOGI("OFFSETS fieldCount=%d\n", clazz->ifieldCount);
    //LOGI("dataobj, instance: %d\n", offsetof(DataObject, instanceData));
    //LOGI("classobj, access: %d\n", offsetof(ClassObject, accessFlags));
    //LOGI("super=%p, fieldOffset=%d\n", clazz->super, fieldOffset);

    /*
     * Start by moving all reference fields to the front.
     */
    clazz->ifieldRefCount = 0;
    j = clazz->ifieldCount - 1;
    for (i = 0; i < clazz->ifieldCount; i++) {
        InstField* pField = &clazz->ifields[i];
        char c = pField->field.signature[0];

        if (c != '[' && c != 'L') {
            /* This isn't a reference field; see if any reference fields
             * follow this one.  If so, we'll move it to this position.
             * (quicksort-style partitioning)
             */
            while (j > i) {
                InstField* refField = &clazz->ifields[j--];
                char rc = refField->field.signature[0];

                if (rc == '[' || rc == 'L') {
                    /* Here's a reference field that follows at least one
                     * non-reference field.  Swap it with the current field.
                     * (When this returns, "pField" points to the reference
                     * field, and "refField" points to the non-ref field.)
                     */
                    swapField(pField, refField);

                    /* Fix the signature.
                     */
                    c = rc;

                    clazz->ifieldRefCount++;
                    break;
                }
            }
            /* We may or may not have swapped a field.
             */
        } else {
            /* This is a reference field.
             */
            clazz->ifieldRefCount++;
        }

        /*
         * If we've hit the end of the reference fields, break.
         */
        if (c != '[' && c != 'L')
            break;

        pField->byteOffset = fieldOffset;
        fieldOffset += sizeof(u4);
        LOGVV("  --- offset1 '%s'=%d\n", pField->field.name,pField->byteOffset);
    }

    /*
     * Now we want to pack all of the double-wide fields together.  If we're
     * not aligned, though, we want to shuffle one 32-bit field into place.
     * If we can't find one, we'll have to pad it.
     */
    if (i != clazz->ifieldCount && (fieldOffset & 0x04) != 0) {
        LOGVV("  +++ not aligned\n");

        InstField* pField = &clazz->ifields[i];
        char c = pField->field.signature[0];

        if (c != 'J' && c != 'D') {
            /*
             * The field that comes next is 32-bit, so just advance past it.
             */
            assert(c != '[' && c != 'L');
            pField->byteOffset = fieldOffset;
            fieldOffset += sizeof(u4);
            i++;
            LOGVV("  --- offset2 '%s'=%d\n",
                pField->field.name, pField->byteOffset);
        } else {
            /*
             * Next field is 64-bit, so search for a 32-bit field we can
             * swap into it.
             */
            bool found = false;
            j = clazz->ifieldCount - 1;
            while (j > i) {
                InstField* singleField = &clazz->ifields[j--];
                char rc = singleField->field.signature[0];

                if (rc != 'J' && rc != 'D') {
                    swapField(pField, singleField);
                    //c = rc;
                    LOGVV("  +++ swapped '%s' for alignment\n",
                        pField->field.name);
                    pField->byteOffset = fieldOffset;
                    fieldOffset += sizeof(u4);
                    LOGVV("  --- offset3 '%s'=%d\n",
                        pField->field.name, pField->byteOffset);
                    found = true;
                    i++;
                    break;
                }
            }
            if (!found) {
                LOGV("  +++ inserting pad field in '%s'\n", clazz->descriptor);
                fieldOffset += sizeof(u4);
            }
        }
    }

    /*
     * Alignment is good, shuffle any double-wide fields forward, and
     * finish assigning field offsets to all fields.
     */
    assert(i == clazz->ifieldCount || (fieldOffset & 0x04) == 0);
    j = clazz->ifieldCount - 1;
    for ( ; i < clazz->ifieldCount; i++) {
        InstField* pField = &clazz->ifields[i];
        char c = pField->field.signature[0];

        if (c != 'D' && c != 'J') {
            /* This isn't a double-wide field; see if any double fields
             * follow this one.  If so, we'll move it to this position.
             * (quicksort-style partitioning)
             */
            while (j > i) {
                InstField* doubleField = &clazz->ifields[j--];
                char rc = doubleField->field.signature[0];

                if (rc == 'D' || rc == 'J') {
                    /* Here's a double-wide field that follows at least one
                     * non-double field.  Swap it with the current field.
                     * (When this returns, "pField" points to the reference
                     * field, and "doubleField" points to the non-double field.)
                     */
                    swapField(pField, doubleField);
                    c = rc;

                    break;
                }
            }
            /* We may or may not have swapped a field.
             */
        } else {
            /* This is a double-wide field, leave it be.
             */
        }

        pField->byteOffset = fieldOffset;
        LOGVV("  --- offset4 '%s'=%d\n", pField->field.name,pField->byteOffset);
        fieldOffset += sizeof(u4);
        if (c == 'J' || c == 'D')
            fieldOffset += sizeof(u4);
    }

#ifndef NDEBUG
    /* Make sure that all reference fields appear before
     * non-reference fields, and all double-wide fields are aligned.
     */
    j = 0;  // seen non-ref
    for (i = 0; i < clazz->ifieldCount; i++) {
        InstField *pField = &clazz->ifields[i];
        char c = pField->field.signature[0];

        if (c == 'D' || c == 'J') {
            assert((pField->byteOffset & 0x07) == 0);
        }

        if (c != '[' && c != 'L') {
            if (!j) {
                assert(i == clazz->ifieldRefCount);
                j = 1;
            }
        } else if (j) {
            assert(false);
        }
    }
    if (!j) {
        assert(clazz->ifieldRefCount == clazz->ifieldCount);
    }
#endif

    /*
     * We map a C struct directly on top of java/lang/Class objects.  Make
     * sure we left enough room for the instance fields.
     */
    assert(clazz != gDvm.classJavaLangClass || (size_t)fieldOffset <
        offsetof(ClassObject, instanceData) + sizeof(clazz->instanceData));

    clazz->objectSize = fieldOffset;

    dvmLinearReadOnly(clazz->classLoader, clazz->ifields);
    return true;
}

/*
 * Throw the VM-spec-mandated error when an exception is thrown during
 * class initialization.
 *
 * The safest way to do this is to call the ExceptionInInitializerError
 * constructor that takes a Throwable.
 *
 * [Do we want to wrap it if the original is an Error rather than
 * an Exception?]
 */
static void throwClinitError(void)
{
    Thread* self = dvmThreadSelf();
    Object* exception;
    Object* eiie;

    exception = dvmGetException(self);
    dvmAddTrackedAlloc(exception, self);
    dvmClearException(self);

    if (gDvm.classJavaLangExceptionInInitializerError == NULL) {
        /*
         * Always resolves to same thing -- no race condition.
         */
        gDvm.classJavaLangExceptionInInitializerError =
            dvmFindSystemClass(
                    "Ljava/lang/ExceptionInInitializerError;");
        if (gDvm.classJavaLangExceptionInInitializerError == NULL) {
            LOGE("Unable to prep java/lang/ExceptionInInitializerError\n");
            goto fail;
        }

        gDvm.methJavaLangExceptionInInitializerError_init =
            dvmFindDirectMethodByDescriptor(gDvm.classJavaLangExceptionInInitializerError,
            "<init>", "(Ljava/lang/Throwable;)V");
        if (gDvm.methJavaLangExceptionInInitializerError_init == NULL) {
            LOGE("Unable to prep java/lang/ExceptionInInitializerError\n");
            goto fail;
        }
    }

    eiie = dvmAllocObject(gDvm.classJavaLangExceptionInInitializerError,
                ALLOC_DEFAULT);
    if (eiie == NULL)
        goto fail;

    /*
     * Construct the new object, and replace the exception with it.
     */
    JValue unused;
    dvmCallMethod(self, gDvm.methJavaLangExceptionInInitializerError_init,
        eiie, &unused, exception);
    dvmSetException(self, eiie);
    dvmReleaseTrackedAlloc(eiie, NULL);
    dvmReleaseTrackedAlloc(exception, self);
    return;

fail:       /* restore original exception */
    dvmSetException(self, exception);
    dvmReleaseTrackedAlloc(exception, self);
    return;
}

/*
 * The class failed to initialize on a previous attempt, so we want to throw
 * a NoClassDefFoundError (v2 2.17.5).  The exception to this rule is if we
 * failed in verification, in which case v2 5.4.1 says we need to re-throw
 * the previous error.
 */
static void throwEarlierClassFailure(ClassObject* clazz)
{
    LOGI("Rejecting re-init on previously-failed class %s v=%p\n",
        clazz->descriptor, clazz->verifyErrorClass);

    if (clazz->verifyErrorClass == NULL) {
        dvmThrowExceptionWithClassMessage("Ljava/lang/NoClassDefFoundError;",
            clazz->descriptor);
    } else {
        dvmThrowExceptionByClassWithClassMessage(clazz->verifyErrorClass,
            clazz->descriptor);
    }
}

/*
 * Initialize any static fields whose values are stored in
 * the DEX file.  This must be done during class initialization.
 */
static void initSFields(ClassObject* clazz)
{
    Thread* self = dvmThreadSelf(); /* for dvmReleaseTrackedAlloc() */
    DexFile* pDexFile;
    const DexClassDef* pClassDef;
    const DexEncodedArray* pValueList;
    EncodedArrayIterator iterator;
    int i;

    if (clazz->sfieldCount == 0) {
        return;
    }
    if (clazz->pDvmDex == NULL) {
        /* generated class; any static fields should already be set up */
        LOGV("Not initializing static fields in %s\n", clazz->descriptor);
        return;
    }
    pDexFile = clazz->pDvmDex->pDexFile;

    pClassDef = dexFindClass(pDexFile, clazz->descriptor);
    assert(pClassDef != NULL);

    pValueList = dexGetStaticValuesList(pDexFile, pClassDef);
    if (pValueList == NULL) {
        return;
    }

    dvmEncodedArrayIteratorInitialize(&iterator, pValueList, clazz);

    /*
     * Iterate over the initial values array, setting the corresponding
     * static field for each array element.
     */

    for (i = 0; dvmEncodedArrayIteratorHasNext(&iterator); i++) {
        AnnotationValue value;
        bool parsed = dvmEncodedArrayIteratorGetNext(&iterator, &value);
        StaticField* sfield = &clazz->sfields[i];
        const char* descriptor = sfield->field.signature;
        bool needRelease = false;

        if (! parsed) {
            /*
             * TODO: Eventually verification should attempt to ensure
             * that this can't happen at least due to a data integrity
             * problem.
             */
            LOGE("Static initializer parse failed for %s at index %d",
                    clazz->descriptor, i);
            dvmAbort();
        }

        /* Verify that the value we got was of a valid type. */

        switch (descriptor[0]) {
            case 'Z': parsed = (value.type == kDexAnnotationBoolean); break;
            case 'B': parsed = (value.type == kDexAnnotationByte);    break;
            case 'C': parsed = (value.type == kDexAnnotationChar);    break;
            case 'S': parsed = (value.type == kDexAnnotationShort);   break;
            case 'I': parsed = (value.type == kDexAnnotationInt);     break;
            case 'J': parsed = (value.type == kDexAnnotationLong);    break;
            case 'F': parsed = (value.type == kDexAnnotationFloat);   break;
            case 'D': parsed = (value.type == kDexAnnotationDouble);  break;
            case '[': parsed = (value.type == kDexAnnotationNull);    break;
            case 'L': {
                switch (value.type) {
                    case kDexAnnotationNull: {
                        /* No need for further tests. */
                        break;
                    }
                    case kDexAnnotationString: {
                        parsed =
                            (strcmp(descriptor, "Ljava/lang/String;") == 0);
                        needRelease = true;
                        break;
                    }
                    case kDexAnnotationType: {
                        parsed =
                            (strcmp(descriptor, "Ljava/lang/Class;") == 0);
                        needRelease = true;
                        break;
                    }
                    default: {
                        parsed = false;
                        break;
                    }
                }
                break;
            }
            default: {
                parsed = false;
                break;
            }
        }

        if (parsed) {
            /*
             * All's well, so store the value. Note: This always
             * stores the full width of a JValue, even though most of
             * the time only the first word is needed.
             */
            sfield->value = value.value;
            if (needRelease) {
                dvmReleaseTrackedAlloc(value.value.l, self);
            }
        } else {
            /*
             * Something up above had a problem. TODO: See comment
             * above the switch about verfication.
             */
            LOGE("Bogus static initialization: value type %d in field type "
                    "%s for %s at index %d",
                value.type, descriptor, clazz->descriptor, i);
            dvmAbort();
        }
    }
}


/*
 * Determine whether "descriptor" yields the same class object in the
 * context of clazz1 and clazz2.
 *
 * The caller must hold gDvm.loadedClasses.
 *
 * Returns "true" if they match.
 */
static bool compareDescriptorClasses(const char* descriptor,
    const ClassObject* clazz1, const ClassObject* clazz2)
{
    ClassObject* result1;
    ClassObject* result2;

    /*
     * Do the first lookup by name.
     */
    result1 = dvmFindClassNoInit(descriptor, clazz1->classLoader);

    /*
     * We can skip a second lookup by name if the second class loader is
     * in the initiating loader list of the class object we retrieved.
     * (This means that somebody already did a lookup of this class through
     * the second loader, and it resolved to the same class.)  If it's not
     * there, we may simply not have had an opportunity to add it yet, so
     * we do the full lookup.
     *
     * The initiating loader test should catch the majority of cases
     * (in particular, the zillions of references to String/Object).
     *
     * Unfortunately we're still stuck grabbing a mutex to do the lookup.
     *
     * For this to work, the superclass/interface should be the first
     * argument, so that way if it's from the bootstrap loader this test
     * will work.  (The bootstrap loader, by definition, never shows up
     * as the initiating loader of a class defined by some other loader.)
     */
    dvmHashTableLock(gDvm.loadedClasses);
    bool isInit = dvmLoaderInInitiatingList(result1, clazz2->classLoader);
    dvmHashTableUnlock(gDvm.loadedClasses);

    if (isInit) {
        //printf("%s(obj=%p) / %s(cl=%p): initiating\n",
        //    result1->descriptor, result1,
        //    clazz2->descriptor, clazz2->classLoader);
        return true;
    } else {
        //printf("%s(obj=%p) / %s(cl=%p): RAW\n",
        //    result1->descriptor, result1,
        //    clazz2->descriptor, clazz2->classLoader);
        result2 = dvmFindClassNoInit(descriptor, clazz2->classLoader);
    }

    if (result1 == NULL || result2 == NULL) {
        dvmClearException(dvmThreadSelf());
        if (result1 == result2) {
            /*
             * Neither class loader could find this class.  Apparently it
             * doesn't exist.
             *
             * We can either throw some sort of exception now, or just
             * assume that it'll fail later when something actually tries
             * to use the class.  For strict handling we should throw now,
             * because a "tricky" class loader could start returning
             * something later, and a pair of "tricky" loaders could set
             * us up for confusion.
             *
             * I'm not sure if we're allowed to complain about nonexistent
             * classes in method signatures during class init, so for now
             * this will just return "true" and let nature take its course.
             */
            return true;
        } else {
            /* only one was found, so clearly they're not the same */
            return false;
        }
    }

    return result1 == result2;
}

/*
 * For every component in the method descriptor, resolve the class in the
 * context of the two classes and compare the results.
 *
 * For best results, the "superclass" class should be first.
 *
 * Returns "true" if the classes match, "false" otherwise.
 */
static bool checkMethodDescriptorClasses(const Method* meth,
    const ClassObject* clazz1, const ClassObject* clazz2)
{
    DexParameterIterator iterator;
    const char* descriptor;

    /* walk through the list of parameters */
    dexParameterIteratorInit(&iterator, &meth->prototype);
    while (true) {
        descriptor = dexParameterIteratorNextDescriptor(&iterator);

        if (descriptor == NULL)
            break;

        if (descriptor[0] == 'L' || descriptor[0] == '[') {
            /* non-primitive type */
            if (!compareDescriptorClasses(descriptor, clazz1, clazz2))
                return false;
        }
    }

    /* check the return type */
    descriptor = dexProtoGetReturnType(&meth->prototype);
    if (descriptor[0] == 'L' || descriptor[0] == '[') {
        if (!compareDescriptorClasses(descriptor, clazz1, clazz2))
            return false;
    }
    return true;
}

/*
 * Validate the descriptors in the superclass and interfaces.
 *
 * What we need to do is ensure that the classes named in the method
 * descriptors in our ancestors and ourselves resolve to the same class
 * objects.  The only time this matters is when the classes come from
 * different class loaders, and the resolver might come up with a
 * different answer for the same class name depending on context.
 *
 * We don't need to check to see if an interface's methods match with
 * its superinterface's methods, because you can't instantiate an
 * interface and do something inappropriate with it.  If interface I1
 * extends I2 and is implemented by C, and I1 and I2 are in separate
 * class loaders and have conflicting views of other classes, we will
 * catch the conflict when we process C.  Anything that implements I1 is
 * doomed to failure, but we don't need to catch that while processing I1.
 *
 * On failure, throws an exception and returns "false".
 */
static bool validateSuperDescriptors(const ClassObject* clazz)
{
    int i;

    if (dvmIsInterfaceClass(clazz))
        return true;

    /*
     * Start with the superclass-declared methods.
     */
    if (clazz->super != NULL &&
        clazz->classLoader != clazz->super->classLoader)
    {
        /*
         * Walk through every method declared in the superclass, and
         * compare resolved descriptor components.  We pull the Method
         * structs out of the vtable.  It doesn't matter whether we get
         * the struct from the parent or child, since we just need the
         * UTF-8 descriptor, which must match.
         *
         * We need to do this even for the stuff inherited from Object,
         * because it's possible that the new class loader has redefined
         * a basic class like String.
         */
        const Method* meth;

        //printf("Checking %s %p vs %s %p\n",
        //    clazz->descriptor, clazz->classLoader,
        //    clazz->super->descriptor, clazz->super->classLoader);
        for (i = clazz->super->vtableCount - 1; i >= 0; i--) {
            meth = clazz->vtable[i];
            if (!checkMethodDescriptorClasses(meth, clazz->super, clazz)) {
                LOGW("Method mismatch: %s in %s (cl=%p) and super %s (cl=%p)\n",
                    meth->name, clazz->descriptor, clazz->classLoader,
                    clazz->super->descriptor, clazz->super->classLoader);
                dvmThrowException("Ljava/lang/LinkageError;",
                    "Classes resolve differently in superclass");
                return false;
            }
        }
    }

    /*
     * Check all interfaces we implement.
     */
    for (i = 0; i < clazz->iftableCount; i++) {
        const InterfaceEntry* iftable = &clazz->iftable[i];

        if (clazz->classLoader != iftable->clazz->classLoader) {
            const ClassObject* iface = iftable->clazz;
            int j;

            for (j = 0; j < iface->virtualMethodCount; j++) {
                const Method* meth;
                int vtableIndex;

                vtableIndex = iftable->methodIndexArray[j];
                meth = clazz->vtable[vtableIndex];

                if (!checkMethodDescriptorClasses(meth, iface, clazz)) {
                    LOGW("Method mismatch: %s in %s (cl=%p) and "
                            "iface %s (cl=%p)\n",
                        meth->name, clazz->descriptor, clazz->classLoader,
                        iface->descriptor, iface->classLoader);
                    dvmThrowException("Ljava/lang/LinkageError;",
                        "Classes resolve differently in interface");
                    return false;
                }
            }
        }
    }

    return true;
}

/*
 * Returns true if the class is being initialized by us (which means that
 * calling dvmInitClass will return immediately after fiddling with locks).
 *
 * There isn't a race here, because either clazz->initThreadId won't match
 * us, or it will and it was set in the same thread.
 */
bool dvmIsClassInitializing(const ClassObject* clazz)
{
    return (clazz->status == CLASS_INITIALIZING &&
            clazz->initThreadId == dvmThreadSelf()->threadId);
}

/*
 * If a class has not been initialized, do so by executing the code in
 * <clinit>.  The sequence is described in the VM spec v2 2.17.5.
 *
 * It is possible for multiple threads to arrive here simultaneously, so
 * we need to lock the class while we check stuff.  We know that no
 * interpreted code has access to the class yet, so we can use the class's
 * monitor lock.
 *
 * We will often be called recursively, e.g. when the <clinit> code resolves
 * one of its fields, the field resolution will try to initialize the class.
 *
 * This can get very interesting if a class has a static field initialized
 * to a new instance of itself.  <clinit> will end up calling <init> on
 * the members it is initializing, which is fine unless it uses the contents
 * of static fields to initialize instance fields.  This will leave the
 * static-referenced objects in a partially initialized state.  This is
 * reasonably rare and can sometimes be cured with proper field ordering.
 *
 * On failure, returns "false" with an exception raised.
 *
 * -----
 *
 * It is possible to cause a deadlock by having a situation like this:
 *   class A { static { sleep(10000); new B(); } }
 *   class B { static { sleep(10000); new A(); } }
 *   new Thread() { public void run() { new A(); } }.start();
 *   new Thread() { public void run() { new B(); } }.start();
 * This appears to be expected under the spec.
 *
 * The interesting question is what to do if somebody calls Thread.interrupt()
 * on one of the deadlocked threads.  According to the VM spec, they're both
 * sitting in "wait".  Should the interrupt code quietly raise the
 * "interrupted" flag, or should the "wait" return immediately with an
 * exception raised?
 *
 * This gets a little murky.  The VM spec says we call "wait", and the
 * spec for Thread.interrupt says Object.wait is interruptible.  So it
 * seems that, if we get unlucky and interrupt class initialization, we
 * are expected to throw (which gets converted to ExceptionInInitializerError
 * since InterruptedException is checked).
 *
 * There are a couple of problems here.  First, all threads are expected to
 * present a consistent view of class initialization, so we can't have it
 * fail in one thread and succeed in another.  Second, once a class fails
 * to initialize, it must *always* fail.  This means that a stray interrupt()
 * call could render a class unusable for the lifetime of the VM.
 *
 * In most cases -- the deadlock example above being a counter-example --
 * the interrupting thread can't tell whether the target thread handled
 * the initialization itself or had to wait while another thread did the
 * work.  Refusing to interrupt class initialization is, in most cases,
 * not something that a program can reliably detect.
 *
 * On the assumption that interrupting class initialization is highly
 * undesirable in most circumstances, and that failing to do so does not
 * deviate from the spec in a meaningful way, we don't allow class init
 * to be interrupted by Thread.interrupt().
 */
bool dvmInitClass(ClassObject* clazz)
{
#if LOG_CLASS_LOADING
    bool initializedByUs = false;
#endif

    Thread* self = dvmThreadSelf();
    const Method* method;

    dvmLockObject(self, (Object*) clazz);
    assert(dvmIsClassLinked(clazz) || clazz->status == CLASS_ERROR);

    /*
     * If the class hasn't been verified yet, do so now.
     */
    if (clazz->status < CLASS_VERIFIED) {
        /*
         * If we're in an "erroneous" state, throw an exception and bail.
         */
        if (clazz->status == CLASS_ERROR) {
            throwEarlierClassFailure(clazz);
            goto bail_unlock;
        }

        assert(clazz->status == CLASS_RESOLVED);
        assert(!IS_CLASS_FLAG_SET(clazz, CLASS_ISPREVERIFIED));

        if (gDvm.classVerifyMode == VERIFY_MODE_NONE ||
            (gDvm.classVerifyMode == VERIFY_MODE_REMOTE &&
             clazz->classLoader == NULL))
        {
            LOGV("+++ not verifying class %s (cl=%p)\n",
                clazz->descriptor, clazz->classLoader);
            goto noverify;
        }

        if (!gDvm.optimizing)
            LOGV("+++ late verify on %s\n", clazz->descriptor);

        /*
         * We're not supposed to optimize an unverified class, but during
         * development this mode was useful.  We can't verify an optimized
         * class because the optimization process discards information.
         */
        if (IS_CLASS_FLAG_SET(clazz, CLASS_ISOPTIMIZED)) {
            LOGW("Class '%s' was optimized without verification; "
                 "not verifying now\n",
                clazz->descriptor);
            LOGW("  ('rm /data/dalvik-cache/*' and restart to fix this)");
            goto verify_failed;
        }

        clazz->status = CLASS_VERIFYING;
        if (!dvmVerifyClass(clazz, VERIFY_DEFAULT)) {
verify_failed:
            dvmThrowExceptionWithClassMessage("Ljava/lang/VerifyError;",
                clazz->descriptor);
            clazz->verifyErrorClass = dvmGetException(self)->clazz;
            clazz->status = CLASS_ERROR;
            goto bail_unlock;
        }

        clazz->status = CLASS_VERIFIED;
    }
noverify:

    if (clazz->status == CLASS_INITIALIZED)
        goto bail_unlock;

    while (clazz->status == CLASS_INITIALIZING) {
        /* we caught somebody else in the act; was it us? */
        if (clazz->initThreadId == self->threadId) {
            //LOGV("HEY: found a recursive <clinit>\n");
            goto bail_unlock;
        }

        if (dvmCheckException(self)) {
            LOGW("GLITCH: exception pending at start of class init\n");
            dvmAbort();
        }

        /*
         * Wait for the other thread to finish initialization.  We pass
         * "false" for the "interruptShouldThrow" arg so it doesn't throw
         * an exception on interrupt.
         */
        dvmObjectWait(self, (Object*) clazz, 0, 0, false);

        /*
         * When we wake up, repeat the test for init-in-progress.  If there's
         * an exception pending (only possible if "interruptShouldThrow"
         * was set), bail out.
         */
        if (dvmCheckException(self)) {
            LOGI("Class init of '%s' failing with wait() exception\n",
                clazz->descriptor);
            /*
             * TODO: this is bogus, because it means the two threads have a
             * different idea of the class status.  We need to flag the
             * class as bad and ensure that the initializer thread respects
             * our notice.  If we get lucky and wake up after the class has
             * finished initialization but before being woken, we have to
             * swallow the exception, perhaps raising thread->interrupted
             * to preserve semantics.
             *
             * Since we're not currently allowing interrupts, this should
             * never happen and we don't need to fix this.
             */
            assert(false);
            throwClinitError();
            clazz->status = CLASS_ERROR;
            goto bail_unlock;
        }
        if (clazz->status == CLASS_INITIALIZING) {
            LOGI("Waiting again for class init\n");
            continue;
        }
        assert(clazz->status == CLASS_INITIALIZED ||
               clazz->status == CLASS_ERROR);
        if (clazz->status == CLASS_ERROR) {
            /*
             * The caller wants an exception, but it was thrown in a
             * different thread.  Synthesize one here.
             */
            dvmThrowException("Ljava/lang/UnsatisfiedLinkError;",
                "(<clinit> failed, see exception in other thread)");
        }
        goto bail_unlock;
    }

    /* see if we failed previously */
    if (clazz->status == CLASS_ERROR) {
        // might be wise to unlock before throwing; depends on which class
        // it is that we have locked
        dvmUnlockObject(self, (Object*) clazz);
        throwEarlierClassFailure(clazz);
        return false;
    }

    /*
     * We're ready to go, and have exclusive access to the class.
     *
     * Before we start initialization, we need to do one extra bit of
     * validation: make sure that the methods declared here match up
     * with our superclass and interfaces.  We know that the UTF-8
     * descriptors match, but classes from different class loaders can
     * have the same name.
     *
     * We do this now, rather than at load/link time, for the same reason
     * that we defer verification.
     *
     * It's unfortunate that we need to do this at all, but we risk
     * mixing reference types with identical names (see Dalvik test 068).
     */
    if (!validateSuperDescriptors(clazz)) {
        assert(dvmCheckException(self));
        clazz->status = CLASS_ERROR;
        goto bail_unlock;
    }

    /*
     * Let's initialize this thing.
     *
     * We unlock the object so that other threads can politely sleep on
     * our mutex with Object.wait(), instead of hanging or spinning trying
     * to grab our mutex.
     */
    assert(clazz->status < CLASS_INITIALIZING);

#if LOG_CLASS_LOADING
    // We started initializing.
    logClassLoad('+', clazz);
    initializedByUs = true;
#endif

    clazz->status = CLASS_INITIALIZING;
    clazz->initThreadId = self->threadId;
    dvmUnlockObject(self, (Object*) clazz);

    /* init our superclass */
    if (clazz->super != NULL && clazz->super->status != CLASS_INITIALIZED) {
        assert(!dvmIsInterfaceClass(clazz));
        if (!dvmInitClass(clazz->super)) {
            assert(dvmCheckException(self));
            clazz->status = CLASS_ERROR;
            /* wake up anybody who started waiting while we were unlocked */
            dvmLockObject(self, (Object*) clazz);
            goto bail_notify;
        }
    }

    /* Initialize any static fields whose values are
     * stored in the Dex file.  This should include all of the
     * simple "final static" fields, which are required to
     * be initialized first. (vmspec 2 sec 2.17.5 item 8)
     * More-complicated final static fields should be set
     * at the beginning of <clinit>;  all we can do is trust
     * that the compiler did the right thing.
     */
    initSFields(clazz);

    /* Execute any static initialization code.
     */
    method = dvmFindDirectMethodByDescriptor(clazz, "<clinit>", "()V");
    if (method == NULL) {
        LOGVV("No <clinit> found for %s\n", clazz->descriptor);
    } else {
        LOGVV("Invoking %s.<clinit>\n", clazz->descriptor);
        JValue unused;
        dvmCallMethod(self, method, NULL, &unused);
    }

    if (dvmCheckException(self)) {
        /*
         * We've had an exception thrown during static initialization.  We
         * need to throw an ExceptionInInitializerError, but we want to
         * tuck the original exception into the "cause" field.
         */
        LOGW("Exception %s thrown during %s.<clinit>\n",
            (dvmGetException(self)->clazz)->descriptor, clazz->descriptor);
        throwClinitError();
        //LOGW("+++ replaced\n");

        dvmLockObject(self, (Object*) clazz);
        clazz->status = CLASS_ERROR;
    } else {
        /* success! */
        dvmLockObject(self, (Object*) clazz);
        clazz->status = CLASS_INITIALIZED;
        LOGVV("Initialized class: %s\n", clazz->descriptor);
    }

bail_notify:
    /*
     * Notify anybody waiting on the object.
     */
    dvmObjectNotifyAll(self, (Object*) clazz);

bail_unlock:

#if LOG_CLASS_LOADING
    if (initializedByUs) {
        // We finished initializing.
        logClassLoad('-', clazz);
    }
#endif

    dvmUnlockObject(self, (Object*) clazz);

    return (clazz->status != CLASS_ERROR);
}

/*
 * Replace method->nativeFunc and method->insns with new values.  This is
 * performed on resolution of a native method.
 */
void dvmSetNativeFunc(const Method* method, DalvikBridgeFunc func,
    const u2* insns)
{
    ClassObject* clazz = method->clazz;

    /* just open up both; easier that way */
    dvmLinearReadWrite(clazz->classLoader, clazz->virtualMethods);
    dvmLinearReadWrite(clazz->classLoader, clazz->directMethods);

    ((Method*)method)->nativeFunc = func;
    ((Method*)method)->insns = insns;

    dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);
    dvmLinearReadOnly(clazz->classLoader, clazz->directMethods);
}

/*
 * Add a RegisterMap to a Method.  This is done when we verify the class
 * and compute the register maps at class initialization time, which means
 * that "pMap" is on the heap and should be freed when the Method is
 * discarded.
 */
void dvmSetRegisterMap(Method* method, const RegisterMap* pMap)
{
    ClassObject* clazz = method->clazz;

    if (method->registerMap != NULL) {
        LOGW("WARNING: registerMap already set for %s.%s\n",
            method->clazz->descriptor, method->name);
        /* keep going */
    }

    /* might be virtual or direct */
    dvmLinearReadWrite(clazz->classLoader, clazz->virtualMethods);
    dvmLinearReadWrite(clazz->classLoader, clazz->directMethods);

    method->registerMap = pMap;

    dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);
    dvmLinearReadOnly(clazz->classLoader, clazz->directMethods);
}

/*
 * dvmHashForeach callback.  A nonzero return value causes foreach to
 * bail out.
 */
static int findClassCallback(void* vclazz, void* arg)
{
    ClassObject* clazz = vclazz;
    const char* descriptor = (const char*) arg;

    if (strcmp(clazz->descriptor, descriptor) == 0)
        return (int) clazz;
    return 0;
}

/*
 * Find a loaded class by descriptor. Returns the first one found.
 * Because there can be more than one if class loaders are involved,
 * this is not an especially good API. (Currently only used by the
 * debugger and "checking" JNI.)
 *
 * "descriptor" should have the form "Ljava/lang/Class;" or
 * "[Ljava/lang/Class;", i.e. a descriptor and not an internal-form
 * class name.
 */
ClassObject* dvmFindLoadedClass(const char* descriptor)
{
    int result;

    dvmHashTableLock(gDvm.loadedClasses);
    result = dvmHashForeach(gDvm.loadedClasses, findClassCallback,
            (void*) descriptor);
    dvmHashTableUnlock(gDvm.loadedClasses);

    return (ClassObject*) result;
}

/*
 * Retrieve the system (a/k/a application) class loader.
 */
Object* dvmGetSystemClassLoader(void)
{
    ClassObject* clazz;
    Method* getSysMeth;
    Object* loader;

    clazz = dvmFindSystemClass("Ljava/lang/ClassLoader;");
    if (clazz == NULL)
        return NULL;

    getSysMeth = dvmFindDirectMethodByDescriptor(clazz, "getSystemClassLoader",
        "()Ljava/lang/ClassLoader;");
    if (getSysMeth == NULL)
        return NULL;

    JValue result;
    dvmCallMethod(dvmThreadSelf(), getSysMeth, NULL, &result);
    loader = (Object*)result.l;
    return loader;
}


/*
 * This is a dvmHashForeach callback.
 */
static int dumpClass(void* vclazz, void* varg)
{
    const ClassObject* clazz = (const ClassObject*) vclazz;
    const ClassObject* super;
    int flags = (int) varg;
    char* desc;
    int i;

    if (clazz == NULL) {
        LOGI("dumpClass: ignoring request to dump null class\n");
        return 0;
    }

    if ((flags & kDumpClassFullDetail) == 0) {
        bool showInit = (flags & kDumpClassInitialized) != 0;
        bool showLoader = (flags & kDumpClassClassLoader) != 0;
        const char* initStr;

        initStr = dvmIsClassInitialized(clazz) ? "true" : "false";

        if (showInit && showLoader)
            LOGI("%s %p %s\n", clazz->descriptor, clazz->classLoader, initStr);
        else if (showInit)
            LOGI("%s %s\n", clazz->descriptor, initStr);
        else if (showLoader)
            LOGI("%s %p\n", clazz->descriptor, clazz->classLoader);
        else
            LOGI("%s\n", clazz->descriptor);

        return 0;
    }

    /* clazz->super briefly holds the superclass index during class prep */
    if ((u4)clazz->super > 0x10000 && (u4) clazz->super != (u4)-1)
        super = clazz->super;
    else
        super = NULL;

    LOGI("----- %s '%s' cl=%p ser=0x%08x -----\n",
        dvmIsInterfaceClass(clazz) ? "interface" : "class",
        clazz->descriptor, clazz->classLoader, clazz->serialNumber);
    LOGI("  objectSize=%d (%d from super)\n", (int) clazz->objectSize,
        super != NULL ? (int) super->objectSize : -1);
    LOGI("  access=0x%04x.%04x\n", clazz->accessFlags >> 16,
        clazz->accessFlags & JAVA_FLAGS_MASK);
    if (super != NULL)
        LOGI("  super='%s' (cl=%p)\n", super->descriptor, super->classLoader);
    if (dvmIsArrayClass(clazz)) {
        LOGI("  dimensions=%d elementClass=%s\n",
            clazz->arrayDim, clazz->elementClass->descriptor);
    }
    if (clazz->iftableCount > 0) {
        LOGI("  interfaces (%d):\n", clazz->iftableCount);
        for (i = 0; i < clazz->iftableCount; i++) {
            InterfaceEntry* ent = &clazz->iftable[i];
            int j;

            LOGI("    %2d: %s (cl=%p)\n",
                i, ent->clazz->descriptor, ent->clazz->classLoader);

            /* enable when needed */
            if (false && ent->methodIndexArray != NULL) {
                for (j = 0; j < ent->clazz->virtualMethodCount; j++)
                    LOGI("      %2d: %d %s %s\n",
                        j, ent->methodIndexArray[j],
                        ent->clazz->virtualMethods[j].name,
                        clazz->vtable[ent->methodIndexArray[j]]->name);
            }
        }
    }
    if (!dvmIsInterfaceClass(clazz)) {
        LOGI("  vtable (%d entries, %d in super):\n", clazz->vtableCount,
            super != NULL ? super->vtableCount : 0);
        for (i = 0; i < clazz->vtableCount; i++) {
            desc = dexProtoCopyMethodDescriptor(&clazz->vtable[i]->prototype);
            LOGI("    %s%2d: %p %20s %s\n",
                (i != clazz->vtable[i]->methodIndex) ? "*** " : "",
                (u4) clazz->vtable[i]->methodIndex, clazz->vtable[i],
                clazz->vtable[i]->name, desc);
            free(desc);
        }
        LOGI("  direct methods (%d entries):\n", clazz->directMethodCount);
        for (i = 0; i < clazz->directMethodCount; i++) {
            desc = dexProtoCopyMethodDescriptor(
                    &clazz->directMethods[i].prototype);
            LOGI("    %2d: %20s %s\n", i, clazz->directMethods[i].name,
                desc);
            free(desc);
        }
    } else {
        LOGI("  interface methods (%d):\n", clazz->virtualMethodCount);
        for (i = 0; i < clazz->virtualMethodCount; i++) {
            desc = dexProtoCopyMethodDescriptor(
                    &clazz->virtualMethods[i].prototype);
            LOGI("    %2d: %2d %20s %s\n", i,
                (u4) clazz->virtualMethods[i].methodIndex,
                clazz->virtualMethods[i].name,
                desc);
            free(desc);
        }
    }
    if (clazz->sfieldCount > 0) {
        LOGI("  static fields (%d entries):\n", clazz->sfieldCount);
        for (i = 0; i < clazz->sfieldCount; i++) {
            LOGI("    %2d: %20s %s\n", i, clazz->sfields[i].field.name,
                clazz->sfields[i].field.signature);
        }
    }
    if (clazz->ifieldCount > 0) {
        LOGI("  instance fields (%d entries):\n", clazz->ifieldCount);
        for (i = 0; i < clazz->ifieldCount; i++) {
            LOGI("    %2d: %20s %s\n", i, clazz->ifields[i].field.name,
                clazz->ifields[i].field.signature);
        }
    }
    return 0;
}

/*
 * Dump the contents of a single class.
 *
 * Pass kDumpClassFullDetail into "flags" to get lots of detail.
 */
void dvmDumpClass(const ClassObject* clazz, int flags)
{
    dumpClass((void*) clazz, (void*) flags);
}

/*
 * Dump the contents of all classes.
 */
void dvmDumpAllClasses(int flags)
{
    dvmHashTableLock(gDvm.loadedClasses);
    dvmHashForeach(gDvm.loadedClasses, dumpClass, (void*) flags);
    dvmHashTableUnlock(gDvm.loadedClasses);
}

/*
 * Get the number of loaded classes
 */
int dvmGetNumLoadedClasses()
{
    int count;
    dvmHashTableLock(gDvm.loadedClasses);
    count = dvmHashTableNumEntries(gDvm.loadedClasses);
    dvmHashTableUnlock(gDvm.loadedClasses);
    return count;
}

/*
 * Write some statistics to the log file.
 */
void dvmDumpLoaderStats(const char* msg)
{
    LOGV("VM stats (%s): cls=%d/%d meth=%d ifld=%d sfld=%d linear=%d\n",
        msg, gDvm.numLoadedClasses, dvmHashTableNumEntries(gDvm.loadedClasses),
        gDvm.numDeclaredMethods, gDvm.numDeclaredInstFields,
        gDvm.numDeclaredStaticFields, gDvm.pBootLoaderAlloc->curOffset);
#ifdef COUNT_PRECISE_METHODS
    LOGI("GC precise methods: %d\n",
        dvmPointerSetGetCount(gDvm.preciseMethods));
#endif
}

#ifdef PROFILE_FIELD_ACCESS
/*
 * Dump the field access counts for all fields in this method.
 */
static int dumpAccessCounts(void* vclazz, void* varg)
{
    const ClassObject* clazz = (const ClassObject*) vclazz;
    int i;

    for (i = 0; i < clazz->ifieldCount; i++) {
        Field* field = &clazz->ifields[i].field;

        if (field->gets != 0)
            printf("GI %d %s.%s\n", field->gets,
                field->clazz->descriptor, field->name);
        if (field->puts != 0)
            printf("PI %d %s.%s\n", field->puts,
                field->clazz->descriptor, field->name);
    }
    for (i = 0; i < clazz->sfieldCount; i++) {
        Field* field = &clazz->sfields[i].field;

        if (field->gets != 0)
            printf("GS %d %s.%s\n", field->gets,
                field->clazz->descriptor, field->name);
        if (field->puts != 0)
            printf("PS %d %s.%s\n", field->puts,
                field->clazz->descriptor, field->name);
    }

    return 0;
}

/*
 * Dump the field access counts for all loaded classes.
 */
void dvmDumpFieldAccessCounts(void)
{
    dvmHashTableLock(gDvm.loadedClasses);
    dvmHashForeach(gDvm.loadedClasses, dumpAccessCounts, NULL);
    dvmHashTableUnlock(gDvm.loadedClasses);
}
#endif


/*
 * Mark all classes associated with the built-in loader.
 */
static int markClassObject(void *clazz, void *arg)
{
    UNUSED_PARAMETER(arg);

    dvmMarkObjectNonNull((Object *)clazz);
    return 0;
}

/*
 * The garbage collector calls this to mark the class objects for all
 * loaded classes.
 */
void dvmGcScanRootClassLoader()
{
    /* dvmClassStartup() may not have been called before the first GC.
     */
    if (gDvm.loadedClasses != NULL) {
        dvmHashTableLock(gDvm.loadedClasses);
        dvmHashForeach(gDvm.loadedClasses, markClassObject, NULL);
        dvmHashTableUnlock(gDvm.loadedClasses);
    }
}


/*
 * ===========================================================================
 *      Method Prototypes and Descriptors
 * ===========================================================================
 */

/*
 * Compare the two method names and prototypes, a la strcmp(). The
 * name is considered the "major" order and the prototype the "minor"
 * order. The prototypes are compared as if by dvmCompareMethodProtos().
 */
int dvmCompareMethodNamesAndProtos(const Method* method1,
        const Method* method2)
{
    int result = strcmp(method1->name, method2->name);

    if (result != 0) {
        return result;
    }

    return dvmCompareMethodProtos(method1, method2);
}

/*
 * Compare the two method names and prototypes, a la strcmp(), ignoring
 * the return value. The name is considered the "major" order and the
 * prototype the "minor" order. The prototypes are compared as if by
 * dvmCompareMethodArgProtos().
 */
int dvmCompareMethodNamesAndParameterProtos(const Method* method1,
        const Method* method2)
{
    int result = strcmp(method1->name, method2->name);

    if (result != 0) {
        return result;
    }

    return dvmCompareMethodParameterProtos(method1, method2);
}

/*
 * Compare a (name, prototype) pair with the (name, prototype) of
 * a method, a la strcmp(). The name is considered the "major" order and
 * the prototype the "minor" order. The descriptor and prototype are
 * compared as if by dvmCompareDescriptorAndMethodProto().
 */
int dvmCompareNameProtoAndMethod(const char* name,
    const DexProto* proto, const Method* method)
{
    int result = strcmp(name, method->name);

    if (result != 0) {
        return result;
    }

    return dexProtoCompare(proto, &method->prototype);
}

/*
 * Compare a (name, method descriptor) pair with the (name, prototype) of
 * a method, a la strcmp(). The name is considered the "major" order and
 * the prototype the "minor" order. The descriptor and prototype are
 * compared as if by dvmCompareDescriptorAndMethodProto().
 */
int dvmCompareNameDescriptorAndMethod(const char* name,
    const char* descriptor, const Method* method)
{
    int result = strcmp(name, method->name);

    if (result != 0) {
        return result;
    }

    return dvmCompareDescriptorAndMethodProto(descriptor, method);
}