/*
* 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.
*/
/*
* Implementation of java.lang.reflect.Proxy.
*
* Traditionally this is implemented entirely in interpreted code,
* generating bytecode that defines the proxy class. Dalvik doesn't
* currently support this approach, so we generate the class directly. If
* we add support for DefineClass with standard classfiles we can
* eliminate this.
*/
#include "Dalvik.h"
#include <stdlib.h>
// fwd
static bool returnTypesAreCompatible(Method* baseMethod, Method* subMethod);
static bool gatherMethods(ArrayObject* interfaces, Method*** pMethods,\
ArrayObject** pThrows, int* pMethodCount);
static int copyWithoutDuplicates(Method** allMethods, int allCount,
Method** outMethods, ArrayObject* throws);
static bool createExceptionClassList(const Method* method,
PointerSet** pThrows);
static void updateExceptionClassList(const Method* method, PointerSet* throws);
static void createConstructor(ClassObject* clazz, Method* meth);
static void createHandlerMethod(ClassObject* clazz, Method* dstMeth,
const Method* srcMeth);
static void proxyConstructor(const u4* args, JValue* pResult,
const Method* method, Thread* self);
static void proxyInvoker(const u4* args, JValue* pResult,
const Method* method, Thread* self);
static bool mustWrapException(const Method* method, const Object* throwable);
/* private static fields in the Proxy class */
#define kThrowsField 0
/*
* Perform Proxy setup.
*/
bool dvmReflectProxyStartup()
{
/*
* Standard methods we must provide in our proxy.
*/
Method* methE;
Method* methH;
Method* methT;
Method* methF;
methE = dvmFindVirtualMethodByDescriptor(gDvm.classJavaLangObject,
"equals", "(Ljava/lang/Object;)Z");
methH = dvmFindVirtualMethodByDescriptor(gDvm.classJavaLangObject,
"hashCode", "()I");
methT = dvmFindVirtualMethodByDescriptor(gDvm.classJavaLangObject,
"toString", "()Ljava/lang/String;");
methF = dvmFindVirtualMethodByDescriptor(gDvm.classJavaLangObject,
"finalize", "()V");
if (methE == NULL || methH == NULL || methT == NULL || methF == NULL) {
LOGE("Could not find equals/hashCode/toString/finalize in Object\n");
return false;
}
gDvm.voffJavaLangObject_equals = methE->methodIndex;
gDvm.voffJavaLangObject_hashCode = methH->methodIndex;
gDvm.voffJavaLangObject_toString = methT->methodIndex;
gDvm.voffJavaLangObject_finalize = methF->methodIndex;
/*
* The prototype signature needs to be cloned from a method in a
* "real" DEX file. We declared this otherwise unused method just
* for this purpose.
*/
ClassObject* proxyClass;
Method* meth;
proxyClass = dvmFindSystemClassNoInit("Ljava/lang/reflect/Proxy;");
if (proxyClass == NULL) {
LOGE("No java.lang.reflect.Proxy\n");
return false;
}
meth = dvmFindDirectMethodByDescriptor(proxyClass, "constructorPrototype",
"(Ljava/lang/reflect/InvocationHandler;)V");
if (meth == NULL) {
LOGE("Could not find java.lang.Proxy.constructorPrototype()\n");
return false;
}
gDvm.methJavaLangReflectProxy_constructorPrototype = meth;
/*
* Get the offset of the "h" field in Proxy.
*/
gDvm.offJavaLangReflectProxy_h = dvmFindFieldOffset(proxyClass, "h",
"Ljava/lang/reflect/InvocationHandler;");
if (gDvm.offJavaLangReflectProxy_h < 0) {
LOGE("Unable to find 'h' field in java.lang.Proxy\n");
return false;
}
return true;
}
/*
* Generate a proxy class with the specified name, interfaces, and loader.
* "interfaces" is an array of class objects.
*
* The Proxy.getProxyClass() code has done the following:
* - Verified that "interfaces" contains only interfaces
* - Verified that no interface appears twice
* - Prepended the package name to the class name if one or more
* interfaces are non-public
* - Searched for an existing instance of an appropriate Proxy class
*
* On failure we leave a partially-created class object sitting around,
* but the garbage collector will take care of it.
*/
ClassObject* dvmGenerateProxyClass(StringObject* str, ArrayObject* interfaces,
Object* loader)
{
int result = -1;
char* nameStr = NULL;
Method** methods = NULL;
ArrayObject* throws = NULL;
ClassObject* newClass = NULL;
int i;
nameStr = dvmCreateCstrFromString(str);
if (nameStr == NULL) {
dvmThrowException("Ljava/lang/IllegalArgumentException;",
"missing name");
goto bail;
}
LOGV("+++ Generate proxy class '%s' %p from %d interface classes\n",
nameStr, loader, interfaces->length);
/*
* Characteristics of a Proxy class:
* - concrete class, public and final
* - superclass is java.lang.reflect.Proxy
* - implements all listed interfaces (req'd for instanceof)
* - has one method for each method in the interfaces (for duplicates,
* the method in the earliest interface wins)
* - has one constructor (takes an InvocationHandler arg)
* - has overrides for hashCode, equals, and toString (these come first)
* - has one field, a reference to the InvocationHandler object, inherited
* from Proxy
*
* TODO: set protection domain so it matches bootstrap classes.
*
* The idea here is to create a class object and fill in the details
* as we would in loadClassFromDex(), and then call dvmLinkClass() to do
* all the heavy lifting (notably populating the virtual and interface
* method tables).
*/
/*
* Generate a temporary list of virtual methods.
*/
int methodCount = -1;
if (!gatherMethods(interfaces, &methods, &throws, &methodCount))
goto bail;
/*
* Allocate storage for the class object and set some basic fields.
*/
newClass = (ClassObject*) dvmMalloc(sizeof(*newClass), ALLOC_DEFAULT);
if (newClass == NULL)
goto bail;
DVM_OBJECT_INIT(&newClass->obj, gDvm.unlinkedJavaLangClass);
dvmSetClassSerialNumber(newClass);
newClass->descriptorAlloc = dvmNameToDescriptor(nameStr);
newClass->descriptor = newClass->descriptorAlloc;
newClass->accessFlags = ACC_PUBLIC | ACC_FINAL;
newClass->super = gDvm.classJavaLangReflectProxy;
newClass->primitiveType = PRIM_NOT;
newClass->classLoader = loader;
#if WITH_HPROF && WITH_HPROF_STACK
hprofFillInStackTrace(newClass);
#endif
/*
* Add direct method definitions. We have one (the constructor).
*/
newClass->directMethodCount = 1;
newClass->directMethods = (Method*) dvmLinearAlloc(newClass->classLoader,
1 * sizeof(Method));
createConstructor(newClass, &newClass->directMethods[0]);
dvmLinearReadOnly(newClass->classLoader, newClass->directMethods);
/*
* Add virtual method definitions.
*/
newClass->virtualMethodCount = methodCount;
newClass->virtualMethods = (Method*) dvmLinearAlloc(newClass->classLoader,
newClass->virtualMethodCount * sizeof(Method));
for (i = 0; i < newClass->virtualMethodCount; i++) {
createHandlerMethod(newClass, &newClass->virtualMethods[i],methods[i]);
}
dvmLinearReadOnly(newClass->classLoader, newClass->virtualMethods);
/*
* Add interface list.
*/
int interfaceCount = interfaces->length;
ClassObject** ifArray = (ClassObject**) interfaces->contents;
newClass->interfaceCount = interfaceCount;
newClass->interfaces = (ClassObject**)dvmLinearAlloc(newClass->classLoader,
sizeof(ClassObject*) * interfaceCount);
for (i = 0; i < interfaceCount; i++)
newClass->interfaces[i] = ifArray[i];
dvmLinearReadOnly(newClass->classLoader, newClass->interfaces);
/*
* Static field list. We have one private field, for our list of
* exceptions declared for each method.
*/
newClass->sfieldCount = 1;
newClass->sfields = (StaticField*) calloc(1, sizeof(StaticField));
StaticField* sfield = &newClass->sfields[kThrowsField];
sfield->field.clazz = newClass;
sfield->field.name = "throws";
sfield->field.signature = "[[Ljava/lang/Throwable;";
sfield->field.accessFlags = ACC_STATIC | ACC_PRIVATE;
dvmSetStaticFieldObject(sfield, (Object*)throws);
/*
* Everything is ready. See if the linker will lap it up.
*/
newClass->status = CLASS_LOADED;
if (!dvmLinkClass(newClass, true)) {
LOGD("Proxy class link failed\n");
goto bail;
}
/*
* All good. Add it to the hash table. We should NOT see a collision
* here; if we do, it means the caller has screwed up and provided us
* with a duplicate name.
*/
if (!dvmAddClassToHash(newClass)) {
LOGE("ERROR: attempted to generate %s more than once\n",
newClass->descriptor);
goto bail;
}
result = 0;
bail:
free(nameStr);
free(methods);
if (result != 0) {
/* must free innards explicitly if we didn't finish linking */
dvmFreeClassInnards(newClass);
newClass = NULL;
if (!dvmCheckException(dvmThreadSelf())) {
/* throw something */
dvmThrowException("Ljava/lang/RuntimeException;", NULL);
}
}
/* allow the GC to free these when nothing else has a reference */
dvmReleaseTrackedAlloc((Object*) throws, NULL);
dvmReleaseTrackedAlloc((Object*) newClass, NULL);
return newClass;
}
/*
* Generate a list of methods. The Method pointers returned point to the
* abstract method definition from the appropriate interface, or to the
* virtual method definition in java.lang.Object.
*
* We also allocate an array of arrays of throwable classes, one for each
* method,so we can do some special handling of checked exceptions. The
* caller must call ReleaseTrackedAlloc() on *pThrows.
*/
static bool gatherMethods(ArrayObject* interfaces, Method*** pMethods,
ArrayObject** pThrows, int* pMethodCount)
{
ClassObject** classes;
ArrayObject* throws = NULL;
Method** methods = NULL;
Method** allMethods = NULL;
int numInterfaces, maxCount, actualCount, allCount;
bool result = false;
int i;
/*
* Get a maximum count so we can allocate storage. We need the
* methods declared by each interface and all of its superinterfaces.
*/
maxCount = 3; // 3 methods in java.lang.Object
numInterfaces = interfaces->length;
classes = (ClassObject**) interfaces->contents;
for (i = 0; i < numInterfaces; i++, classes++) {
ClassObject* clazz = *classes;
LOGVV("--- %s virtualMethodCount=%d\n",
clazz->descriptor, clazz->virtualMethodCount);
maxCount += clazz->virtualMethodCount;
int j;
for (j = 0; j < clazz->iftableCount; j++) {
ClassObject* iclass = clazz->iftable[j].clazz;
LOGVV("--- +%s %d\n",
iclass->descriptor, iclass->virtualMethodCount);
maxCount += iclass->virtualMethodCount;
}
}
methods = (Method**) malloc(maxCount * sizeof(*methods));
allMethods = (Method**) malloc(maxCount * sizeof(*methods));
if (methods == NULL || allMethods == NULL)
goto bail;
/*
* First three entries are the java.lang.Object methods.
*/
ClassObject* obj = gDvm.classJavaLangObject;
allMethods[0] = obj->vtable[gDvm.voffJavaLangObject_equals];
allMethods[1] = obj->vtable[gDvm.voffJavaLangObject_hashCode];
allMethods[2] = obj->vtable[gDvm.voffJavaLangObject_toString];
allCount = 3;
/*
* Add the methods from each interface, in order.
*/
classes = (ClassObject**) interfaces->contents;
for (i = 0; i < numInterfaces; i++, classes++) {
ClassObject* clazz = *classes;
int j;
for (j = 0; j < clazz->virtualMethodCount; j++) {
allMethods[allCount++] = &clazz->virtualMethods[j];
}
for (j = 0; j < clazz->iftableCount; j++) {
ClassObject* iclass = clazz->iftable[j].clazz;
int k;
for (k = 0; k < iclass->virtualMethodCount; k++) {
allMethods[allCount++] = &iclass->virtualMethods[k];
}
}
}
assert(allCount == maxCount);
/*
* Allocate some storage to hold the lists of throwables. We need
* one entry per unique method, but it's convenient to allocate it
* ahead of the duplicate processing.
*/
ClassObject* arrArrClass;
arrArrClass = dvmFindArrayClass("[[Ljava/lang/Throwable;", NULL);
if (arrArrClass == NULL)
goto bail;
throws = dvmAllocArrayByClass(arrArrClass, allCount, ALLOC_DEFAULT);
/*
* Identify and remove duplicates.
*/
actualCount = copyWithoutDuplicates(allMethods, allCount, methods, throws);
if (actualCount < 0)
goto bail;
//LOGI("gathered methods:\n");
//for (i = 0; i < actualCount; i++) {
// LOGI(" %d: %s.%s\n",
// i, methods[i]->clazz->descriptor, methods[i]->name);
//}
*pMethods = methods;
*pMethodCount = actualCount;
*pThrows = throws;
result = true;
bail:
free(allMethods);
if (!result) {
free(methods);
dvmReleaseTrackedAlloc((Object*)throws, NULL);
}
return result;
}
/*
* Identify and remove duplicates, where "duplicate" means it has the
* same name and arguments, but not necessarily the same return type.
*
* If duplicate methods have different return types, we want to use the
* first method whose return type is assignable from all other duplicate
* methods. That is, if we have:
* class base {...}
* class sub extends base {...}
* class subsub extends sub {...}
* Then we want to return the method that returns subsub, since callers
* to any form of the method will get a usable object back.
*
* All other duplicate methods are stripped out.
*
* This also populates the "throwLists" array with arrays of Class objects,
* one entry per method in "outMethods". Methods that don't declare any
* throwables (or have no common throwables with duplicate methods) will
* have NULL entries.
*
* Returns the number of methods copied into "methods", or -1 on failure.
*/
static int copyWithoutDuplicates(Method** allMethods, int allCount,
Method** outMethods, ArrayObject* throwLists)
{
Method* best;
int outCount = 0;
int i, j;
/*
* The plan is to run through all methods, checking all other methods
* for a duplicate. If we find a match, we see if the other methods'
* return type is compatible/assignable with ours. If the current
* method is assignable from all others, we copy it to the new list,
* and NULL out all other entries. If not, we keep looking for a
* better version.
*
* If there are no duplicates, we copy the method and NULL the entry.
*
* At the end of processing, if we have any non-NULL entries, then we
* have bad duplicates and must exit with an exception.
*/
for (i = 0; i < allCount; i++) {
bool best, dupe;
if (allMethods[i] == NULL)
continue;
/*
* Find all duplicates. If any of the return types is not
* assignable to our return type, then we're not the best.
*
* We start from 0, not i, because we need to compare assignability
* the other direction even if we've compared these before.
*/
dupe = false;
best = true;
for (j = 0; j < allCount; j++) {
if (i == j)
continue;
if (allMethods[j] == NULL)
continue;
if (dvmCompareMethodNamesAndParameterProtos(allMethods[i],
allMethods[j]) == 0)
{
/*
* Duplicate method, check return type. If it's a primitive
* type or void, the types must match exactly, or we throw
* an exception now.
*/
LOGV("MATCH on %s.%s and %s.%s\n",
allMethods[i]->clazz->descriptor, allMethods[i]->name,
allMethods[j]->clazz->descriptor, allMethods[j]->name);
dupe = true;
if (!returnTypesAreCompatible(allMethods[i], allMethods[j]))
best = false;
}
}
/*
* If this is the best of a set of duplicates, copy it over and
* nuke all duplicates.
*
* While we do this, we create the set of exceptions declared to
* be thrown by all occurrences of the method.
*/
if (dupe) {
if (best) {
LOGV("BEST %d %s.%s -> %d\n", i,
allMethods[i]->clazz->descriptor, allMethods[i]->name,
outCount);
/* if we have exceptions, make a local copy */
PointerSet* commonThrows = NULL;
if (!createExceptionClassList(allMethods[i], &commonThrows))
return -1;
/*
* Run through one more time, erasing the duplicates. (This
* would go faster if we had marked them somehow.)
*/
for (j = 0; j < allCount; j++) {
if (i == j)
continue;
if (allMethods[j] == NULL)
continue;
if (dvmCompareMethodNamesAndParameterProtos(allMethods[i],
allMethods[j]) == 0)
{
LOGV("DEL %d %s.%s\n", j,
allMethods[j]->clazz->descriptor,
allMethods[j]->name);
/*
* Update set to hold the intersection of method[i]'s
* and method[j]'s throws.
*/
if (commonThrows != NULL) {
updateExceptionClassList(allMethods[j],
commonThrows);
}
allMethods[j] = NULL;
}
}
/*
* If the set of Throwable classes isn't empty, create an
* array of Class, copy them into it, and put the result
* into the "throwLists" array.
*/
if (commonThrows != NULL &&
dvmPointerSetGetCount(commonThrows) > 0)
{
int commonCount = dvmPointerSetGetCount(commonThrows);
ArrayObject* throwArray;
Object** contents;
int ent;
throwArray = dvmAllocArrayByClass(
gDvm.classJavaLangClassArray, commonCount,
ALLOC_DEFAULT);
if (throwArray == NULL) {
LOGE("common-throw array alloc failed\n");
return -1;
}
contents = (Object**) throwArray->contents;
for (ent = 0; ent < commonCount; ent++) {
contents[ent] = (Object*)
dvmPointerSetGetEntry(commonThrows, ent);
}
/* add it to the array of arrays */
contents = (Object**) throwLists->contents;
contents[outCount] = (Object*) throwArray;
dvmReleaseTrackedAlloc((Object*) throwArray, NULL);
}
/* copy the winner and NULL it out */
outMethods[outCount++] = allMethods[i];
allMethods[i] = NULL;
dvmPointerSetFree(commonThrows);
} else {
LOGV("BEST not %d\n", i);
}
} else {
/*
* Singleton. Copy the entry and NULL it out.
*/
LOGV("COPY singleton %d %s.%s -> %d\n", i,
allMethods[i]->clazz->descriptor, allMethods[i]->name,
outCount);
/* keep track of our throwables */
ArrayObject* exceptionArray = dvmGetMethodThrows(allMethods[i]);
if (exceptionArray != NULL) {
Object** contents;
contents = (Object**) throwLists->contents;
contents[outCount] = (Object*) exceptionArray;
dvmReleaseTrackedAlloc((Object*) exceptionArray, NULL);
}
outMethods[outCount++] = allMethods[i];
allMethods[i] = NULL;
}
}
/*
* Check for stragglers. If we find any, throw an exception.
*/
for (i = 0; i < allCount; i++) {
if (allMethods[i] != NULL) {
LOGV("BAD DUPE: %d %s.%s\n", i,
allMethods[i]->clazz->descriptor, allMethods[i]->name);
dvmThrowException("Ljava/lang/IllegalArgumentException;",
"incompatible return types in proxied interfaces");
return -1;
}
}
return outCount;
}
/*
* Classes can declare to throw multiple exceptions in a hierarchy, e.g.
* IOException and FileNotFoundException. Since we're only interested in
* knowing the set that can be thrown without requiring an extra wrapper,
* we can remove anything that is a subclass of something else in the list.
*
* The "mix" step we do next reduces things toward the most-derived class,
* so it's important that we start with the least-derived classes.
*/
static void reduceExceptionClassList(ArrayObject* exceptionArray)
{
const ClassObject** classes = (const ClassObject**)exceptionArray->contents;
int len = exceptionArray->length;
int i, j;
/*
* Consider all pairs of classes. If one is the subclass of the other,
* null out the subclass.
*/
for (i = 0; i < len-1; i++) {
if (classes[i] == NULL)
continue;
for (j = i + 1; j < len; j++) {
if (classes[j] == NULL)
continue;
if (dvmInstanceof(classes[i], classes[j])) {
classes[i] = NULL;
break; /* no more comparisons against classes[i] */
} else if (dvmInstanceof(classes[j], classes[i])) {
classes[j] = NULL;
}
}
}
}
/*
* Create a local array with a copy of the throwable classes declared by
* "method". If no throws are declared, "*pSet" will be NULL.
*
* Returns "false" on allocation failure.
*/
static bool createExceptionClassList(const Method* method, PointerSet** pThrows)
{
ArrayObject* exceptionArray = NULL;
bool result = false;
exceptionArray = dvmGetMethodThrows(method);
if (exceptionArray != NULL && exceptionArray->length > 0) {
/* reduce list, nulling out redundant entries */
reduceExceptionClassList(exceptionArray);
*pThrows = dvmPointerSetAlloc(exceptionArray->length);
if (*pThrows == NULL)
goto bail;
const ClassObject** contents;
int i;
contents = (const ClassObject**) exceptionArray->contents;
for (i = 0; i < (int) exceptionArray->length; i++) {
if (contents[i] != NULL)
dvmPointerSetAddEntry(*pThrows, contents[i]);
}
} else {
*pThrows = NULL;
}
result = true;
bail:
dvmReleaseTrackedAlloc((Object*) exceptionArray, NULL);
return result;
}
/*
* We need to compute the intersection of the arguments, i.e. remove
* anything from "throws" that isn't in the method's list of throws.
*
* If one class is a subclass of another, we want to keep just the subclass,
* moving toward the most-restrictive set.
*
* We assume these are all classes, and don't try to filter out interfaces.
*/
static void updateExceptionClassList(const Method* method, PointerSet* throws)
{
int setSize = dvmPointerSetGetCount(throws);
if (setSize == 0)
return;
ArrayObject* exceptionArray = dvmGetMethodThrows(method);
if (exceptionArray == NULL) {
/* nothing declared, so intersection is empty */
dvmPointerSetClear(throws);
return;
}
/* reduce list, nulling out redundant entries */
reduceExceptionClassList(exceptionArray);
int mixLen = dvmPointerSetGetCount(throws);
const ClassObject* mixSet[mixLen];
int declLen = exceptionArray->length;
const ClassObject** declSet = (const ClassObject**)exceptionArray->contents;
int i, j;
/* grab a local copy to work on */
for (i = 0; i < mixLen; i++) {
mixSet[i] = dvmPointerSetGetEntry(throws, i);
}
for (i = 0; i < mixLen; i++) {
for (j = 0; j < declLen; j++) {
if (declSet[j] == NULL)
continue;
if (mixSet[i] == declSet[j]) {
/* match, keep this one */
break;
} else if (dvmInstanceof(mixSet[i], declSet[j])) {
/* mix is a subclass of a declared throwable, keep it */
break;
} else if (dvmInstanceof(declSet[j], mixSet[i])) {
/* mix is a superclass, replace it */
mixSet[i] = declSet[j];
break;
}
}
if (j == declLen) {
/* no match, remove entry by nulling it out */
mixSet[i] = NULL;
}
}
/* copy results back out; this eliminates duplicates as we go */
dvmPointerSetClear(throws);
for (i = 0; i < mixLen; i++) {
if (mixSet[i] != NULL)
dvmPointerSetAddEntry(throws, mixSet[i]);
}
dvmReleaseTrackedAlloc((Object*) exceptionArray, NULL);
}
/*
* Check to see if the return types are compatible.
*
* If the return type is primitive or void, it must match exactly.
*
* If not, the type in "subMethod" must be assignable to the type in
* "baseMethod".
*/
static bool returnTypesAreCompatible(Method* subMethod, Method* baseMethod)
{
const char* baseSig = dexProtoGetReturnType(&baseMethod->prototype);
const char* subSig = dexProtoGetReturnType(&subMethod->prototype);
ClassObject* baseClass;
ClassObject* subClass;
if (baseSig[1] == '\0' || subSig[1] == '\0') {
/* at least one is primitive type */
return (baseSig[0] == subSig[0] && baseSig[1] == subSig[1]);
}
baseClass = dvmFindClass(baseSig, baseMethod->clazz->classLoader);
subClass = dvmFindClass(subSig, subMethod->clazz->classLoader);
bool result = dvmInstanceof(subClass, baseClass);
return result;
}
/*
* Create a constructor for our Proxy class. The constructor takes one
* argument, a java.lang.reflect.InvocationHandler.
*/
static void createConstructor(ClassObject* clazz, Method* meth)
{
meth->clazz = clazz;
meth->accessFlags = ACC_PUBLIC | ACC_NATIVE;
meth->name = "<init>";
meth->prototype =
gDvm.methJavaLangReflectProxy_constructorPrototype->prototype;
meth->shorty =
gDvm.methJavaLangReflectProxy_constructorPrototype->shorty;
// no pDexCode or pDexMethod
int argsSize = dvmComputeMethodArgsSize(meth) + 1;
meth->registersSize = meth->insSize = argsSize;
meth->nativeFunc = proxyConstructor;
}
/*
* Create a method in our Proxy class with the name and signature of
* the interface method it implements.
*/
static void createHandlerMethod(ClassObject* clazz, Method* dstMeth,
const Method* srcMeth)
{
dstMeth->clazz = clazz;
dstMeth->insns = (u2*) srcMeth;
dstMeth->accessFlags = ACC_PUBLIC | ACC_NATIVE;
dstMeth->name = srcMeth->name;
dstMeth->prototype = srcMeth->prototype;
dstMeth->shorty = srcMeth->shorty;
// no pDexCode or pDexMethod
int argsSize = dvmComputeMethodArgsSize(dstMeth) + 1;
dstMeth->registersSize = dstMeth->insSize = argsSize;
dstMeth->nativeFunc = proxyInvoker;
}
/*
* Return a new Object[] array with the contents of "args". We determine
* the number and types of values in "args" based on the method signature.
* Primitive types are boxed.
*
* Returns NULL if the method takes no arguments.
*
* The caller must call dvmReleaseTrackedAlloc() on the return value.
*
* On failure, returns with an appropriate exception raised.
*/
static ArrayObject* boxMethodArgs(const Method* method, const u4* args)
{
const char* desc = &method->shorty[1]; // [0] is the return type.
ArrayObject* argArray = NULL;
int argCount;
Object** argObjects;
bool failed = true;
/* count args */
argCount = dexProtoGetParameterCount(&method->prototype);
/* allocate storage */
argArray = dvmAllocArray(gDvm.classJavaLangObjectArray, argCount,
kObjectArrayRefWidth, ALLOC_DEFAULT);
if (argArray == NULL)
goto bail;
argObjects = (Object**) argArray->contents;
/*
* Fill in the array.
*/
int srcIndex = 0;
argCount = 0;
while (*desc != '\0') {
char descChar = *(desc++);
JValue value;
switch (descChar) {
case 'Z':
case 'C':
case 'F':
case 'B':
case 'S':
case 'I':
value.i = args[srcIndex++];
argObjects[argCount] = (Object*) dvmWrapPrimitive(value,
dvmFindPrimitiveClass(descChar));
/* argObjects is tracked, don't need to hold this too */
dvmReleaseTrackedAlloc(argObjects[argCount], NULL);
argCount++;
break;
case 'D':
case 'J':
value.j = dvmGetArgLong(args, srcIndex);
srcIndex += 2;
argObjects[argCount] = (Object*) dvmWrapPrimitive(value,
dvmFindPrimitiveClass(descChar));
dvmReleaseTrackedAlloc(argObjects[argCount], NULL);
argCount++;
break;
case '[':
case 'L':
argObjects[argCount++] = (Object*) args[srcIndex++];
break;
}
}
failed = false;
bail:
if (failed) {
dvmReleaseTrackedAlloc((Object*)argArray, NULL);
argArray = NULL;
}
return argArray;
}
/*
* This is the constructor for a generated proxy object. All we need to
* do is stuff "handler" into "h".
*/
static void proxyConstructor(const u4* args, JValue* pResult,
const Method* method, Thread* self)
{
Object* obj = (Object*) args[0];
Object* handler = (Object*) args[1];
dvmSetFieldObject(obj, gDvm.offJavaLangReflectProxy_h, handler);
}
/*
* This is the common message body for proxy methods.
*
* The method we're calling looks like:
* public Object invoke(Object proxy, Method method, Object[] args)
*
* This means we have to create a Method object, box our arguments into
* a new Object[] array, make the call, and unbox the return value if
* necessary.
*/
static void proxyInvoker(const u4* args, JValue* pResult,
const Method* method, Thread* self)
{
Object* thisObj = (Object*) args[0];
Object* methodObj = NULL;
ArrayObject* argArray = NULL;
Object* handler;
Method* invoke;
ClassObject* returnType;
int hOffset;
JValue invokeResult;
/*
* Retrieve handler object for this proxy instance. The field is
* defined in the superclass (Proxy).
*/
handler = dvmGetFieldObject(thisObj, gDvm.offJavaLangReflectProxy_h);
/*
* Find the invoke() method, looking in "this"s class. (Because we
* start here we don't have to convert it to a vtable index and then
* index into this' vtable.)
*/
invoke = dvmFindVirtualMethodHierByDescriptor(handler->clazz, "invoke",
"(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;");
if (invoke == NULL) {
LOGE("Unable to find invoke()\n");
dvmAbort();
}
LOGV("invoke: %s.%s, this=%p, handler=%s\n",
method->clazz->descriptor, method->name,
thisObj, handler->clazz->descriptor);
/*
* Create a java.lang.reflect.Method object for this method.
*
* We don't want to use "method", because that's the concrete
* implementation in the proxy class. We want the abstract Method
* from the declaring interface. We have a pointer to it tucked
* away in the "insns" field.
*
* TODO: this could be cached for performance.
*/
methodObj = dvmCreateReflectMethodObject((Method*) method->insns);
if (methodObj == NULL) {
assert(dvmCheckException(self));
goto bail;
}
/*
* Determine the return type from the signature.
*
* TODO: this could be cached for performance.
*/
returnType = dvmGetBoxedReturnType(method);
if (returnType == NULL) {
char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
LOGE("Could not determine return type for '%s'\n", desc);
free(desc);
assert(dvmCheckException(self));
goto bail;
}
LOGV(" return type will be %s\n", returnType->descriptor);
/*
* Convert "args" array into Object[] array, using the method
* signature to determine types. If the method takes no arguments,
* we must pass null.
*/
argArray = boxMethodArgs(method, args+1);
if (dvmCheckException(self))
goto bail;
/*
* Call h.invoke(proxy, method, args).
*
* We don't need to repackage exceptions, so if one has been thrown
* just jump to the end.
*/
dvmCallMethod(self, invoke, handler, &invokeResult,
thisObj, methodObj, argArray);
if (dvmCheckException(self)) {
Object* excep = dvmGetException(self);
if (mustWrapException(method, excep)) {
/* wrap with UndeclaredThrowableException */
dvmWrapException("Ljava/lang/reflect/UndeclaredThrowableException;");
}
goto bail;
}
/*
* Unbox the return value. If it's the wrong type, throw a
* ClassCastException. If it's a null pointer and we need a
* primitive type, throw a NullPointerException.
*/
if (returnType->primitiveType == PRIM_VOID) {
LOGVV("+++ ignoring return to void\n");
} else if (invokeResult.l == NULL) {
if (dvmIsPrimitiveClass(returnType)) {
dvmThrowException("Ljava/lang/NullPointerException;",
"null result when primitive expected");
goto bail;
}
pResult->l = NULL;
} else {
if (!dvmUnwrapPrimitive(invokeResult.l, returnType, pResult)) {
dvmThrowExceptionWithClassMessage("Ljava/lang/ClassCastException;",
((Object*)invokeResult.l)->clazz->descriptor);
goto bail;
}
}
bail:
dvmReleaseTrackedAlloc(methodObj, self);
dvmReleaseTrackedAlloc((Object*)argArray, self);
}
/*
* Determine if it's okay for this method to throw this exception. If
* an unchecked exception was thrown we immediately return false. If
* checked, we have to ensure that this method and all of its duplicates
* have declared that they throw it.
*/
static bool mustWrapException(const Method* method, const Object* throwable)
{
const ArrayObject* throws;
const ArrayObject* methodThrows;
const Object** contents;
const ClassObject** classes;
if (!dvmIsCheckedException(throwable))
return false;
const StaticField* sfield = &method->clazz->sfields[kThrowsField];
throws = (ArrayObject*) dvmGetStaticFieldObject(sfield);
int methodIndex = method - method->clazz->virtualMethods;
assert(methodIndex >= 0 && methodIndex < method->clazz->virtualMethodCount);
contents = (const Object**) throws->contents;
methodThrows = (ArrayObject*) contents[methodIndex];
if (methodThrows == NULL) {
/* no throws declared, must wrap all checked exceptions */
//printf("+++ methodThrows[%d] is null, wrapping all\n", methodIndex);
return true;
}
int throwCount = methodThrows->length;
classes = (const ClassObject**) methodThrows->contents;
int i;
//printf("%s.%s list:\n", method->clazz->descriptor, method->name);
//for (i = 0; i < throwCount; i++)
// printf(" %d: %s\n", i, classes[i]->descriptor);
for (i = 0; i < throwCount; i++) {
if (dvmInstanceof(throwable->clazz, classes[i])) {
/* this was declared, okay to throw */
return false;
}
}
/* no match in declared throws */
return true;
}