summaryrefslogtreecommitdiffstats
path: root/vm/reflect/Proxy.cpp
diff options
context:
space:
mode:
authorCarl Shapiro <cshapiro@google.com>2011-04-13 15:15:55 -0700
committerCarl Shapiro <cshapiro@google.com>2011-04-13 15:15:55 -0700
commit056a24a06b2d166c8e8f380488253964db6d7dcd (patch)
treecc380de61a4e525e29a375b9da549ed95d5ddb7f /vm/reflect/Proxy.cpp
parentdb0c9549818d9f6e508d26e45ff9d886802aa1da (diff)
downloadandroid_dalvik-056a24a06b2d166c8e8f380488253964db6d7dcd.tar.gz
android_dalvik-056a24a06b2d166c8e8f380488253964db6d7dcd.tar.bz2
android_dalvik-056a24a06b2d166c8e8f380488253964db6d7dcd.zip
Move the reflection code into C++.
Smart pointers would make dvmGenerateProxyClass cleaner. This code should be revisited once those interfaces come into existence. Change-Id: If25abb87e22d658ad88f997324c35703b23f623c
Diffstat (limited to 'vm/reflect/Proxy.cpp')
-rw-r--r--vm/reflect/Proxy.cpp1030
1 files changed, 1030 insertions, 0 deletions
diff --git a/vm/reflect/Proxy.cpp b/vm/reflect/Proxy.cpp
new file mode 100644
index 000000000..b8284bf9a
--- /dev/null
+++ b/vm/reflect/Proxy.cpp
@@ -0,0 +1,1030 @@
+/*
+ * 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
+#define kProxySFieldCount 1
+
+/*
+ * 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;
+ ArrayObject* throws = NULL;
+
+ char* nameStr = dvmCreateCstrFromString(str);
+ if (nameStr == NULL) {
+ dvmThrowIllegalArgumentException("missing name");
+ return NULL;
+ }
+
+ 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).
+ */
+
+ /*
+ * Allocate storage for the class object and set some basic fields.
+ */
+ size_t newClassSize =
+ sizeof(ClassObject) + kProxySFieldCount * sizeof(StaticField);
+ ClassObject* newClass =
+ (ClassObject*) dvmMalloc(newClassSize, ALLOC_DEFAULT);
+ if (newClass == NULL)
+ goto bail;
+ DVM_OBJECT_INIT(&newClass->obj, gDvm.classJavaLangClass);
+ dvmSetClassSerialNumber(newClass);
+ newClass->descriptorAlloc = dvmNameToDescriptor(nameStr);
+ newClass->descriptor = newClass->descriptorAlloc;
+ newClass->accessFlags = ACC_PUBLIC | ACC_FINAL;
+ dvmSetFieldObject((Object *)newClass,
+ offsetof(ClassObject, super),
+ (Object *)gDvm.classJavaLangReflectProxy);
+ newClass->primitiveType = PRIM_NOT;
+ dvmSetFieldObject((Object *)newClass,
+ offsetof(ClassObject, classLoader),
+ (Object *)loader);
+
+ /*
+ * 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.
+ */
+ {
+ /*
+ * Generate a temporary list of virtual methods.
+ */
+ int methodCount;
+ Method **methods;
+ if (!gatherMethods(interfaces, &methods, &throws, &methodCount)) {
+ goto bail;
+ }
+ newClass->virtualMethodCount = methodCount;
+ size_t virtualMethodsSize = methodCount * sizeof(Method);
+ newClass->virtualMethods =
+ (Method*)dvmLinearAlloc(newClass->classLoader, virtualMethodsSize);
+ for (int i = 0; i < newClass->virtualMethodCount; i++) {
+ createHandlerMethod(newClass, &newClass->virtualMethods[i], methods[i]);
+ }
+ free(methods);
+ dvmLinearReadOnly(newClass->classLoader, newClass->virtualMethods);
+ }
+
+ /*
+ * Add interface list.
+ */
+ {
+ size_t interfaceCount = interfaces->length;
+ ClassObject** ifArray = (ClassObject**)(void*)interfaces->contents;
+ newClass->interfaceCount = interfaceCount;
+ size_t interfacesSize = sizeof(ClassObject*) * interfaceCount;
+ newClass->interfaces =
+ (ClassObject**)dvmLinearAlloc(newClass->classLoader, interfacesSize);
+ for (size_t 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.
+ */
+ assert(kProxySFieldCount == 1);
+ newClass->sfieldCount = kProxySFieldCount;
+ {
+ 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. This class didn't come out of a DEX file
+ * so we didn't tuck any indexes into the class object. We can
+ * advance to LOADED state immediately.
+ */
+ newClass->status = CLASS_LOADED;
+ if (!dvmLinkClass(newClass)) {
+ 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);
+ if (result != 0) {
+ /* must free innards explicitly if we didn't finish linking */
+ dvmFreeClassInnards(newClass);
+ newClass = NULL;
+ if (!dvmCheckException(dvmThreadSelf())) {
+ /* throw something */
+ dvmThrowRuntimeException(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**)(void*)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**)(void*)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)
+{
+ 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**)(void*)throwArray->contents;
+ for (ent = 0; ent < commonCount; ent++) {
+ contents[ent] = (Object*)
+ dvmPointerSetGetEntry(commonThrows, ent);
+ }
+
+ /* add it to the array of arrays */
+ contents = (Object**)(void*)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**)(void*)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);
+ dvmThrowIllegalArgumentException(
+ "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**)(void*)exceptionArray->contents;
+
+ /*
+ * Consider all pairs of classes. If one is the subclass of the other,
+ * null out the subclass.
+ */
+ size_t len = exceptionArray->length;
+ for (size_t i = 0; i < len - 1; i++) {
+ if (classes[i] == NULL)
+ continue;
+ for (size_t 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;
+
+ contents = (const ClassObject**)(void*)exceptionArray->contents;
+ for (size_t i = 0; i < 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);
+
+ size_t mixLen = dvmPointerSetGetCount(throws);
+ const ClassObject* mixSet[mixLen];
+
+ size_t declLen = exceptionArray->length;
+ const ClassObject** declSet = (const ClassObject**)(void*)exceptionArray->contents;
+
+ /* grab a local copy to work on */
+ for (size_t i = 0; i < mixLen; i++) {
+ mixSet[i] = (ClassObject*)dvmPointerSetGetEntry(throws, i);
+ }
+
+ for (size_t i = 0; i < mixLen; i++) {
+ size_t j;
+ 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 (size_t 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)
+{
+ /*
+ * The constructor signatures (->prototype and ->shorty) need to
+ * be cloned from a method in a "real" DEX file. We declared the
+ * otherwise unused method Proxy.constructorPrototype() just for
+ * this purpose.
+ */
+
+ 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.
+
+ /* count args */
+ size_t argCount = dexProtoGetParameterCount(&method->prototype);
+
+ /* allocate storage */
+ ArrayObject* argArray = dvmAllocArray(gDvm.classJavaLangObjectArray,
+ argCount, kObjectArrayRefWidth, ALLOC_DEFAULT);
+ if (argArray == NULL)
+ return NULL;
+ Object** argObjects = (Object**)(void*)argArray->contents;
+
+ /*
+ * Fill in the array.
+ */
+
+ size_t srcIndex = 0;
+ size_t dstIndex = 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[dstIndex] = (Object*) dvmBoxPrimitive(value,
+ dvmFindPrimitiveClass(descChar));
+ /* argObjects is tracked, don't need to hold this too */
+ dvmReleaseTrackedAlloc(argObjects[dstIndex], NULL);
+ dstIndex++;
+ break;
+ case 'D':
+ case 'J':
+ value.j = dvmGetArgLong(args, srcIndex);
+ srcIndex += 2;
+ argObjects[dstIndex] = (Object*) dvmBoxPrimitive(value,
+ dvmFindPrimitiveClass(descChar));
+ dvmReleaseTrackedAlloc(argObjects[dstIndex], NULL);
+ dstIndex++;
+ break;
+ case '[':
+ case 'L':
+ argObjects[dstIndex++] = (Object*) args[srcIndex++];
+ break;
+ }
+ }
+
+ 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;
+ 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.
+ *
+ * We're not adding invokeResult.l to the tracked allocation list, but
+ * since we're just unboxing it or returning it to interpreted code
+ * that shouldn't be a problem.
+ */
+ 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)) {
+ dvmThrowNullPointerException(
+ "null result when primitive expected");
+ goto bail;
+ }
+ pResult->l = NULL;
+ } else {
+ if (!dvmUnboxPrimitive((Object*)invokeResult.l, returnType, pResult)) {
+ dvmThrowClassCastException(((Object*)invokeResult.l)->clazz,
+ returnType);
+ 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)
+{
+ if (!dvmIsCheckedException(throwable))
+ return false;
+
+ const StaticField* sfield = &method->clazz->sfields[kThrowsField];
+ const ArrayObject* throws = (ArrayObject*) dvmGetStaticFieldObject(sfield);
+
+ int methodIndex = method - method->clazz->virtualMethods;
+ assert(methodIndex >= 0 && methodIndex < method->clazz->virtualMethodCount);
+
+ const Object** contents = (const Object**)(void*)throws->contents;
+ const ArrayObject* methodThrows = (ArrayObject*) contents[methodIndex];
+
+ if (methodThrows == NULL) {
+ /* no throws declared, must wrap all checked exceptions */
+ return true;
+ }
+
+ size_t throwCount = methodThrows->length;
+ const ClassObject** classes =
+ (const ClassObject**)(void*)methodThrows->contents;
+
+ for (size_t 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;
+}