/* * 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 // fwd static bool gatherMethods(ArrayObject* interfaces, Method*** pMethods, int* pMethodCount); static bool addMethod(Method* meth, Method** methArray, int slot); 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); /* * 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; return true; } /* * Generate a proxy class with the specified name, interfaces, and loader. * "interfaces" is an array of class objects. * * The interpreted code has done all of the necessary checks, e.g. we know * that "interfaces" contains only interface classes. * * 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; 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 (barring duplicates) * - 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 * * 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; if (!gatherMethods(interfaces, &methods, &methodCount)) goto bail; /* * Allocate storage for the class object and set some basic fields. */ newClass = (ClassObject*) dvmMalloc(sizeof(*newClass), ALLOC_DEFAULT); if (newClass == NULL) return NULL; DVM_OBJECT_INIT(&newClass->obj, gDvm.unlinkedJavaLangClass); 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 newClass->hprofSerialNumber = 0; 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); /* * The class has one instance field, "protected InvocationHandler h", * which is filled in by the constructor. */ newClass->ifieldCount = 1; newClass->ifields = (InstField*) dvmLinearAlloc(newClass->classLoader, 1 * sizeof(InstField)); InstField* ifield = &newClass->ifields[0]; ifield->field.clazz = newClass; ifield->field.name = "h"; ifield->field.signature = "Ljava/lang/reflect/InvocationHandler;"; ifield->field.accessFlags = ACC_PROTECTED; ifield->byteOffset = -1; /* set later */ dvmLinearReadOnly(newClass->classLoader, newClass->ifields); /* * Everything is ready. See if the linker will lap it up. */ newClass->status = CLASS_LOADED; if (!dvmLinkClass(newClass, true)) { LOGI("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; dvmThrowException("Ljava/lang/RuntimeException;", NULL); } /* this allows the GC to free it */ 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. */ static bool gatherMethods(ArrayObject* interfaces, Method*** pMethods, int* pMethodCount) { ClassObject** classes; Method** methods; int numInterfaces, maxCount, actualCount; 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)); if (methods == NULL) return false; /* * First three entries are the java.lang.Object methods. */ ClassObject* obj = gDvm.classJavaLangObject; methods[0] = obj->vtable[gDvm.voffJavaLangObject_equals]; methods[1] = obj->vtable[gDvm.voffJavaLangObject_hashCode]; methods[2] = obj->vtable[gDvm.voffJavaLangObject_toString]; actualCount = 3; /* * Add the methods from each interface, in order, checking for * duplicates. This is O(n^2), but that should be okay here. */ classes = (ClassObject**) interfaces->contents; for (i = 0; i < numInterfaces; i++, classes++) { ClassObject* clazz = *classes; int j; for (j = 0; j < clazz->virtualMethodCount; j++) { if (addMethod(&clazz->virtualMethods[j], methods, actualCount)) actualCount++; } for (j = 0; j < clazz->iftableCount; j++) { ClassObject* iclass = clazz->iftable[j].clazz; int k; for (k = 0; k < iclass->virtualMethodCount; k++) { if (addMethod(&iclass->virtualMethods[k], methods, actualCount)) actualCount++; } } } //for (i = 0; i < actualCount; i++) { // LOGI(" %d: %s.%s\n", // i, methods[i]->clazz->descriptor, methods[i]->name); //} *pMethods = methods; *pMethodCount = actualCount; return true; } /* * Add a method to "methArray" if a matching method does not already * exist. Two methods match if they have the same name and signature. * * Returns "true" if the item was added, "false" if a duplicate was * found and the method was not added. */ static bool addMethod(Method* meth, Method** methArray, int slot) { int i; for (i = 0; i < slot; i++) { if (dvmCompareMethodNamesAndProtos(methArray[i], meth) == 0) { return false; } } methArray[slot] = meth; return true; } /* * 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 = ""; 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. */ static void proxyConstructor(const u4* args, JValue* pResult, const Method* method, Thread* self) { Object* obj = (Object*) args[0]; Object* handler = (Object*) args[1]; ClassObject* clazz = obj->clazz; int fieldOffset; fieldOffset = dvmFindFieldOffset(clazz, "h", "Ljava/lang/reflect/InvocationHandler;"); if (fieldOffset < 0) { LOGE("Unable to find 'h' in Proxy object\n"); //dvmDumpClass(clazz, kDumpClassFullDetail); dvmAbort(); // this should never happen } dvmSetFieldObject(obj, fieldOffset, 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. */ hOffset = dvmFindFieldOffset(thisObj->clazz, "h", "Ljava/lang/reflect/InvocationHandler;"); if (hOffset < 0) { LOGE("Unable to find 'h' in Proxy object\n"); dvmAbort(); } handler = dvmGetFieldObject(thisObj, hOffset); /* * 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)) 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); }