/* * 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. */ /* * Resolve classes, methods, fields, and strings. * * According to the VM spec (v2 5.5), classes may be initialized by use * of the "new", "getstatic", "putstatic", or "invokestatic" instructions. * If we are resolving a static method or static field, we make the * initialization check here. * * (NOTE: the verifier has its own resolve functions, which can be invoked * if a class isn't pre-verified. Those functions must not update the * "resolved stuff" tables for static fields and methods, because they do * not perform initialization.) */ #include "Dalvik.h" #include <stdlib.h> /* * Find the class corresponding to "classIdx", which maps to a class name * string. It might be in the same DEX file as "referrer", in a different * DEX file, generated by a class loader, or generated by the VM (e.g. * array classes). * * Because the DexTypeId is associated with the referring class' DEX file, * we may have to resolve the same class more than once if it's referred * to from classes in multiple DEX files. This is a necessary property for * DEX files associated with different class loaders. * * We cache a copy of the lookup in the DexFile's "resolved class" table, * so future references to "classIdx" are faster. * * Note that "referrer" may be in the process of being linked. * * Traditional VMs might do access checks here, but in Dalvik the class * "constant pool" is shared between all classes in the DEX file. We rely * on the verifier to do the checks for us. * * Does not initialize the class. * * "fromUnverifiedConstant" should only be set if this call is the direct * result of executing a "const-class" or "instance-of" instruction, which * use class constants not resolved by the bytecode verifier. * * Returns NULL with an exception raised on failure. */ ClassObject* dvmResolveClass(const ClassObject* referrer, u4 classIdx, bool fromUnverifiedConstant) { DvmDex* pDvmDex = referrer->pDvmDex; ClassObject* resClass; const char* className; /* * Check the table first -- this gets called from the other "resolve" * methods. */ resClass = dvmDexGetResolvedClass(pDvmDex, classIdx); if (resClass != NULL) return resClass; LOGVV("--- resolving class %u (referrer=%s cl=%p)\n", classIdx, referrer->descriptor, referrer->classLoader); /* * Class hasn't been loaded yet, or is in the process of being loaded * and initialized now. Try to get a copy. If we find one, put the * pointer in the DexTypeId. There isn't a race condition here -- * 32-bit writes are guaranteed atomic on all target platforms. Worst * case we have two threads storing the same value. * * If this is an array class, we'll generate it here. */ className = dexStringByTypeIdx(pDvmDex->pDexFile, classIdx); if (className[0] != '\0' && className[1] == '\0') { /* primitive type */ resClass = dvmFindPrimitiveClass(className[0]); } else { resClass = dvmFindClassNoInit(className, referrer->classLoader); } if (resClass != NULL) { /* * If the referrer was pre-verified, the resolved class must come * from the same DEX or from a bootstrap class. The pre-verifier * makes assumptions that could be invalidated by a wacky class * loader. (See the notes at the top of oo/Class.c.) * * The verifier does *not* fail a class for using a const-class * or instance-of instruction referring to an unresolveable class, * because the result of the instruction is simply a Class object * or boolean -- there's no need to resolve the class object during * verification. Instance field and virtual method accesses can * break dangerously if we get the wrong class, but const-class and * instance-of are only interesting at execution time. So, if we * we got here as part of executing one of the "unverified class" * instructions, we skip the additional check. * * Ditto for class references from annotations and exception * handler lists. */ if (!fromUnverifiedConstant && IS_CLASS_FLAG_SET(referrer, CLASS_ISPREVERIFIED)) { ClassObject* resClassCheck = resClass; if (dvmIsArrayClass(resClassCheck)) resClassCheck = resClassCheck->elementClass; if (referrer->pDvmDex != resClassCheck->pDvmDex && resClassCheck->classLoader != NULL) { LOGW("Class resolved by unexpected DEX:" " %s(%p):%p ref [%s] %s(%p):%p\n", referrer->descriptor, referrer->classLoader, referrer->pDvmDex, resClass->descriptor, resClassCheck->descriptor, resClassCheck->classLoader, resClassCheck->pDvmDex); LOGW("(%s had used a different %s during pre-verification)\n", referrer->descriptor, resClass->descriptor); dvmThrowException("Ljava/lang/IllegalAccessError;", "Class ref in pre-verified class resolved to unexpected " "implementation"); return NULL; } } LOGVV("##### +ResolveClass(%s): referrer=%s dex=%p ldr=%p ref=%d\n", resClass->descriptor, referrer->descriptor, referrer->pDvmDex, referrer->classLoader, classIdx); /* * Add what we found to the list so we can skip the class search * next time through. * * TODO: should we be doing this when fromUnverifiedConstant==true? * (see comments at top of oo/Class.c) */ dvmDexSetResolvedClass(pDvmDex, classIdx, resClass); } else { /* not found, exception should be raised */ LOGVV("Class not found: %s\n", dexStringByTypeIdx(pDvmDex->pDexFile, classIdx)); assert(dvmCheckException(dvmThreadSelf())); } return resClass; } /* * Find the method corresponding to "methodRef". * * We use "referrer" to find the DexFile with the constant pool that * "methodRef" is an index into. We also use its class loader. The method * being resolved may very well be in a different DEX file. * * If this is a static method, we ensure that the method's class is * initialized. */ Method* dvmResolveMethod(const ClassObject* referrer, u4 methodIdx, MethodType methodType) { DvmDex* pDvmDex = referrer->pDvmDex; ClassObject* resClass; const DexMethodId* pMethodId; Method* resMethod; assert(methodType != METHOD_INTERFACE); LOGVV("--- resolving method %u (referrer=%s)\n", methodIdx, referrer->descriptor); pMethodId = dexGetMethodId(pDvmDex->pDexFile, methodIdx); resClass = dvmResolveClass(referrer, pMethodId->classIdx, false); if (resClass == NULL) { /* can't find the class that the method is a part of */ assert(dvmCheckException(dvmThreadSelf())); return NULL; } if (dvmIsInterfaceClass(resClass)) { /* method is part of an interface */ dvmThrowExceptionWithClassMessage( "Ljava/lang/IncompatibleClassChangeError;", resClass->descriptor); return NULL; } const char* name = dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx); DexProto proto; dexProtoSetFromMethodId(&proto, pDvmDex->pDexFile, pMethodId); /* * We need to chase up the class hierarchy to find methods defined * in super-classes. (We only want to check the current class * if we're looking for a constructor; since DIRECT calls are only * for constructors and private methods, we don't want to walk up.) */ if (methodType == METHOD_DIRECT) { resMethod = dvmFindDirectMethod(resClass, name, &proto); } else if (methodType == METHOD_STATIC) { resMethod = dvmFindDirectMethodHier(resClass, name, &proto); } else { resMethod = dvmFindVirtualMethodHier(resClass, name, &proto); } if (resMethod == NULL) { dvmThrowException("Ljava/lang/NoSuchMethodError;", name); return NULL; } LOGVV("--- found method %d (%s.%s)\n", methodIdx, resClass->descriptor, resMethod->name); /* see if this is a pure-abstract method */ if (dvmIsAbstractMethod(resMethod) && !dvmIsAbstractClass(resClass)) { dvmThrowException("Ljava/lang/AbstractMethodError;", name); return NULL; } /* * If we're the first to resolve this class, we need to initialize * it now. Only necessary for METHOD_STATIC. */ if (methodType == METHOD_STATIC) { if (!dvmIsClassInitialized(resMethod->clazz) && !dvmInitClass(resMethod->clazz)) { assert(dvmCheckException(dvmThreadSelf())); return NULL; } else { assert(!dvmCheckException(dvmThreadSelf())); } } else { /* * Edge case: if the <clinit> for a class creates an instance * of itself, we will call <init> on a class that is still being * initialized by us. */ assert(dvmIsClassInitialized(resMethod->clazz) || dvmIsClassInitializing(resMethod->clazz)); } /* * If the class has been initialized, add a pointer to our data structure * so we don't have to jump through the hoops again. If this is a * static method and the defining class is still initializing (i.e. this * thread is executing <clinit>), don't do the store, otherwise other * threads could call the method without waiting for class init to finish. */ if (methodType == METHOD_STATIC && !dvmIsClassInitialized(resMethod->clazz)) { LOGVV("--- not caching resolved method %s.%s (class init=%d/%d)\n", resMethod->clazz->descriptor, resMethod->name, dvmIsClassInitializing(resMethod->clazz), dvmIsClassInitialized(resMethod->clazz)); } else { dvmDexSetResolvedMethod(pDvmDex, methodIdx, resMethod); } return resMethod; } /* * Resolve an interface method reference. * * Returns NULL with an exception raised on failure. */ Method* dvmResolveInterfaceMethod(const ClassObject* referrer, u4 methodIdx) { DvmDex* pDvmDex = referrer->pDvmDex; ClassObject* resClass; const DexMethodId* pMethodId; Method* resMethod; int i; LOGVV("--- resolving interface method %d (referrer=%s)\n", methodIdx, referrer->descriptor); pMethodId = dexGetMethodId(pDvmDex->pDexFile, methodIdx); resClass = dvmResolveClass(referrer, pMethodId->classIdx, false); if (resClass == NULL) { /* can't find the class that the method is a part of */ assert(dvmCheckException(dvmThreadSelf())); return NULL; } if (!dvmIsInterfaceClass(resClass)) { /* whoops */ dvmThrowExceptionWithClassMessage( "Ljava/lang/IncompatibleClassChangeError;", resClass->descriptor); return NULL; } /* * This is the first time the method has been resolved. Set it in our * resolved-method structure. It always resolves to the same thing, * so looking it up and storing it doesn't create a race condition. * * If we scan into the interface's superclass -- which is always * java/lang/Object -- we will catch things like: * interface I ... * I myobj = (something that implements I) * myobj.hashCode() * However, the Method->methodIndex will be an offset into clazz->vtable, * rather than an offset into clazz->iftable. The invoke-interface * code can test to see if the method returned is abstract or concrete, * and use methodIndex accordingly. I'm not doing this yet because * (a) we waste time in an unusual case, and (b) we're probably going * to fix it in the DEX optimizer. * * We do need to scan the superinterfaces, in case we're invoking a * superinterface method on an interface reference. The class in the * DexTypeId is for the static type of the object, not the class in * which the method is first defined. We have the full, flattened * list in "iftable". */ const char* methodName = dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx); DexProto proto; dexProtoSetFromMethodId(&proto, pDvmDex->pDexFile, pMethodId); LOGVV("+++ looking for '%s' '%s' in resClass='%s'\n", methodName, methodSig, resClass->descriptor); resMethod = dvmFindVirtualMethod(resClass, methodName, &proto); if (resMethod == NULL) { LOGVV("+++ did not resolve immediately\n"); for (i = 0; i < resClass->iftableCount; i++) { resMethod = dvmFindVirtualMethod(resClass->iftable[i].clazz, methodName, &proto); if (resMethod != NULL) break; } if (resMethod == NULL) { dvmThrowException("Ljava/lang/NoSuchMethodError;", methodName); return NULL; } } else { LOGVV("+++ resolved immediately: %s (%s %d)\n", resMethod->name, resMethod->clazz->descriptor, (u4) resMethod->methodIndex); } LOGVV("--- found interface method %d (%s.%s)\n", methodIdx, resClass->descriptor, resMethod->name); /* we're expecting this to be abstract */ assert(dvmIsAbstractMethod(resMethod)); /* interface methods are always public; no need to check access */ /* * The interface class *may* be initialized. According to VM spec * v2 2.17.4, the interfaces a class refers to "need not" be initialized * when the class is initialized. * * It isn't necessary for an interface class to be initialized before * we resolve methods on that interface. * * We choose not to do the initialization now. */ //assert(dvmIsClassInitialized(resMethod->clazz)); /* * Add a pointer to our data structure so we don't have to jump * through the hoops again. * * As noted above, no need to worry about whether the interface that * defines the method has been or is currently executing <clinit>. */ dvmDexSetResolvedMethod(pDvmDex, methodIdx, resMethod); return resMethod; } /* * Resolve an instance field reference. * * Returns NULL and throws an exception on error (no such field, illegal * access). */ InstField* dvmResolveInstField(const ClassObject* referrer, u4 ifieldIdx) { DvmDex* pDvmDex = referrer->pDvmDex; ClassObject* resClass; const DexFieldId* pFieldId; InstField* resField; LOGVV("--- resolving field %u (referrer=%s cl=%p)\n", ifieldIdx, referrer->descriptor, referrer->classLoader); pFieldId = dexGetFieldId(pDvmDex->pDexFile, ifieldIdx); /* * Find the field's class. */ resClass = dvmResolveClass(referrer, pFieldId->classIdx, false); if (resClass == NULL) { assert(dvmCheckException(dvmThreadSelf())); return NULL; } resField = dvmFindInstanceFieldHier(resClass, dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx), dexStringByTypeIdx(pDvmDex->pDexFile, pFieldId->typeIdx)); if (resField == NULL) { dvmThrowException("Ljava/lang/NoSuchFieldError;", dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx)); return NULL; } /* * Class must be initialized by now (unless verifier is buggy). We * could still be in the process of initializing it if the field * access is from a static initializer. */ assert(dvmIsClassInitialized(resField->field.clazz) || dvmIsClassInitializing(resField->field.clazz)); /* * The class is initialized (or initializing), the field has been * found. Add a pointer to our data structure so we don't have to * jump through the hoops again. * * Anything that uses the resolved table entry must have an instance * of the class, so any class init activity has already happened (or * been deliberately bypassed when <clinit> created an instance). * So it's always okay to update the table. */ dvmDexSetResolvedField(pDvmDex, ifieldIdx, (Field*)resField); LOGVV(" field %u is %s.%s\n", ifieldIdx, resField->field.clazz->descriptor, resField->field.name); return resField; } /* * Resolve a static field reference. The DexFile format doesn't distinguish * between static and instance field references, so the "resolved" pointer * in the Dex struct will have the wrong type. We trivially cast it here. * * Causes the field's class to be initialized. */ StaticField* dvmResolveStaticField(const ClassObject* referrer, u4 sfieldIdx) { DvmDex* pDvmDex = referrer->pDvmDex; ClassObject* resClass; const DexFieldId* pFieldId; StaticField* resField; pFieldId = dexGetFieldId(pDvmDex->pDexFile, sfieldIdx); /* * Find the field's class. */ resClass = dvmResolveClass(referrer, pFieldId->classIdx, false); if (resClass == NULL) { assert(dvmCheckException(dvmThreadSelf())); return NULL; } resField = dvmFindStaticFieldHier(resClass, dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx), dexStringByTypeIdx(pDvmDex->pDexFile, pFieldId->typeIdx)); if (resField == NULL) { dvmThrowException("Ljava/lang/NoSuchFieldError;", dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx)); return NULL; } /* * If we're the first to resolve the field in which this class resides, * we need to do it now. Note that, if the field was inherited from * a superclass, it is not necessarily the same as "resClass". */ if (!dvmIsClassInitialized(resField->field.clazz) && !dvmInitClass(resField->field.clazz)) { assert(dvmCheckException(dvmThreadSelf())); return NULL; } /* * If the class has been initialized, add a pointer to our data structure * so we don't have to jump through the hoops again. If it's still * initializing (i.e. this thread is executing <clinit>), don't do * the store, otherwise other threads could use the field without waiting * for class init to finish. */ if (dvmIsClassInitialized(resField->field.clazz)) { dvmDexSetResolvedField(pDvmDex, sfieldIdx, (Field*) resField); } else { LOGVV("--- not caching resolved field %s.%s (class init=%d/%d)\n", resField->field.clazz->descriptor, resField->field.name, dvmIsClassInitializing(resField->field.clazz), dvmIsClassInitialized(resField->field.clazz)); } return resField; } /* * Resolve a string reference. * * Finding the string is easy. We need to return a reference to a * java/lang/String object, not a bunch of characters, which means the * first time we get here we need to create an interned string. */ StringObject* dvmResolveString(const ClassObject* referrer, u4 stringIdx) { DvmDex* pDvmDex = referrer->pDvmDex; StringObject* strObj; StringObject* internStrObj; const char* utf8; u4 utf16Size; LOGVV("+++ resolving string, referrer is %s\n", referrer->descriptor); /* * Create a UTF-16 version so we can trivially compare it to what's * already interned. */ utf8 = dexStringAndSizeById(pDvmDex->pDexFile, stringIdx, &utf16Size); strObj = dvmCreateStringFromCstrAndLength(utf8, utf16Size); if (strObj == NULL) { /* ran out of space in GC heap? */ assert(dvmCheckException(dvmThreadSelf())); goto bail; } /* * Add it to the intern list. The return value is the one in the * intern list, which (due to race conditions) may or may not be * the one we just created. The intern list is synchronized, so * there will be only one "live" version. * * By requesting an immortal interned string, we guarantee that * the returned object will never be collected by the GC. * * A NULL return here indicates some sort of hashing failure. */ internStrObj = dvmLookupImmortalInternedString(strObj); dvmReleaseTrackedAlloc((Object*) strObj, NULL); strObj = internStrObj; if (strObj == NULL) { assert(dvmCheckException(dvmThreadSelf())); goto bail; } /* save a reference so we can go straight to the object next time */ dvmDexSetResolvedString(pDvmDex, stringIdx, strObj); bail: return strObj; } /* * For debugging: return a string representing the methodType. */ const char* dvmMethodTypeStr(MethodType methodType) { switch (methodType) { case METHOD_DIRECT: return "direct"; case METHOD_STATIC: return "static"; case METHOD_VIRTUAL: return "virtual"; case METHOD_INTERFACE: return "interface"; case METHOD_UNKNOWN: return "UNKNOWN"; } assert(false); return "BOGUS"; }