blob: c2f666a587e9c25270b57e34d06938a71da1b668 [file] [log] [blame]
/*
* 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 "analysis/Optimize.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
#define CLASS_SFIELD_SLOTS 1
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 bool precacheReferenceOffsets(ClassObject* clazz);
static void computeRefOffsets(ClassObject* clazz);
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 test = 1;
switch (test) {
case 0:
fiddle = (char*)dvmLinearAlloc(NULL, 3200-28);
dvmLinearReadOnly(NULL, (char*)fiddle);
break;
case 1:
fiddle = (char*)dvmLinearAlloc(NULL, 3200-24);
dvmLinearReadOnly(NULL, (char*)fiddle);
break;
case 2:
fiddle = (char*)dvmLinearAlloc(NULL, 3200-20);
dvmLinearReadOnly(NULL, (char*)fiddle);
break;
case 3:
fiddle = (char*)dvmLinearAlloc(NULL, 3200-16);
dvmLinearReadOnly(NULL, (char*)fiddle);
break;
case 4:
fiddle = (char*)dvmLinearAlloc(NULL, 3200-12);
dvmLinearReadOnly(NULL, (char*)fiddle);
break;
}
fiddle = (char*)dvmLinearAlloc(NULL, 896);
dvmLinearReadOnly(NULL, (char*)fiddle);
fiddle = (char*)dvmLinearAlloc(NULL, 20); // watch addr of this alloc
dvmLinearReadOnly(NULL, (char*)fiddle);
fiddle = (char*)dvmLinearAlloc(NULL, 1);
fiddle[0] = 'q';
dvmLinearReadOnly(NULL, fiddle);
fiddle = (char*)dvmLinearAlloc(NULL, 4096);
fiddle[0] = 'x';
fiddle[4095] = 'y';
dvmLinearReadOnly(NULL, fiddle);
dvmLinearFree(NULL, fiddle);
fiddle = (char*)dvmLinearAlloc(NULL, 0);
dvmLinearReadOnly(NULL, fiddle);
fiddle = (char*)dvmLinearRealloc(NULL, fiddle, 12);
fiddle[11] = 'z';
dvmLinearReadOnly(NULL, (char*)fiddle);
fiddle = (char*)dvmLinearRealloc(NULL, fiddle, 5);
dvmLinearReadOnly(NULL, fiddle);
fiddle = (char*)dvmLinearAlloc(NULL, 17001);
fiddle[0] = 'x';
fiddle[17000] = 'y';
dvmLinearReadOnly(NULL, (char*)fiddle);
char* str = (char*)dvmLinearStrdup(NULL, "This is a test!");
LOGI("GOT: '%s'\n", str);
/* try to check the bounds; allocator may round allocation size up */
fiddle = (char*)dvmLinearAlloc(NULL, 12);
LOGI("Should be 1: %d\n", dvmLinearAllocContains(fiddle, 12));
LOGI("Should be 0: %d\n", dvmLinearAllocContains(fiddle, 13));
LOGI("Should be 0: %d\n", dvmLinearAllocContains(fiddle - 128*1024, 1));
dvmLinearAllocDump(NULL);
dvmLinearFree(NULL, (char*)str);
}
static size_t classObjectSize(size_t sfieldCount)
{
size_t size;
size = offsetof(ClassObject, sfields);
size += sizeof(StaticField) * sfieldCount;
return size;
}
size_t dvmClassObjectSize(const ClassObject *clazz)
{
assert(clazz != NULL);
return classObjectSize(clazz->sfieldCount);
}
/* (documented in header) */
ClassObject* dvmFindPrimitiveClass(char type)
{
PrimitiveType primitiveType = dexGetPrimitiveTypeFromDescriptorChar(type);
switch (primitiveType) {
case PRIM_VOID: return gDvm.typeVoid;
case PRIM_BOOLEAN: return gDvm.typeBoolean;
case PRIM_BYTE: return gDvm.typeByte;
case PRIM_SHORT: return gDvm.typeShort;
case PRIM_CHAR: return gDvm.typeChar;
case PRIM_INT: return gDvm.typeInt;
case PRIM_LONG: return gDvm.typeLong;
case PRIM_FLOAT: return gDvm.typeFloat;
case PRIM_DOUBLE: return gDvm.typeDouble;
default: {
LOGW("Unknown primitive type '%c'\n", type);
return NULL;
}
}
}
/*
* Synthesize a primitive class.
*
* Just creates the class and returns it (does not add it to the class list).
*/
static bool createPrimitiveType(PrimitiveType primitiveType, ClassObject** pClass)
{
/*
* Fill out a few fields in the ClassObject.
*
* Note that primitive classes do not sub-class the class Object.
* This matters for "instanceof" checks. Also, we assume that the
* primitive class does not override finalize().
*/
const char* descriptor = dexGetPrimitiveTypeDescriptor(primitiveType);
assert(descriptor != NULL);
ClassObject* newClass = (ClassObject*) dvmMalloc(sizeof(*newClass), ALLOC_DEFAULT);
if (newClass == NULL) {
return false;
}
DVM_OBJECT_INIT(&newClass->obj, gDvm.classJavaLangClass);
dvmSetClassSerialNumber(newClass);
newClass->accessFlags = ACC_PUBLIC | ACC_FINAL | ACC_ABSTRACT;
newClass->primitiveType = primitiveType;
newClass->descriptorAlloc = NULL;
newClass->descriptor = descriptor;
newClass->super = NULL;
newClass->status = CLASS_INITIALIZED;
/* don't need to set newClass->objectSize */
LOGVV("Created class for primitive type '%s'\n", newClass->descriptor);
*pClass = newClass;
dvmReleaseTrackedAlloc((Object*) newClass, NULL);
return true;
}
/*
* Create the classes representing primitive types.
*/
static bool createPrimitiveTypes(void) {
bool ok = true;
ok &= createPrimitiveType(PRIM_VOID, &gDvm.typeVoid);
ok &= createPrimitiveType(PRIM_BOOLEAN, &gDvm.typeBoolean);
ok &= createPrimitiveType(PRIM_BYTE, &gDvm.typeByte);
ok &= createPrimitiveType(PRIM_SHORT, &gDvm.typeShort);
ok &= createPrimitiveType(PRIM_CHAR, &gDvm.typeChar);
ok &= createPrimitiveType(PRIM_INT, &gDvm.typeInt);
ok &= createPrimitiveType(PRIM_LONG, &gDvm.typeLong);
ok &= createPrimitiveType(PRIM_FLOAT, &gDvm.typeFloat);
ok &= createPrimitiveType(PRIM_DOUBLE, &gDvm.typeDouble);
return ok;
}
/*
* Initialize the bootstrap class loader.
*
* Call this after the bootclasspath string has been finalized.
*/
bool dvmClassStartup(void)
{
/* 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 = (InitiatingLoaderList*)
calloc(ZYGOTE_CLASS_CUTOFF, sizeof(InitiatingLoaderList));
/*
* Initialize the class Class. This has to be done specially, particularly
* because it is an instance of itself.
*/
gDvm.classJavaLangClass = (ClassObject*) dvmMalloc(
classObjectSize(CLASS_SFIELD_SLOTS), ALLOC_DEFAULT);
DVM_OBJECT_INIT(&gDvm.classJavaLangClass->obj, gDvm.classJavaLangClass);
gDvm.classJavaLangClass->descriptor = "Ljava/lang/Class;";
/*
* Initialize the classes representing primitive types. These are
* instances of the class Class, but other than that they're fairly
* different from regular classes.
*/
if (!createPrimitiveTypes()) {
return false;
}
/*
* 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)
{
/* discard all system-loaded classes */
dvmHashTableFree(gDvm.loadedClasses);
gDvm.loadedClasses = NULL;
/* discard primitive classes created for arrays */
dvmFreeClassInnards(gDvm.typeVoid);
dvmFreeClassInnards(gDvm.typeBoolean);
dvmFreeClassInnards(gDvm.typeByte);
dvmFreeClassInnards(gDvm.typeShort);
dvmFreeClassInnards(gDvm.typeChar);
dvmFreeClassInnards(gDvm.typeInt);
dvmFreeClassInnards(gDvm.typeLong);
dvmFreeClassInnards(gDvm.typeFloat);
dvmFreeClassInnards(gDvm.typeDouble);
/* 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) {
LOGD("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;
}
LOGD("Unable to process classpath element '%s'\n", cpe->fileName);
return false;
}
/*
* Convert a colon-separated list of directories, Zip files, and DEX files
* into an array of ClassPathEntry structs.
*
* 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.
*
* If entries are added or removed from the bootstrap class path, the
* dependencies in the DEX files will break, and everything except the
* very first entry will need to be regenerated.
*/
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 {
if (isBootstrap &&
dvmPathToAbsolutePortion(cp) == NULL) {
LOGE("Non-absolute bootclasspath entry '%s'\n", cp);
free(cpe);
cpe = NULL;
goto bail;
}
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)) {
/* drop from list and continue on */
free(tmp.fileName);
} else {
/* copy over, pointers and all */
cpe[idx] = tmp;
idx++;
}
}
cp += strlen(cp) +1;
}
assert(idx <= count);
if (idx == 0 && !gDvm.optimizing) {
LOGE("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);
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 && !unprepOkay && !dvmIsClassLinked((ClassObject*)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)
{
assert(clazz->serialNumber == 0);
clazz->serialNumber = android_atomic_inc(&gDvm.classSerialNumber);
}
/*
* 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;
/* convert "Landroid/debug/Stuff;" to "android.debug.Stuff" */
dotName = dvmDescriptorToDot(descriptor);
if (dotName == NULL) {
dvmThrowOutOfMemoryError(NULL);
goto bail;
}
nameObj = dvmCreateStringFromCstr(dotName);
if (nameObj == NULL) {
assert(dvmCheckException(self));
goto bail;
}
dvmMethodTraceClassPrepBegin();
/*
* 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);
const Method* loadClass =
loader->clazz->vtable[gDvm.voffJavaLangClassLoader_loadClass];
JValue result;
dvmCallMethod(self, loadClass, loader, &result, nameObj);
clazz = (ClassObject*) result.l;
dvmMethodTraceClassPrepEnd();
Object* 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);
dvmThrowChainedNoClassDefFoundError(descriptor, excep);
dvmReleaseTrackedAlloc(excep, self);
clazz = NULL;
goto bail;
} else if (clazz == NULL) {
LOGW("ClassLoader returned NULL w/o exception pending\n");
dvmThrowNullPointerException("ClassLoader returned null");
goto bail;
}
/* not adding clazz to tracked-alloc list, because it's a ClassObject */
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;
bool profilerNotified = false;
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 attempted while exception %s pending\n",
descriptor, dvmGetException(self)->clazz->descriptor);
dvmDumpAllThreads(false);
dvmAbort();
}
clazz = dvmLookupClass(descriptor, loader, true);
if (clazz == NULL) {
const DexClassDef* pClassDef;
dvmMethodTraceClassPrepBegin();
profilerNotified = true;
#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) {
if (gDvm.noClassDefFoundErrorObj != NULL) {
/* usual case -- use prefabricated object */
dvmSetException(self, gDvm.noClassDefFoundErrorObj);
} else {
/* dexopt case -- can't guarantee prefab (core.jar) */
dvmThrowNoClassDefFoundError(descriptor);
}
goto bail;
}
/* found a match, try to load it */
clazz = loadClassFromDex(pDvmDex, pClassDef, loader);
if (dvmCheckException(self)) {
/* class was found but had issues */
if (clazz != NULL) {
dvmFreeClassInnards(clazz);
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. (Note all accesses to initThreadId here are
* guarded by the class object's lock.)
*/
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.
*/
dvmFreeClassInnards(clazz);
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)) {
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);
dvmThrowClassCircularityError(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);
assert(clazz == gDvm.classJavaLangObject || clazz->super != NULL);
if (!dvmIsInterfaceClass(clazz)) {
//LOGI("class=%s vtableCount=%d, virtualMeth=%d\n",
// clazz->descriptor, clazz->vtableCount,
// clazz->virtualMethodCount);
assert(clazz->vtableCount >= clazz->virtualMethodCount);
}
bail:
if (profilerNotified)
dvmMethodTraceClassPrepEnd();
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().
*/
/* TODO: Can there be fewer special checks in the usual path? */
assert(descriptor != NULL);
if (classLoader == NULL &&
strcmp(descriptor, "Ljava/lang/Class;") == 0) {
assert(gDvm.classJavaLangClass != NULL);
newClass = gDvm.classJavaLangClass;
} else {
size_t size = classObjectSize(pHeader->staticFieldsSize);
newClass = (ClassObject*) dvmMalloc(size, ALLOC_DEFAULT);
}
if (newClass == NULL)
return NULL;
DVM_OBJECT_INIT(&newClass->obj, gDvm.classJavaLangClass);
dvmSetClassSerialNumber(newClass);
newClass->descriptor = descriptor;
assert(newClass->descriptorAlloc == NULL);
newClass->accessFlags = pClassDef->accessFlags;
dvmSetFieldObject((Object *)newClass,
offsetof(ClassObject, classLoader),
(Object *)classLoader);
newClass->pDvmDex = pDvmDex;
newClass->primitiveType = PRIM_NOT;
newClass->status = CLASS_IDX;
/*
* 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*)); /* 32-bit check */
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 */
/*
* Over-allocate the class object and append static field info
* onto the end. It's fixed-size and known at alloc time. This
* seems to increase zygote sharing. Heap compaction will have to
* be careful if it ever tries to move ClassObject instances,
* because we pass Field pointers around internally. But at least
* now these Field pointers are in the object heap.
*/
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;
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. We do this in two batches, direct then
* virtual.
*
* If register maps have already been generated for this class, and
* precise GC is enabled, we pull out pointers to them. We know that
* they were streamed to the DEX file in the same order in which the
* methods appear.
*
* If the class wasn't pre-verified, the maps will be generated when
* the class is verified during class initialization.
*/
u4 classDefIdx = dexGetIndexForClassDef(pDexFile, pClassDef);
const void* classMapData;
u4 numMethods;
if (gDvm.preciseGc) {
classMapData =
dvmRegisterMapGetClassData(pDexFile, classDefIdx, &numMethods);
/* sanity check */
if (classMapData != NULL &&
pHeader->directMethodsSize + pHeader->virtualMethodsSize != numMethods)
{
LOGE("ERROR: in %s, direct=%d virtual=%d, maps have %d\n",
newClass->descriptor, pHeader->directMethodsSize,
pHeader->virtualMethodsSize, numMethods);
assert(false);
classMapData = NULL; /* abandon */
}
} else {
classMapData = NULL;
}
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]);
if (classMapData != NULL) {
const RegisterMap* pMap = dvmRegisterMapGetNext(&classMapData);
if (dvmRegisterMapGetFormat(pMap) != kRegMapFormatNone) {
newClass->directMethods[i].registerMap = pMap;
/* TODO: add rigorous checks */
assert((newClass->directMethods[i].registersSize+7) / 8 ==
newClass->directMethods[i].registerMap->regWidth);
}
}
}
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]);
if (classMapData != NULL) {
const RegisterMap* pMap = dvmRegisterMapGetNext(&classMapData);
if (dvmRegisterMapGetFormat(pMap) != kRegMapFormatNone) {
newClass->virtualMethods[i].registerMap = pMap;
/* TODO: add rigorous checks */
assert((newClass->virtualMethods[i].registersSize+7) / 8 ==
newClass->virtualMethods[i].registerMap->regWidth);
}
}
}
dvmLinearReadOnly(classLoader, newClass->virtualMethods);
}
newClass->sourceFile = dexGetSourceFile(pDexFile, pClassDef);
/* 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);
/* 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.
*/
clazz->vtableCount = -1;
if (clazz->vtable == gDvm.classJavaLangObject->vtable) {
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;
dvmLinearReadWrite(clazz->classLoader, directMethods);
for (i = 0; i < directMethodCount; i++) {
freeMethodInnards(&directMethods[i]);
}
dvmLinearReadOnly(clazz->classLoader, directMethods);
dvmLinearFree(clazz->classLoader, directMethods);
}
if (clazz->virtualMethods != NULL) {
Method *virtualMethods = clazz->virtualMethods;
int virtualMethodCount = clazz->virtualMethodCount;
clazz->virtualMethodCount = -1;
clazz->virtualMethods = NULL;
dvmLinearReadWrite(clazz->classLoader, virtualMethods);
for (i = 0; i < virtualMethodCount; i++) {
freeMethodInnards(&virtualMethods[i]);
}
dvmLinearReadOnly(clazz->classLoader, virtualMethods);
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;
/* The sfields are attached to the ClassObject, and will be freed
* with it. */
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.
*
* The containing class is largely torn down by this point.
*/
static void freeMethodInnards(Method* meth)
{
#if 0
free(meth->exceptions);
free(meth->lines);
free(meth->locals);
#endif
/*
* Some register maps are allocated on the heap, either because of late
* verification or because we're caching an uncompressed form.
*/
const RegisterMap* pMap = meth->registerMap;
if (pMap != NULL && dvmRegisterMapGetOnHeap(pMap)) {
dvmFreeRegisterMap((RegisterMap*) pMap);
meth->registerMap = NULL;
}
/*
* We may have copied the instructions.
*/
if (IS_METHOD_FLAG_SET(meth, METHOD_ISWRITABLE)) {
DexCode* methodDexCode = (DexCode*) dvmGetMethodCode(meth);
dvmLinearFree(meth->clazz->classLoader, methodDexCode);
}
}
/*
* Clone a Method, making new copies of anything that will be freed up
* by freeMethodInnards(). This is used for "miranda" methods.
*/
static void cloneMethod(Method* dst, const Method* src)
{
if (src->registerMap != NULL) {
LOGE("GLITCH: only expected abstract methods here\n");
LOGE(" cloning %s.%s\n", src->clazz->descriptor, src->name);
dvmAbort();
}
memcpy(dst, src, sizeof(Method));
}
/*
* 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) {
/*
* The Enum class declares a "final" finalize() method to
* prevent subclasses from introducing a finalizer. We don't
* want to set the finalizable flag for Enum or its subclasses,
* so we check for it here.
*
* We also want to avoid setting it on Object, but it's easier
* to just strip that out later.
*/
if (clazz->classLoader != NULL ||
strcmp(clazz->descriptor, "Ljava/lang/Enum;") != 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);
}
}
}
#if 0 /* replaced with private/read-write mapping */
/*
* We usually map bytecode directly out of the DEX file, which is mapped
* shared read-only. If we want to be able to modify it, we have to make
* a new copy.
*
* Once copied, the code will be in the LinearAlloc region, which may be
* marked read-only.
*
* The bytecode instructions are embedded inside a DexCode structure, so we
* need to copy all of that. (The dvmGetMethodCode function backs up the
* instruction pointer to find the start of the DexCode.)
*/
void dvmMakeCodeReadWrite(Method* meth)
{
DexCode* methodDexCode = (DexCode*) dvmGetMethodCode(meth);
if (IS_METHOD_FLAG_SET(meth, METHOD_ISWRITABLE)) {
dvmLinearReadWrite(meth->clazz->classLoader, methodDexCode);
return;
}
assert(!dvmIsNativeMethod(meth) && !dvmIsAbstractMethod(meth));
size_t dexCodeSize = dexGetDexCodeSize(methodDexCode);
LOGD("Making a copy of %s.%s code (%d bytes)\n",
meth->clazz->descriptor, meth->name, dexCodeSize);
DexCode* newCode =
(DexCode*) dvmLinearAlloc(meth->clazz->classLoader, dexCodeSize);
memcpy(newCode, methodDexCode, dexCodeSize);
meth->insns = newCode->insns;
SET_METHOD_FLAG(meth, METHOD_ISWRITABLE);
}
/*
* Mark the bytecode read-only.
*
* If the contents of the DexCode haven't actually changed, we could revert
* to the original shared page.
*/
void dvmMakeCodeReadOnly(Method* meth)
{
DexCode* methodDexCode = (DexCode*) dvmGetMethodCode(meth);
LOGV("+++ marking %p read-only\n", methodDexCode);
dvmLinearReadOnly(meth->clazz->classLoader, methodDexCode);
}
#endif
/*
* 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, jniArgInfo;
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;
case 'Z':
case 'B':
returnType = DALVIK_JNI_RETURN_S1;
break;
case 'C':
returnType = DALVIK_JNI_RETURN_U2;
break;
case 'S':
returnType = DALVIK_JNI_RETURN_S2;
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)
{
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;
}
/*
* Now that the above has been done, it is safe to cache
* info about the class.
*/
if (!dvmFindReferenceMembers(clazz)) {
LOGE("Trouble with Reference setup\n");
return false;
}
return true;
}
/*
* Set the bitmap of reference offsets, refOffsets, from the ifields
* list.
*/
static void computeRefOffsets(ClassObject* clazz)
{
if (clazz->super != NULL) {
clazz->refOffsets = clazz->super->refOffsets;
} else {
clazz->refOffsets = 0;
}
/*
* If our superclass overflowed, we don't stand a chance.
*/
if (clazz->refOffsets != CLASS_WALK_SUPER) {
InstField *f;
int i;
/* All of the fields that contain object references
* are guaranteed to be at the beginning of the ifields list.
*/
f = clazz->ifields;
const int ifieldRefCount = clazz->ifieldRefCount;
for (i = 0; i < ifieldRefCount; i++) {
/*
* Note that, per the comment on struct InstField,
* f->byteOffset is the offset from the beginning of
* obj, not the offset into obj->instanceData.
*/
assert(f->byteOffset >= (int) CLASS_SMALLEST_OFFSET);
assert((f->byteOffset & (CLASS_OFFSET_ALIGNMENT - 1)) == 0);
if (CLASS_CAN_ENCODE_OFFSET(f->byteOffset)) {
u4 newBit = CLASS_BIT_FROM_OFFSET(f->byteOffset);
assert(newBit != 0);
clazz->refOffsets |= newBit;
} else {
clazz->refOffsets = CLASS_WALK_SUPER;
break;
}
f++;
}
}
}
/*
* Link (prepare and resolve). Verification is deferred until later.
*
* This converts symbolic references into pointers. It's independent of
* the source file format.
*
* If clazz->status is CLASS_IDX, then clazz->super 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)
{
u4 superclassIdx = 0;
u4 *interfaceIdxArray = NULL;
bool okay = false;
int i;
assert(clazz != NULL);
assert(clazz->descriptor != NULL);
assert(clazz->status == CLASS_IDX || clazz->status == CLASS_LOADED);
if (gDvm.verboseClass)
LOGV("CLASS: linking '%s'...\n", clazz->descriptor);
assert(gDvm.classJavaLangClass != NULL);
assert(clazz->obj.clazz == gDvm.classJavaLangClass);
if (clazz->classLoader == NULL &&
(strcmp(clazz->descriptor, "Ljava/lang/Class;") == 0))
{
if (gDvm.classJavaLangClass->ifieldCount > CLASS_FIELD_SLOTS) {
LOGE("java.lang.Class has %d instance fields (expected at most %d)",
gDvm.classJavaLangClass->ifieldCount, CLASS_FIELD_SLOTS);
dvmAbort();
}
if (gDvm.classJavaLangClass->sfieldCount != CLASS_SFIELD_SLOTS) {
LOGE("java.lang.Class has %d static fields (expected %d)",
gDvm.classJavaLangClass->sfieldCount, CLASS_SFIELD_SLOTS);
dvmAbort();
}
}
/* "Resolve" the class.
*
* At this point, clazz's reference fields may contain Dex file
* indices instead of direct object references. Proxy objects are
* an exception, and may be the only exception. We need to
* translate those indices into real references, and let the GC
* look inside this ClassObject.
*/
if (clazz->status == CLASS_IDX) {
if (clazz->interfaceCount > 0) {
/* Copy u4 DEX idx values out of the ClassObject* array
* where we stashed them.
*/
assert(sizeof(*interfaceIdxArray) == sizeof(*clazz->interfaces));
size_t len = clazz->interfaceCount * sizeof(*interfaceIdxArray);
interfaceIdxArray = (u4*)malloc(len);
if (interfaceIdxArray == NULL) {
LOGW("Unable to allocate memory to link %s", clazz->descriptor);
goto bail;
}
memcpy(interfaceIdxArray, clazz->interfaces, len);
dvmLinearReadWrite(clazz->classLoader, clazz->interfaces);
memset(clazz->interfaces, 0, len);
dvmLinearReadOnly(clazz->classLoader, clazz->interfaces);
}
assert(sizeof(superclassIdx) == sizeof(clazz->super));
superclassIdx = (u4) clazz->super;
clazz->super = NULL;
/* After this line, clazz will be fair game for the GC. The
* superclass and interfaces are all NULL.
*/
clazz->status = CLASS_LOADED;
if (superclassIdx != kDexNoIndex) {
ClassObject* super = dvmResolveClass(clazz, superclassIdx, false);
if (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;
}
dvmSetFieldObject((Object *)clazz,
offsetof(ClassObject, super),
(Object *)super);
}
if (clazz->interfaceCount > 0) {
/* Resolve the interfaces implemented directly by this class. */
assert(interfaceIdxArray != NULL);
dvmLinearReadWrite(clazz->classLoader, clazz->interfaces);
for (i = 0; i < clazz->interfaceCount; i++) {
assert(interfaceIdxArray[i] != kDexNoIndex);
clazz->interfaces[i] =
dvmResolveClass(clazz, interfaceIdxArray[i], 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, interfaceIdxArray[i]);
if (gDvm.optimizing) {
/* happens with "external" libs */
LOGV("Failed resolving %s interface %d '%s'\n",
clazz->descriptor, interfaceIdxArray[i],
classDescriptor);
} else {
LOGI("Failed resolving %s interface %d '%s'\n",
clazz->descriptor, interfaceIdxArray[i],
classDescriptor);
}
goto bail;
}
/* 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);
dvmThrowIllegalAccessError("interface not accessible");
goto bail;
}
LOGVV("+++ found interface '%s'\n",
clazz->interfaces[i]->descriptor);
}
dvmLinearReadOnly(clazz->classLoader, clazz->interfaces);
}
}
/*
* There are now Class references visible to the GC in super and
* interfaces.
*/
/*
* All classes have a direct superclass, except for
* java/lang/Object and primitive classes. Primitive classes are
* are created CLASS_INITIALIZED, so won't get here.
*/
assert(clazz->primitiveType == PRIM_NOT);
if (strcmp(clazz->descriptor, "Ljava/lang/Object;") == 0) {
if (clazz->super != NULL) {
/* TODO: is this invariant true for all java/lang/Objects,
* regardless of the class loader? For now, assume it is.
*/
dvmThrowClassFormatError("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 (clazz->super == NULL) {
dvmThrowLinkageError("no superclass defined");
goto bail;
}
/* verify */
if (dvmIsFinalClass(clazz->super)) {
LOGW("Superclass of '%s' is final '%s'\n",
clazz->descriptor, clazz->super->descriptor);
dvmThrowIncompatibleClassChangeError("superclass is final");
goto bail;
} else if (dvmIsInterfaceClass(clazz->super)) {
LOGW("Superclass of '%s' is interface '%s'\n",
clazz->descriptor, clazz->super->descriptor);
dvmThrowIncompatibleClassChangeError("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);
dvmThrowIllegalAccessError("superclass not accessible");
goto bail;
}
/* 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.
dvmThrowLinkageError("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);
}
}
/*
* 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");
dvmThrowInternalError(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 */
}
}
}
/*
* Compact the offsets the GC has to examine into a bitmap, if
* possible. (This has to happen after Reference.referent is
* massaged in precacheReferenceOffsets.)
*/
computeRefOffsets(clazz);
/*
* 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 (DEBUGGER_ACTIVE) {
dvmDbgPostClassPrepare(clazz);
}
bail:
if (!okay) {
clazz->status = CLASS_ERROR;
if (!dvmCheckException(dvmThreadSelf())) {
dvmThrowVirtualMachineError(NULL);
}
}
if (interfaceIdxArray != NULL) {
free(interfaceIdxArray);
}
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;