/*
* 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.
*/
/*
* Array objects.
*/
#include "Dalvik.h"
#include <stdlib.h>
#include <stddef.h>
#if WITH_HPROF && WITH_HPROF_STACK
#include "hprof/Hprof.h"
#endif
static ClassObject* createArrayClass(const char* descriptor, Object* loader);
static ClassObject* createPrimitiveClass(int idx);
static const char gPrimLetter[] = PRIM_TYPE_TO_LETTER;
/*
* Allocate space for a new array object. This is the lowest-level array
* allocation function.
*
* Pass in the array class and the width of each element.
*
* On failure, returns NULL with an exception raised.
*/
ArrayObject* dvmAllocArray(ClassObject* arrayClass, size_t length,
size_t elemWidth, int allocFlags)
{
ArrayObject* newArray;
size_t size;
assert(arrayClass->descriptor[0] == '[');
if (length > 0x0fffffff) {
/* too large and (length * elemWidth) will overflow 32 bits */
LOGE("Rejecting allocation of %u-element array\n", length);
dvmThrowBadAllocException("array size too large");
return NULL;
}
size = offsetof(ArrayObject, contents);
size += length * elemWidth;
/* Note that we assume that the Array class does not
* override finalize().
*/
newArray = dvmMalloc(size, allocFlags);
if (newArray != NULL) {
DVM_OBJECT_INIT(&newArray->obj, arrayClass);
newArray->length = length;
LOGVV("AllocArray: %s [%d] (%d)\n",
arrayClass->descriptor, (int) length, (int) size);
#if WITH_HPROF && WITH_HPROF_STACK
hprofFillInStackTrace(&newArray->obj);
#endif
dvmTrackAllocation(arrayClass, size);
}
/* the caller must call dvmReleaseTrackedAlloc */
return newArray;
}
/*
* Create a new array, given an array class. The class may represent an
* array of references or primitives.
*/
ArrayObject* dvmAllocArrayByClass(ClassObject* arrayClass,
size_t length, int allocFlags)
{
const char* descriptor = arrayClass->descriptor;
assert(descriptor[0] == '['); /* must be array class */
if (descriptor[1] != '[' && descriptor[1] != 'L') {
/* primitive array */
assert(descriptor[2] == '\0');
return dvmAllocPrimitiveArray(descriptor[1], length, allocFlags);
} else {
return dvmAllocArray(arrayClass, length, kObjectArrayRefWidth,
allocFlags);
}
}
/*
* Find the array class for "elemClassObj", which could itself be an
* array class.
*/
ClassObject* dvmFindArrayClassForElement(ClassObject* elemClassObj)
{
ClassObject* arrayClass;
assert(elemClassObj != NULL);
/* Simply prepend "[" to the descriptor. */
int nameLen = strlen(elemClassObj->descriptor);
char className[nameLen + 2];
className[0] = '[';
memcpy(className+1, elemClassObj->descriptor, nameLen+1);
arrayClass = dvmFindArrayClass(className, elemClassObj->classLoader);
return arrayClass;
}
/*
* Create a new array that holds references to members of the specified class.
*
* "elemClassObj" is the element type, and may itself be an array class. It
* may not be a primitive class.
*
* "allocFlags" determines whether the new object will be added to the
* "tracked alloc" table.
*
* This is less efficient than dvmAllocArray(), but occasionally convenient.
*/
ArrayObject* dvmAllocObjectArray(ClassObject* elemClassObj, size_t length,
int allocFlags)
{
ClassObject* arrayClass;
ArrayObject* newArray = NULL;
LOGVV("dvmAllocObjectArray: '%s' len=%d\n",
elemClassObj->descriptor, (int)length);
arrayClass = dvmFindArrayClassForElement(elemClassObj);
if (arrayClass != NULL) {
newArray = dvmAllocArray(arrayClass, length, kObjectArrayRefWidth,
allocFlags);
}
/* the caller must call dvmReleaseTrackedAlloc */
return newArray;
}
/*
* Create a new array that holds primitive types.
*
* "type" is the primitive type letter, e.g. 'I' for int or 'J' for long.
* If the array class doesn't exist, it will be created.
*/
ArrayObject* dvmAllocPrimitiveArray(char type, size_t length, int allocFlags)
{
ArrayObject* newArray;
ClassObject** pTypeClass;
int width;
switch (type) {
case 'I':
pTypeClass = &gDvm.classArrayInt;
width = 4;
break;
case 'C':
pTypeClass = &gDvm.classArrayChar;
width = 2;
break;
case 'B':
pTypeClass = &gDvm.classArrayByte;
width = 1;
break;
case 'Z':
pTypeClass = &gDvm.classArrayBoolean;
width = 1; /* special-case this? */
break;
case 'F':
pTypeClass = &gDvm.classArrayFloat;
width = 4;
break;
case 'D':
pTypeClass = &gDvm.classArrayDouble;
width = 8;
break;
case 'S':
pTypeClass = &gDvm.classArrayShort;
width = 2;
break;
case 'J':
pTypeClass = &gDvm.classArrayLong;
width = 8;
break;
default:
LOGE("Unknown type '%c'\n", type);
assert(false);
return NULL;
}
if (*pTypeClass == NULL) {
char typeClassName[3] = "[x";
typeClassName[1] = type;
*pTypeClass = dvmFindArrayClass(typeClassName, NULL);
if (*pTypeClass == NULL) {
LOGE("ERROR: failed to generate array class for '%s'\n",
typeClassName);
return NULL;
}
}
newArray = dvmAllocArray(*pTypeClass, length, width, allocFlags);
/* the caller must dvmReleaseTrackedAlloc if allocFlags==ALLOC_DEFAULT */
return newArray;
}
/*
* Recursively create an array with multiple dimensions. Elements may be
* Objects or primitive types.
*
* The dimension we're creating is in dimensions[0], so when we recurse
* we advance the pointer.
*/
ArrayObject* dvmAllocMultiArray(ClassObject* arrayClass, int curDim,
const int* dimensions)
{
ArrayObject* newArray;
const char* elemName = arrayClass->descriptor + 1; // Advance past one '['.
LOGVV("dvmAllocMultiArray: class='%s' curDim=%d *dimensions=%d\n",
arrayClass->descriptor, curDim, *dimensions);
if (curDim == 0) {
if (*elemName == 'L' || *elemName == '[') {
LOGVV(" end: array class (obj) is '%s'\n",
arrayClass->descriptor);
newArray = dvmAllocArray(arrayClass, *dimensions,
kObjectArrayRefWidth, ALLOC_DEFAULT);
} else {
LOGVV(" end: array class (prim) is '%s'\n",
arrayClass->descriptor);
newArray = dvmAllocPrimitiveArray(
gPrimLetter[arrayClass->elementClass->primitiveType],
*dimensions, ALLOC_DEFAULT);
}
} else {
ClassObject* subArrayClass;
int i;
/* if we have X[][], find X[] */
subArrayClass = dvmFindArrayClass(elemName, arrayClass->classLoader);
if (subArrayClass == NULL) {
/* not enough '['s on the initial class? */
assert(dvmCheckException(dvmThreadSelf()));
return NULL;
}
assert(dvmIsArrayClass(subArrayClass));
/* allocate the array that holds the sub-arrays */
newArray = dvmAllocArray(arrayClass, *dimensions, kObjectArrayRefWidth,
ALLOC_DEFAULT);
if (newArray == NULL) {
assert(dvmCheckException(dvmThreadSelf()));
return NULL;
}
/*
* Create a new sub-array in every element of the array.
*/
for (i = 0; i < *dimensions; i++) {
ArrayObject* newSubArray;
newSubArray = dvmAllocMultiArray(subArrayClass, curDim-1,
dimensions+1);
if (newSubArray == NULL) {
dvmReleaseTrackedAlloc((Object*) newArray, NULL);
assert(dvmCheckException(dvmThreadSelf()));
return NULL;
}
dvmSetObjectArrayElement(newArray, i, (Object *)newSubArray);
dvmReleaseTrackedAlloc((Object*) newSubArray, NULL);
}
}
/* caller must call dvmReleaseTrackedAlloc */
return newArray;
}
/*
* Find an array class, by name (e.g. "[I").
*
* If the array class doesn't exist, we generate it.
*
* If the element class doesn't exist, we return NULL (no exception raised).
*/
ClassObject* dvmFindArrayClass(const char* descriptor, Object* loader)
{
ClassObject* clazz;
assert(descriptor[0] == '[');
//LOGV("dvmFindArrayClass: '%s' %p\n", descriptor, loader);
clazz = dvmLookupClass(descriptor, loader, false);
if (clazz == NULL) {
LOGV("Array class '%s' %p not found; creating\n", descriptor, loader);
clazz = createArrayClass(descriptor, loader);
if (clazz != NULL)
dvmAddInitiatingLoader(clazz, loader);
}
return clazz;
}
/*
* Create an array class (i.e. the class object for the array, not the
* array itself). "descriptor" looks like "[C" or "[Ljava/lang/String;".
*
* If "descriptor" refers to an array of primitives, look up the
* primitive type's internally-generated class object.
*
* "loader" is the class loader of the class that's referring to us. It's
* used to ensure that we're looking for the element type in the right
* context. It does NOT become the class loader for the array class; that
* always comes from the base element class.
*
* Returns NULL with an exception raised on failure.
*/
static ClassObject* createArrayClass(const char* descriptor, Object* loader)
{
ClassObject* newClass = NULL;
ClassObject* elementClass = NULL;
int arrayDim;
u4 extraFlags;
assert(descriptor[0] == '[');
assert(gDvm.classJavaLangClass != NULL);
assert(gDvm.classJavaLangObject != NULL);
/*
* Identify the underlying element class and the array dimension depth.
*/
extraFlags = CLASS_ISARRAY;
if (descriptor[1] == '[') {
/* array of arrays; keep descriptor and grab stuff from parent */
ClassObject* outer;
outer = dvmFindClassNoInit(&descriptor[1], loader);
if (outer != NULL) {
/* want the base class, not "outer", in our elementClass */
elementClass = outer->elementClass;
arrayDim = outer->arrayDim + 1;
extraFlags |= CLASS_ISOBJECTARRAY;
} else {
assert(elementClass == NULL); /* make sure we fail */
}
} else {
arrayDim = 1;
if (descriptor[1] == 'L') {
/* array of objects; strip off "[" and look up descriptor. */
const char* subDescriptor = &descriptor[1];
LOGVV("searching for element class '%s'\n", subDescriptor);
elementClass = dvmFindClassNoInit(subDescriptor, loader);
extraFlags |= CLASS_ISOBJECTARRAY;
} else {
/* array of a primitive type */
elementClass = dvmFindPrimitiveClass(descriptor[1]);
}
}
if (elementClass == NULL) {
/* failed */
assert(dvmCheckException(dvmThreadSelf()));
dvmFreeClassInnards(newClass);
dvmReleaseTrackedAlloc((Object*) newClass, NULL);
return NULL;
}
/*
* See if it's already loaded. Array classes are always associated
* with the class loader of their underlying element type -- an array
* of Strings goes with the loader for java/lang/String -- so we need
* to look for it there. (The caller should have checked for the
* existence of the class before calling here, but they did so with
* *their* class loader, not the element class' loader.)
*
* If we find it, the caller adds "loader" to the class' initiating
* loader list, which should prevent us from going through this again.
*
* This call is unnecessary if "loader" and "elementClass->classLoader"
* are the same, because our caller (dvmFindArrayClass) just did the
* lookup. (Even if we get this wrong we still have correct behavior,
* because we effectively do this lookup again when we add the new
* class to the hash table -- necessary because of possible races with
* other threads.)
*/
if (loader != elementClass->classLoader) {
LOGVV("--- checking for '%s' in %p vs. elem %p\n",
descriptor, loader, elementClass->classLoader);
newClass = dvmLookupClass(descriptor, elementClass->classLoader, false);
if (newClass != NULL) {
LOGV("--- we already have %s in %p, don't need in %p\n",
descriptor, elementClass->classLoader, loader);
return newClass;
}
}
/*
* Fill out the fields in the ClassObject.
*
* It is possible to execute some methods against arrays, because all
* arrays are instances of Object, so we need to set up a vtable. We
* can just point at the one in Object.
*
* Array classes are simple enough that we don't need to do a full
* link step.
*/
newClass = (ClassObject*) dvmMalloc(sizeof(*newClass), ALLOC_DEFAULT);
if (newClass == NULL)
return NULL;
DVM_OBJECT_INIT(&newClass->obj, gDvm.classJavaLangClass);
dvmSetClassSerialNumber(newClass);
newClass->descriptorAlloc = strdup(descriptor);
newClass->descriptor = newClass->descriptorAlloc;
dvmSetFieldObject((Object *)newClass,
offsetof(ClassObject, super),
(Object *)gDvm.classJavaLangObject);
newClass->vtableCount = gDvm.classJavaLangObject->vtableCount;
newClass->vtable = gDvm.classJavaLangObject->vtable;
newClass->primitiveType = PRIM_NOT;
dvmSetFieldObject((Object *)newClass,
offsetof(ClassObject, elementClass),
(Object *)elementClass);
dvmSetFieldObject((Object *)newClass,
offsetof(ClassObject, classLoader),
(Object *)elementClass->classLoader);
newClass->arrayDim = arrayDim;
newClass->status = CLASS_INITIALIZED;
#if WITH_HPROF && WITH_HPROF_STACK
hprofFillInStackTrace(newClass);
#endif
/* don't need to set newClass->objectSize */
/*
* All arrays have java/lang/Cloneable and java/io/Serializable as
* interfaces. We need to set that up here, so that stuff like
* "instanceof" works right.
*
* Note: The GC could run during the call to dvmFindSystemClassNoInit(),
* so we need to make sure the class object is GC-valid while we're in
* there. Do this by clearing the interface list so the GC will just
* think that the entries are null.
*
* TODO?
* We may want to cache these two classes to avoid the lookup, though
* it's not vital -- we only do it when creating an array class, not
* every time we create an array. Better yet, create a single, global
* copy of "interfaces" and "iftable" somewhere near the start and
* just point to those (and remember not to free them for arrays).
*/
newClass->interfaceCount = 2;
newClass->interfaces = (ClassObject**)dvmLinearAlloc(newClass->classLoader,
sizeof(ClassObject*) * 2);
memset(newClass->interfaces, 0, sizeof(ClassObject*) * 2);
newClass->interfaces[0] =
dvmFindSystemClassNoInit("Ljava/lang/Cloneable;");
newClass->interfaces[1] =
dvmFindSystemClassNoInit("Ljava/io/Serializable;");
dvmLinearReadOnly(newClass->classLoader, newClass->interfaces);
if (newClass->interfaces[0] == NULL || newClass->interfaces[1] == NULL) {
LOGE("Unable to create array class '%s': missing interfaces\n",
descriptor);
dvmFreeClassInnards(newClass);
dvmThrowException("Ljava/lang/InternalError;", "missing array ifaces");
dvmReleaseTrackedAlloc((Object*) newClass, NULL);
return NULL;
}
/*
* We assume that Cloneable/Serializable don't have superinterfaces --
* normally we'd have to crawl up and explicitly list all of the
* supers as well. These interfaces don't have any methods, so we
* don't have to worry about the ifviPool either.
*/
newClass->iftableCount = 2;
newClass->iftable = (InterfaceEntry*) dvmLinearAlloc(newClass->classLoader,
sizeof(InterfaceEntry) * 2);
memset(newClass->iftable, 0, sizeof(InterfaceEntry) * 2);
newClass->iftable[0].clazz = newClass->interfaces[0];
newClass->iftable[1].clazz = newClass->interfaces[1];
dvmLinearReadOnly(newClass->classLoader, newClass->iftable);
/*
* Inherit access flags from the element. Arrays can't be used as a
* superclass or interface, so we want to add "final" and remove
* "interface".
*
* Don't inherit any non-standard flags (e.g., CLASS_FINALIZABLE)
* from elementClass. We assume that the array class does not
* override finalize().
*/
newClass->accessFlags = ((newClass->elementClass->accessFlags &
~ACC_INTERFACE) | ACC_FINAL) & JAVA_FLAGS_MASK;
/* Set the flags we determined above.
* This must happen after accessFlags is set.
*/
SET_CLASS_FLAG(newClass, extraFlags);
if (!dvmAddClassToHash(newClass)) {
/*
* 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.
*/
LOGI("WOW: somebody generated %s simultaneously\n",
newClass->descriptor);
/* Clean up the class before letting the
* GC get its hands on it.
*/
dvmFreeClassInnards(newClass);
/* Let the GC free the class.
*/
dvmReleaseTrackedAlloc((Object*) newClass, NULL);
/* Grab the winning class.
*/
newClass = dvmLookupClass(descriptor, elementClass->classLoader, false);
assert(newClass != NULL);
return newClass;
}
dvmReleaseTrackedAlloc((Object*) newClass, NULL);
LOGV("Created array class '%s' %p (access=0x%04x.%04x)\n",
descriptor, newClass->classLoader,
newClass->accessFlags >> 16,
newClass->accessFlags & JAVA_FLAGS_MASK);
return newClass;
}
/*
* Get a class we generated for the primitive types.
*
* These correspond to e.g. Integer.TYPE, and are used as the element
* class in arrays of primitives.
*
* "type" should be 'I', 'J', 'Z', etc.
*
* Returns NULL if the type doesn't correspond to a known primitive type.
*/
ClassObject* dvmFindPrimitiveClass(char type)
{
int idx;
switch (type) {
case 'Z':
idx = PRIM_BOOLEAN;
break;
case 'C':
idx = PRIM_CHAR;
break;
case 'F':
idx = PRIM_FLOAT;
break;
case 'D':
idx = PRIM_DOUBLE;
break;
case 'B':
idx = PRIM_BYTE;
break;
case 'S':
idx = PRIM_SHORT;
break;
case 'I':
idx = PRIM_INT;
break;
case 'J':
idx = PRIM_LONG;
break;
case 'V':
idx = PRIM_VOID;
break;
default:
LOGW("Unknown primitive type '%c'\n", type);
return NULL;
}
/*
* Create the primitive class if it hasn't already been, and add it
* to the table.
*/
if (gDvm.primitiveClass[idx] == NULL) {
ClassObject* primClass = createPrimitiveClass(idx);
dvmReleaseTrackedAlloc((Object*) primClass, NULL);
if (android_atomic_release_cas(0, (int) primClass,
(int*) &gDvm.primitiveClass[idx]) != 0)
{
/*
* Looks like somebody beat us to it. Free up the one we
* just created and use the other one.
*/
dvmFreeClassInnards(primClass);
}
}
return gDvm.primitiveClass[idx];
}
/*
* Synthesize a primitive class.
*
* Just creates the class and returns it (does not add it to the class list).
*/
static ClassObject* createPrimitiveClass(int idx)
{
ClassObject* newClass;
static const char* kClassDescriptors[PRIM_MAX] = {
"Z", "C", "F", "D", "B", "S", "I", "J", "V"
};
assert(gDvm.classJavaLangClass != NULL);
assert(idx >= 0 && idx < PRIM_MAX);
/*
* Fill out a few fields in the ClassObject.
*
* Note that primitive classes do not sub-class java/lang/Object. This
* matters for "instanceof" checks. Also, we assume that the primitive
* class does not override finalize().
*/
newClass = (ClassObject*) dvmMalloc(sizeof(*newClass), ALLOC_DEFAULT);
if (newClass == NULL)
return NULL;
DVM_OBJECT_INIT(&newClass->obj, gDvm.classJavaLangClass);
dvmSetClassSerialNumber(newClass);
newClass->accessFlags = ACC_PUBLIC | ACC_FINAL | ACC_ABSTRACT;
newClass->primitiveType = idx;
newClass->descriptorAlloc = NULL;
newClass->descriptor = kClassDescriptors[idx];
//newClass->super = gDvm.classJavaLangObject;
newClass->status = CLASS_INITIALIZED;
#if WITH_HPROF && WITH_HPROF_STACK
hprofFillInStackTrace(newClass);
#endif
/* don't need to set newClass->objectSize */
LOGVV("Created primitive class '%s'\n", kClassDescriptors[idx]);
return newClass;
}
/*
* Copy the entire contents of one array of objects to another. If the copy
* is impossible because of a type clash, we fail and return "false".
*/
bool dvmCopyObjectArray(ArrayObject* dstArray, const ArrayObject* srcArray,
ClassObject* dstElemClass)
{
Object** src = (Object**)srcArray->contents;
u4 length, count;
assert(srcArray->length == dstArray->length);
assert(dstArray->obj.clazz->elementClass == dstElemClass ||
(dstArray->obj.clazz->elementClass == dstElemClass->elementClass &&
dstArray->obj.clazz->arrayDim == dstElemClass->arrayDim+1));
length = dstArray->length;
for (count = 0; count < length; count++) {
if (!dvmInstanceof(src[count]->clazz, dstElemClass)) {
LOGW("dvmCopyObjectArray: can't store %s in %s\n",
src[count]->clazz->descriptor, dstElemClass->descriptor);
return false;
}
dvmSetObjectArrayElement(dstArray, count, src[count]);
}
return true;
}
/*
* Copy the entire contents of an array of boxed primitives into an
* array of primitives. The boxed value must fit in the primitive (i.e.
* narrowing conversions are not allowed).
*/
bool dvmUnboxObjectArray(ArrayObject* dstArray, const ArrayObject* srcArray,
ClassObject* dstElemClass)
{
Object** src = (Object**)srcArray->contents;
void* dst = (void*)dstArray->contents;
u4 count = dstArray->length;
PrimitiveType typeIndex = dstElemClass->primitiveType;
assert(typeIndex != PRIM_NOT);
assert(srcArray->length == dstArray->length);
while (count--) {
JValue result;
/*
* This will perform widening conversions as appropriate. It
* might make sense to be more restrictive and require that the
* primitive type exactly matches the box class, but it's not
* necessary for correctness.
*/
if (!dvmUnwrapPrimitive(*src, dstElemClass, &result)) {
LOGW("dvmCopyObjectArray: can't store %s in %s\n",
(*src)->clazz->descriptor, dstElemClass->descriptor);
return false;
}
/* would be faster with 4 loops, but speed not crucial here */
switch (typeIndex) {
case PRIM_BOOLEAN:
case PRIM_BYTE:
{
u1* tmp = dst;
*tmp++ = result.b;
dst = tmp;
}
break;
case PRIM_CHAR:
case PRIM_SHORT:
{
u2* tmp = dst;
*tmp++ = result.s;
dst = tmp;
}
break;
case PRIM_FLOAT:
case PRIM_INT:
{
u4* tmp = dst;
*tmp++ = result.i;
dst = tmp;
}
break;
case PRIM_DOUBLE:
case PRIM_LONG:
{
u8* tmp = dst;
*tmp++ = result.j;
dst = tmp;
}
break;
default:
/* should not be possible to get here */
dvmAbort();
}
src++;
}
return true;
}
/*
* Returns the width, in bytes, required by elements in instances of
* the array class.
*/
size_t dvmArrayClassElementWidth(const ClassObject* arrayClass)
{
const char *descriptor;
assert(dvmIsArrayClass(arrayClass));
if (dvmIsObjectArrayClass(arrayClass)) {
return sizeof(Object *);
} else {
descriptor = arrayClass->descriptor;
switch (descriptor[1]) {
case 'B': return 1; /* byte */
case 'C': return 2; /* char */
case 'D': return 8; /* double */
case 'F': return 4; /* float */
case 'I': return 4; /* int */
case 'J': return 8; /* long */
case 'S': return 2; /* short */
case 'Z': return 1; /* boolean */
}
}
LOGE("class %p has an unhandled descriptor '%s'", arrayClass, descriptor);
dvmDumpThread(dvmThreadSelf(), false);
dvmAbort();
return 0; /* Quiet the compiler. */
}
size_t dvmArrayObjectSize(const ArrayObject *array)
{
size_t size;
assert(array != NULL);
size = offsetof(ArrayObject, contents);
size += array->length * dvmArrayClassElementWidth(array->obj.clazz);
return size;
}
/*
* Add all primitive classes to the root set of objects.
TODO: do these belong to the root class loader?
*/
void dvmGcScanPrimitiveClasses()
{
int i;
for (i = 0; i < PRIM_MAX; i++) {
dvmMarkObject((Object *)gDvm.primitiveClass[i]); // may be NULL
}
}