diff options
Diffstat (limited to 'vm/Exception.cpp')
-rw-r--r-- | vm/Exception.cpp | 1443 |
1 files changed, 1443 insertions, 0 deletions
diff --git a/vm/Exception.cpp b/vm/Exception.cpp new file mode 100644 index 0000000..5af48ba --- /dev/null +++ b/vm/Exception.cpp @@ -0,0 +1,1443 @@ +/* + * 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. + */ +/* + * Exception handling. + */ +#include "Dalvik.h" +#include "libdex/DexCatch.h" + +#include <stdlib.h> + +/* +Notes on Exception Handling + +We have one fairly sticky issue to deal with: creating the exception stack +trace. The trouble is that we need the current value of the program +counter for the method now being executed, but that's only held in a local +variable or hardware register in the main interpreter loop. + +The exception mechanism requires that the current stack trace be associated +with a Throwable at the time the Throwable is constructed. The construction +may or may not be associated with a throw. We have three situations to +consider: + + (1) A Throwable is created with a "new Throwable" statement in the + application code, for immediate or deferred use with a "throw" statement. + (2) The VM throws an exception from within the interpreter core, e.g. + after an integer divide-by-zero. + (3) The VM throws an exception from somewhere deeper down, e.g. while + trying to link a class. + +We need to have the current value for the PC, which means that for +situation (3) the interpreter loop must copy it to an externally-accessible +location before handling any opcode that could cause the VM to throw +an exception. We can't store it globally, because the various threads +would trample each other. We can't store it in the Thread structure, +because it'll get overwritten as soon as the Throwable constructor starts +executing. It needs to go on the stack, but our stack frames hold the +caller's *saved* PC, not the current PC. + +Situation #1 doesn't require special handling. Situation #2 could be dealt +with by passing the PC into the exception creation function. The trick +is to solve situation #3 in a way that adds minimal overhead to common +operations. Making it more costly to throw an exception is acceptable. + +There are a few ways to deal with this: + + (a) Change "savedPc" to "currentPc" in the stack frame. All of the + stack logic gets offset by one frame. The current PC is written + to the current stack frame when necessary. + (b) Write the current PC into the current stack frame, but without + replacing "savedPc". The JNI local refs pointer, which is only + used for native code, can be overloaded to save space. + (c) In dvmThrowException(), push an extra stack frame on, with the + current PC in it. The current PC is written into the Thread struct + when necessary, and copied out when the VM throws. + (d) Before doing something that might throw an exception, push a + temporary frame on with the saved PC in it. + +Solution (a) is the simplest, but breaks Dalvik's goal of mingling native +and interpreted stacks. + +Solution (b) retains the simplicity of (a) without rearranging the stack, +but now in some cases we're storing the PC twice, which feels wrong. + +Solution (c) usually works, because we push the saved PC onto the stack +before the Throwable construction can overwrite the copy in Thread. One +way solution (c) could break is: + - Interpreter saves the PC + - Execute some bytecode, which runs successfully (and alters the saved PC) + - Throw an exception before re-saving the PC (i.e in the same opcode) +This is a risk for anything that could cause <clinit> to execute, e.g. +executing a static method or accessing a static field. Attemping to access +a field that doesn't exist in a class that does exist might cause this. +It may be possible to simply bracket the dvmCallMethod*() functions to +save/restore it. + +Solution (d) incurs additional overhead, but may have other benefits (e.g. +it's easy to find the stack frames that should be removed before storage +in the Throwable). + +Current plan is option (b), because it's simple, fast, and doesn't change +the way the stack works. +*/ + +/* fwd */ +static bool initException(Object* exception, const char* msg, Object* cause, + Thread* self); + +void dvmThrowExceptionFmtV(ClassObject* exceptionClass, + const char* fmt, va_list args) +{ + char msgBuf[512]; + + vsnprintf(msgBuf, sizeof(msgBuf), fmt, args); + dvmThrowChainedException(exceptionClass, msgBuf, NULL); +} + +void dvmThrowChainedException(ClassObject* excepClass, const char* msg, + Object* cause) +{ + Thread* self = dvmThreadSelf(); + Object* exception; + + if (excepClass == NULL) { + /* + * The exception class was passed in as NULL. This might happen + * early on in VM initialization. There's nothing better to do + * than just log the message as an error and abort. + */ + LOGE("Fatal error: %s", msg); + dvmAbort(); + } + + /* make sure the exception is initialized */ + if (!dvmIsClassInitialized(excepClass) && !dvmInitClass(excepClass)) { + LOGE("ERROR: unable to initialize exception class '%s'", + excepClass->descriptor); + if (strcmp(excepClass->descriptor, "Ljava/lang/InternalError;") == 0) + dvmAbort(); + dvmThrowChainedException(gDvm.exInternalError, + "failed to init original exception class", cause); + return; + } + + exception = dvmAllocObject(excepClass, ALLOC_DEFAULT); + if (exception == NULL) { + /* + * We're in a lot of trouble. We might be in the process of + * throwing an out-of-memory exception, in which case the + * pre-allocated object will have been thrown when our object alloc + * failed. So long as there's an exception raised, return and + * allow the system to try to recover. If not, something is broken + * and we need to bail out. + */ + if (dvmCheckException(self)) + goto bail; + LOGE("FATAL: unable to allocate exception '%s' '%s'", + excepClass->descriptor, msg != NULL ? msg : "(no msg)"); + dvmAbort(); + } + + /* + * Init the exception. + */ + if (gDvm.optimizing) { + /* need the exception object, but can't invoke interpreted code */ + LOGV("Skipping init of exception %s '%s'", + excepClass->descriptor, msg); + } else { + assert(excepClass == exception->clazz); + if (!initException(exception, msg, cause, self)) { + /* + * Whoops. If we can't initialize the exception, we can't use + * it. If there's an exception already set, the constructor + * probably threw an OutOfMemoryError. + */ + if (!dvmCheckException(self)) { + /* + * We're required to throw something, so we just + * throw the pre-constructed internal error. + */ + self->exception = gDvm.internalErrorObj; + } + goto bail; + } + } + + self->exception = exception; + +bail: + dvmReleaseTrackedAlloc(exception, self); +} + +void dvmThrowChainedExceptionWithClassMessage( + ClassObject* exceptionClass, const char* messageDescriptor, + Object* cause) +{ + char* message = dvmDescriptorToName(messageDescriptor); + + dvmThrowChainedException(exceptionClass, message, cause); + free(message); +} + +/* + * Find and return an exception constructor method that can take the + * indicated parameters, or return NULL if no such constructor exists. + */ +static Method* findExceptionInitMethod(ClassObject* excepClass, + bool hasMessage, bool hasCause) +{ + if (hasMessage) { + Method* result; + + if (hasCause) { + result = dvmFindDirectMethodByDescriptor( + excepClass, "<init>", + "(Ljava/lang/String;Ljava/lang/Throwable;)V"); + } else { + result = dvmFindDirectMethodByDescriptor( + excepClass, "<init>", "(Ljava/lang/String;)V"); + } + + if (result != NULL) { + return result; + } + + if (hasCause) { + return dvmFindDirectMethodByDescriptor( + excepClass, "<init>", + "(Ljava/lang/Object;Ljava/lang/Throwable;)V"); + } else { + return dvmFindDirectMethodByDescriptor( + excepClass, "<init>", "(Ljava/lang/Object;)V"); + } + } else if (hasCause) { + return dvmFindDirectMethodByDescriptor( + excepClass, "<init>", "(Ljava/lang/Throwable;)V"); + } else { + return dvmFindDirectMethodByDescriptor(excepClass, "<init>", "()V"); + } +} + +/* + * Initialize an exception with an appropriate constructor. + * + * "exception" is the exception object to initialize. + * Either or both of "msg" and "cause" may be null. + * "self" is dvmThreadSelf(), passed in so we don't have to look it up again. + * + * If the process of initializing the exception causes another + * exception (e.g., OutOfMemoryError) to be thrown, return an error + * and leave self->exception intact. + */ +static bool initException(Object* exception, const char* msg, Object* cause, + Thread* self) +{ + enum { + kInitUnknown, + kInitNoarg, + kInitMsg, + kInitMsgThrow, + kInitThrow + } initKind = kInitUnknown; + Method* initMethod = NULL; + ClassObject* excepClass = exception->clazz; + StringObject* msgStr = NULL; + bool result = false; + bool needInitCause = false; + + assert(self != NULL); + assert(self->exception == NULL); + + /* if we have a message, create a String */ + if (msg == NULL) + msgStr = NULL; + else { + msgStr = dvmCreateStringFromCstr(msg); + if (msgStr == NULL) { + LOGW("Could not allocate message string \"%s\" while " + "throwing internal exception (%s)", + msg, excepClass->descriptor); + goto bail; + } + } + + if (cause != NULL) { + if (!dvmInstanceof(cause->clazz, gDvm.exThrowable)) { + LOGE("Tried to init exception with cause '%s'", + cause->clazz->descriptor); + dvmAbort(); + } + } + + /* + * The Throwable class has four public constructors: + * (1) Throwable() + * (2) Throwable(String message) + * (3) Throwable(String message, Throwable cause) (added in 1.4) + * (4) Throwable(Throwable cause) (added in 1.4) + * + * The first two are part of the original design, and most exception + * classes should support them. The third prototype was used by + * individual exceptions. e.g. ClassNotFoundException added it in 1.2. + * The general "cause" mechanism was added in 1.4. Some classes, + * such as IllegalArgumentException, initially supported the first + * two, but added the second two in a later release. + * + * Exceptions may be picky about how their "cause" field is initialized. + * If you call ClassNotFoundException(String), it may choose to + * initialize its "cause" field to null. Doing so prevents future + * calls to Throwable.initCause(). + * + * So, if "cause" is not NULL, we need to look for a constructor that + * takes a throwable. If we can't find one, we fall back on calling + * #1/#2 and making a separate call to initCause(). Passing a null ref + * for "message" into Throwable(String, Throwable) is allowed, but we + * prefer to use the Throwable-only version because it has different + * behavior. + * + * java.lang.TypeNotPresentException is a strange case -- it has #3 but + * not #2. (Some might argue that the constructor is actually not #3, + * because it doesn't take the message string as an argument, but it + * has the same effect and we can work with it here.) + * + * java.lang.AssertionError is also a strange case -- it has a + * constructor that takes an Object, but not one that takes a String. + * There may be other cases like this, as well, so we generally look + * for an Object-taking constructor if we can't find one that takes + * a String. + */ + if (cause == NULL) { + if (msgStr == NULL) { + initMethod = findExceptionInitMethod(excepClass, false, false); + initKind = kInitNoarg; + } else { + initMethod = findExceptionInitMethod(excepClass, true, false); + if (initMethod != NULL) { + initKind = kInitMsg; + } else { + /* no #2, try #3 */ + initMethod = findExceptionInitMethod(excepClass, true, true); + if (initMethod != NULL) { + initKind = kInitMsgThrow; + } + } + } + } else { + if (msgStr == NULL) { + initMethod = findExceptionInitMethod(excepClass, false, true); + if (initMethod != NULL) { + initKind = kInitThrow; + } else { + initMethod = findExceptionInitMethod(excepClass, false, false); + initKind = kInitNoarg; + needInitCause = true; + } + } else { + initMethod = findExceptionInitMethod(excepClass, true, true); + if (initMethod != NULL) { + initKind = kInitMsgThrow; + } else { + initMethod = findExceptionInitMethod(excepClass, true, false); + initKind = kInitMsg; + needInitCause = true; + } + } + } + + if (initMethod == NULL) { + /* + * We can't find the desired constructor. This can happen if a + * subclass of java/lang/Throwable doesn't define an expected + * constructor, e.g. it doesn't provide one that takes a string + * when a message has been provided. + */ + LOGW("WARNING: exception class '%s' missing constructor " + "(msg='%s' kind=%d)", + excepClass->descriptor, msg, initKind); + assert(strcmp(excepClass->descriptor, + "Ljava/lang/RuntimeException;") != 0); + dvmThrowChainedException(gDvm.exRuntimeException, + "re-throw on exception class missing constructor", NULL); + goto bail; + } + + /* + * Call the constructor with the appropriate arguments. + */ + JValue unused; + switch (initKind) { + case kInitNoarg: + LOGVV("+++ exc noarg (ic=%d)", needInitCause); + dvmCallMethod(self, initMethod, exception, &unused); + break; + case kInitMsg: + LOGVV("+++ exc msg (ic=%d)", needInitCause); + dvmCallMethod(self, initMethod, exception, &unused, msgStr); + break; + case kInitThrow: + LOGVV("+++ exc throw"); + assert(!needInitCause); + dvmCallMethod(self, initMethod, exception, &unused, cause); + break; + case kInitMsgThrow: + LOGVV("+++ exc msg+throw"); + assert(!needInitCause); + dvmCallMethod(self, initMethod, exception, &unused, msgStr, cause); + break; + default: + assert(false); + goto bail; + } + + /* + * It's possible the constructor has thrown an exception. If so, we + * return an error and let our caller deal with it. + */ + if (self->exception != NULL) { + LOGW("Exception thrown (%s) while throwing internal exception (%s)", + self->exception->clazz->descriptor, exception->clazz->descriptor); + goto bail; + } + + /* + * If this exception was caused by another exception, and we weren't + * able to find a cause-setting constructor, set the "cause" field + * with an explicit call. + */ + if (needInitCause) { + Method* initCause; + initCause = dvmFindVirtualMethodHierByDescriptor(excepClass, "initCause", + "(Ljava/lang/Throwable;)Ljava/lang/Throwable;"); + if (initCause != NULL) { + dvmCallMethod(self, initCause, exception, &unused, cause); + if (self->exception != NULL) { + /* initCause() threw an exception; return an error and + * let the caller deal with it. + */ + LOGW("Exception thrown (%s) during initCause() " + "of internal exception (%s)", + self->exception->clazz->descriptor, + exception->clazz->descriptor); + goto bail; + } + } else { + LOGW("WARNING: couldn't find initCause in '%s'", + excepClass->descriptor); + } + } + + + result = true; + +bail: + dvmReleaseTrackedAlloc((Object*) msgStr, self); // NULL is ok + return result; +} + + +/* + * Clear the pending exception. This is used by the optimization and + * verification code, which mostly happens during runs of dexopt. + * + * This can also be called when the VM is in a "normal" state, e.g. when + * verifying classes that couldn't be verified at optimization time. + */ +void dvmClearOptException(Thread* self) +{ + self->exception = NULL; +} + +/* + * Returns "true" if this is a "checked" exception, i.e. it's a subclass + * of Throwable (assumed) but not a subclass of RuntimeException or Error. + */ +bool dvmIsCheckedException(const Object* exception) +{ + if (dvmInstanceof(exception->clazz, gDvm.exError) || + dvmInstanceof(exception->clazz, gDvm.exRuntimeException)) + { + return false; + } else { + return true; + } +} + +/* + * Wrap the now-pending exception in a different exception. This is useful + * for reflection stuff that wants to hand a checked exception back from a + * method that doesn't declare it. + * + * If something fails, an (unchecked) exception related to that failure + * will be pending instead. + */ +void dvmWrapException(const char* newExcepStr) +{ + Thread* self = dvmThreadSelf(); + Object* origExcep; + ClassObject* iteClass; + + origExcep = dvmGetException(self); + dvmAddTrackedAlloc(origExcep, self); // don't let the GC free it + + dvmClearException(self); // clear before class lookup + iteClass = dvmFindSystemClass(newExcepStr); + if (iteClass != NULL) { + Object* iteExcep; + Method* initMethod; + + iteExcep = dvmAllocObject(iteClass, ALLOC_DEFAULT); + if (iteExcep != NULL) { + initMethod = dvmFindDirectMethodByDescriptor(iteClass, "<init>", + "(Ljava/lang/Throwable;)V"); + if (initMethod != NULL) { + JValue unused; + dvmCallMethod(self, initMethod, iteExcep, &unused, + origExcep); + + /* if <init> succeeded, replace the old exception */ + if (!dvmCheckException(self)) + dvmSetException(self, iteExcep); + } + dvmReleaseTrackedAlloc(iteExcep, NULL); + + /* if initMethod doesn't exist, or failed... */ + if (!dvmCheckException(self)) + dvmSetException(self, origExcep); + } else { + /* leave OutOfMemoryError pending */ + } + } else { + /* leave ClassNotFoundException pending */ + } + + assert(dvmCheckException(self)); + dvmReleaseTrackedAlloc(origExcep, self); +} + +/* + * Get the "cause" field from an exception. + * + * The Throwable class initializes the "cause" field to "this" to + * differentiate between being initialized to null and never being + * initialized. We check for that here and convert it to NULL. + */ +Object* dvmGetExceptionCause(const Object* exception) +{ + if (!dvmInstanceof(exception->clazz, gDvm.exThrowable)) { + LOGE("Tried to get cause from object of type '%s'", + exception->clazz->descriptor); + dvmAbort(); + } + Object* cause = + dvmGetFieldObject(exception, gDvm.offJavaLangThrowable_cause); + if (cause == exception) + return NULL; + else + return cause; +} + +/* + * Print the stack trace of the current exception on stderr. This is called + * from the JNI ExceptionDescribe call. + * + * For consistency we just invoke the Throwable printStackTrace method, + * which might be overridden in the exception object. + * + * Exceptions thrown during the course of printing the stack trace are + * ignored. + */ +void dvmPrintExceptionStackTrace() +{ + Thread* self = dvmThreadSelf(); + Object* exception; + Method* printMethod; + + exception = self->exception; + if (exception == NULL) + return; + + dvmAddTrackedAlloc(exception, self); + self->exception = NULL; + printMethod = dvmFindVirtualMethodHierByDescriptor(exception->clazz, + "printStackTrace", "()V"); + if (printMethod != NULL) { + JValue unused; + dvmCallMethod(self, printMethod, exception, &unused); + } else { + LOGW("WARNING: could not find printStackTrace in %s", + exception->clazz->descriptor); + } + + if (self->exception != NULL) { + LOGW("NOTE: exception thrown while printing stack trace: %s", + self->exception->clazz->descriptor); + } + + self->exception = exception; + dvmReleaseTrackedAlloc(exception, self); +} + +/* + * Search the method's list of exceptions for a match. + * + * Returns the offset of the catch block on success, or -1 on failure. + */ +static int findCatchInMethod(Thread* self, const Method* method, int relPc, + ClassObject* excepClass) +{ + /* + * Need to clear the exception before entry. Otherwise, dvmResolveClass + * might think somebody threw an exception while it was loading a class. + */ + assert(!dvmCheckException(self)); + assert(!dvmIsNativeMethod(method)); + + LOGVV("findCatchInMethod %s.%s excep=%s depth=%d", + method->clazz->descriptor, method->name, excepClass->descriptor, + dvmComputeExactFrameDepth(self->interpSave.curFrame)); + + DvmDex* pDvmDex = method->clazz->pDvmDex; + const DexCode* pCode = dvmGetMethodCode(method); + DexCatchIterator iterator; + + if (dexFindCatchHandler(&iterator, pCode, relPc)) { + for (;;) { + DexCatchHandler* handler = dexCatchIteratorNext(&iterator); + + if (handler == NULL) { + break; + } + + if (handler->typeIdx == kDexNoIndex) { + /* catch-all */ + LOGV("Match on catch-all block at 0x%02x in %s.%s for %s", + relPc, method->clazz->descriptor, + method->name, excepClass->descriptor); + return handler->address; + } + + ClassObject* throwable = + dvmDexGetResolvedClass(pDvmDex, handler->typeIdx); + if (throwable == NULL) { + /* + * TODO: this behaves badly if we run off the stack + * while trying to throw an exception. The problem is + * that, if we're in a class loaded by a class loader, + * the call to dvmResolveClass has to ask the class + * loader for help resolving any previously-unresolved + * classes. If this particular class loader hasn't + * resolved StackOverflowError, it will call into + * interpreted code, and blow up. + * + * We currently replace the previous exception with + * the StackOverflowError, which means they won't be + * catching it *unless* they explicitly catch + * StackOverflowError, in which case we'll be unable + * to resolve the class referred to by the "catch" + * block. + * + * We end up getting a huge pile of warnings if we do + * a simple synthetic test, because this method gets + * called on every stack frame up the tree, and it + * fails every time. + * + * This eventually bails out, effectively becoming an + * uncatchable exception, so other than the flurry of + * warnings it's not really a problem. Still, we could + * probably handle this better. + */ + throwable = dvmResolveClass(method->clazz, handler->typeIdx, + true); + if (throwable == NULL) { + /* + * We couldn't find the exception they wanted in + * our class files (or, perhaps, the stack blew up + * while we were querying a class loader). Cough + * up a warning, then move on to the next entry. + * Keep the exception status clear. + */ + LOGW("Could not resolve class ref'ed in exception " + "catch list (class index %d, exception %s)", + handler->typeIdx, + (self->exception != NULL) ? + self->exception->clazz->descriptor : "(none)"); + dvmClearException(self); + continue; + } + } + + //LOGD("ADDR MATCH, check %s instanceof %s", + // excepClass->descriptor, pEntry->excepClass->descriptor); + + if (dvmInstanceof(excepClass, throwable)) { + LOGV("Match on catch block at 0x%02x in %s.%s for %s", + relPc, method->clazz->descriptor, + method->name, excepClass->descriptor); + return handler->address; + } + } + } + + LOGV("No matching catch block at 0x%02x in %s for %s", + relPc, method->name, excepClass->descriptor); + return -1; +} + +/* + * Find a matching "catch" block. "pc" is the relative PC within the + * current method, indicating the offset from the start in 16-bit units. + * + * Returns the offset to the catch block, or -1 if we run up against a + * break frame without finding anything. + * + * The class resolution stuff we have to do while evaluating the "catch" + * blocks could cause an exception. The caller should clear the exception + * before calling here and restore it after. + * + * Sets *newFrame to the frame pointer of the frame with the catch block. + * If "scanOnly" is false, self->interpSave.curFrame is also set to this value. + */ +int dvmFindCatchBlock(Thread* self, int relPc, Object* exception, + bool scanOnly, void** newFrame) +{ + u4* fp = self->interpSave.curFrame; + int catchAddr = -1; + + assert(!dvmCheckException(self)); + + while (true) { + StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); + catchAddr = findCatchInMethod(self, saveArea->method, relPc, + exception->clazz); + if (catchAddr >= 0) + break; + + /* + * Normally we'd check for ACC_SYNCHRONIZED methods and unlock + * them as we unroll. Dalvik uses what amount to generated + * "finally" blocks to take care of this for us. + */ + + /* output method profiling info */ + if (!scanOnly) { + TRACE_METHOD_UNROLL(self, saveArea->method); + } + + /* + * Move up one frame. If the next thing up is a break frame, + * break out now so we're left unrolled to the last method frame. + * We need to point there so we can roll up the JNI local refs + * if this was a native method. + */ + assert(saveArea->prevFrame != NULL); + if (dvmIsBreakFrame((u4*)saveArea->prevFrame)) { + if (!scanOnly) + break; // bail with catchAddr == -1 + + /* + * We're scanning for the debugger. It needs to know if this + * exception is going to be caught or not, and we need to figure + * out if it will be caught *ever* not just between the current + * position and the next break frame. We can't tell what native + * code is going to do, so we assume it never catches exceptions. + * + * Start by finding an interpreted code frame. + */ + fp = saveArea->prevFrame; // this is the break frame + saveArea = SAVEAREA_FROM_FP(fp); + fp = saveArea->prevFrame; // this may be a good one + while (fp != NULL) { + if (!dvmIsBreakFrame((u4*)fp)) { + saveArea = SAVEAREA_FROM_FP(fp); + if (!dvmIsNativeMethod(saveArea->method)) + break; + } + + fp = SAVEAREA_FROM_FP(fp)->prevFrame; + } + if (fp == NULL) + break; // bail with catchAddr == -1 + + /* + * Now fp points to the "good" frame. When the interp code + * invoked the native code, it saved a copy of its current PC + * into xtra.currentPc. Pull it out of there. + */ + relPc = + saveArea->xtra.currentPc - SAVEAREA_FROM_FP(fp)->method->insns; + } else { + fp = saveArea->prevFrame; + + /* savedPc in was-current frame goes with method in now-current */ + relPc = saveArea->savedPc - SAVEAREA_FROM_FP(fp)->method->insns; + } + } + + if (!scanOnly) + self->interpSave.curFrame = fp; + + /* + * The class resolution in findCatchInMethod() could cause an exception. + * Clear it to be safe. + */ + self->exception = NULL; + + *newFrame = fp; + return catchAddr; +} + +/* + * We have to carry the exception's stack trace around, but in many cases + * it will never be examined. It makes sense to keep it in a compact, + * VM-specific object, rather than an array of Objects with strings. + * + * Pass in the thread whose stack we're interested in. If "thread" is + * not self, the thread must be suspended. This implies that the thread + * list lock is held, which means we can't allocate objects or we risk + * jamming the GC. So, we allow this function to return different formats. + * (This shouldn't be called directly -- see the inline functions in the + * header file.) + * + * If "wantObject" is true, this returns a newly-allocated Object, which is + * presently an array of integers, but could become something else in the + * future. If "wantObject" is false, return plain malloc data. + * + * NOTE: if we support class unloading, we will need to scan the class + * object references out of these arrays. + */ +void* dvmFillInStackTraceInternal(Thread* thread, bool wantObject, size_t* pCount) +{ + ArrayObject* stackData = NULL; + int* simpleData = NULL; + void* fp; + void* startFp; + size_t stackDepth; + int* intPtr; + + if (pCount != NULL) + *pCount = 0; + fp = thread->interpSave.curFrame; + + assert(thread == dvmThreadSelf() || dvmIsSuspended(thread)); + + /* + * We're looking at a stack frame for code running below a Throwable + * constructor. We want to remove the Throwable methods and the + * superclass initializations so the user doesn't see them when they + * read the stack dump. + * + * TODO: this just scrapes off the top layers of Throwable. Might not do + * the right thing if we create an exception object or cause a VM + * exception while in a Throwable method. + */ + while (fp != NULL) { + const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); + const Method* method = saveArea->method; + + if (dvmIsBreakFrame((u4*)fp)) + break; + if (!dvmInstanceof(method->clazz, gDvm.exThrowable)) + break; + //LOGD("EXCEP: ignoring %s.%s", + // method->clazz->descriptor, method->name); + fp = saveArea->prevFrame; + } + startFp = fp; + + /* + * Compute the stack depth. + */ + stackDepth = 0; + while (fp != NULL) { + const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); + + if (!dvmIsBreakFrame((u4*)fp)) + stackDepth++; + + assert(fp != saveArea->prevFrame); + fp = saveArea->prevFrame; + } + //LOGD("EXCEP: stack depth is %d", stackDepth); + + if (!stackDepth) + goto bail; + + /* + * We need to store a pointer to the Method and the program counter. + * We have 4-byte pointers, so we use '[I'. + */ + if (wantObject) { + assert(sizeof(Method*) == 4); + stackData = dvmAllocPrimitiveArray('I', stackDepth*2, ALLOC_DEFAULT); + if (stackData == NULL) { + assert(dvmCheckException(dvmThreadSelf())); + goto bail; + } + intPtr = (int*)(void*)stackData->contents; + } else { + /* array of ints; first entry is stack depth */ + assert(sizeof(Method*) == sizeof(int)); + simpleData = (int*) malloc(sizeof(int) * stackDepth*2); + if (simpleData == NULL) + goto bail; + + assert(pCount != NULL); + intPtr = simpleData; + } + if (pCount != NULL) + *pCount = stackDepth; + + fp = startFp; + while (fp != NULL) { + const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); + const Method* method = saveArea->method; + + if (!dvmIsBreakFrame((u4*)fp)) { + //LOGD("EXCEP keeping %s.%s", method->clazz->descriptor, + // method->name); + + *intPtr++ = (int) method; + if (dvmIsNativeMethod(method)) { + *intPtr++ = 0; /* no saved PC for native methods */ + } else { + assert(saveArea->xtra.currentPc >= method->insns && + saveArea->xtra.currentPc < + method->insns + dvmGetMethodInsnsSize(method)); + *intPtr++ = (int) (saveArea->xtra.currentPc - method->insns); + } + + stackDepth--; // for verification + } + + assert(fp != saveArea->prevFrame); + fp = saveArea->prevFrame; + } + assert(stackDepth == 0); + +bail: + if (wantObject) { + dvmReleaseTrackedAlloc((Object*) stackData, dvmThreadSelf()); + return stackData; + } else { + return simpleData; + } +} + + +/* + * Given an Object previously created by dvmFillInStackTrace(), use the + * contents of the saved stack trace to generate an array of + * java/lang/StackTraceElement objects. + * + * The returned array is not added to the "local refs" list. + */ +ArrayObject* dvmGetStackTrace(const Object* ostackData) +{ + const ArrayObject* stackData = (const ArrayObject*) ostackData; + size_t stackSize = stackData->length / 2; + const int* intVals = (const int*)(void*)stackData->contents; + return dvmGetStackTraceRaw(intVals, stackSize); +} + +/* + * Generate an array of StackTraceElement objects from the raw integer + * data encoded by dvmFillInStackTrace(). + * + * "intVals" points to the first {method,pc} pair. + * + * The returned array is not added to the "local refs" list. + */ +ArrayObject* dvmGetStackTraceRaw(const int* intVals, size_t stackDepth) +{ + /* allocate a StackTraceElement array */ + ClassObject* klass = gDvm.classJavaLangStackTraceElementArray; + ArrayObject* array = dvmAllocArrayByClass(klass, stackDepth, ALLOC_DEFAULT); + if (array != NULL){ + dvmFillStackTraceElements(intVals, stackDepth, array); + dvmReleaseTrackedAlloc((Object*) array, NULL); + } + return array; +} + +/* + * Fills the StackTraceElement array elements from the raw integer + * data encoded by dvmFillInStackTrace(). + * + * "intVals" points to the first {method,pc} pair. + */ +void dvmFillStackTraceElements(const int* intVals, size_t stackDepth, ArrayObject* steArray) +{ + unsigned int i; + + /* init this if we haven't yet */ + if (!dvmIsClassInitialized(gDvm.classJavaLangStackTraceElement)) + dvmInitClass(gDvm.classJavaLangStackTraceElement); + + /* + * Allocate and initialize a StackTraceElement for each stack frame. + * We use the standard constructor to configure the object. + */ + for (i = 0; i < stackDepth; i++) { + Object* ste = dvmAllocObject(gDvm.classJavaLangStackTraceElement,ALLOC_DEFAULT); + if (ste == NULL) { + return; + } + + Method* meth = (Method*) *intVals++; + int pc = *intVals++; + + int lineNumber; + if (pc == -1) // broken top frame? + lineNumber = 0; + else + lineNumber = dvmLineNumFromPC(meth, pc); + + std::string dotName(dvmHumanReadableDescriptor(meth->clazz->descriptor)); + StringObject* className = dvmCreateStringFromCstr(dotName); + + StringObject* methodName = dvmCreateStringFromCstr(meth->name); + + const char* sourceFile = dvmGetMethodSourceFile(meth); + StringObject* fileName = (sourceFile != NULL) ? dvmCreateStringFromCstr(sourceFile) : NULL; + + /* + * Invoke: + * public StackTraceElement(String declaringClass, String methodName, + * String fileName, int lineNumber) + * (where lineNumber==-2 means "native") + */ + JValue unused; + dvmCallMethod(dvmThreadSelf(), gDvm.methJavaLangStackTraceElement_init, + ste, &unused, className, methodName, fileName, lineNumber); + + dvmReleaseTrackedAlloc(ste, NULL); + dvmReleaseTrackedAlloc((Object*) className, NULL); + dvmReleaseTrackedAlloc((Object*) methodName, NULL); + dvmReleaseTrackedAlloc((Object*) fileName, NULL); + + if (dvmCheckException(dvmThreadSelf())) { + return; + } + + dvmSetObjectArrayElement(steArray, i, ste); + } +} + +/* + * Dump the contents of a raw stack trace to the log. + */ +void dvmLogRawStackTrace(const int* intVals, int stackDepth) { + /* + * Run through the array of stack frame data. + */ + for (int i = 0; i < stackDepth; i++) { + Method* meth = (Method*) *intVals++; + int pc = *intVals++; + + std::string dotName(dvmHumanReadableDescriptor(meth->clazz->descriptor)); + if (dvmIsNativeMethod(meth)) { + LOGI("\tat %s.%s(Native Method)", dotName.c_str(), meth->name); + } else { + LOGI("\tat %s.%s(%s:%d)", + dotName.c_str(), meth->name, dvmGetMethodSourceFile(meth), + dvmLineNumFromPC(meth, pc)); + } + } +} + +/* + * Get the message string. We'd like to just grab the field out of + * Throwable, but the getMessage() function can be overridden by the + * sub-class. + * + * Returns the message string object, or NULL if it wasn't set or + * we encountered a failure trying to retrieve it. The string will + * be added to the tracked references table. + */ +static StringObject* getExceptionMessage(Object* exception) +{ + Thread* self = dvmThreadSelf(); + Method* getMessageMethod; + StringObject* messageStr = NULL; + Object* pendingException; + + /* + * If an exception is pending, clear it while we work and restore + * it when we're done. + */ + pendingException = dvmGetException(self); + if (pendingException != NULL) { + dvmAddTrackedAlloc(pendingException, self); + dvmClearException(self); + } + + getMessageMethod = dvmFindVirtualMethodHierByDescriptor(exception->clazz, + "getMessage", "()Ljava/lang/String;"); + if (getMessageMethod != NULL) { + /* could be in NATIVE mode from CheckJNI, so switch state */ + ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_RUNNING); + JValue result; + + dvmCallMethod(self, getMessageMethod, exception, &result); + messageStr = (StringObject*) result.l; + if (messageStr != NULL) + dvmAddTrackedAlloc((Object*) messageStr, self); + + dvmChangeStatus(self, oldStatus); + } else { + LOGW("WARNING: could not find getMessage in %s", + exception->clazz->descriptor); + } + + if (dvmGetException(self) != NULL) { + LOGW("NOTE: exception thrown while retrieving exception message: %s", + dvmGetException(self)->clazz->descriptor); + /* will be overwritten below */ + } + + dvmSetException(self, pendingException); + if (pendingException != NULL) { + dvmReleaseTrackedAlloc(pendingException, self); + } + return messageStr; +} + +/* + * Print the direct stack trace of the given exception to the log. + */ +static void logStackTraceOf(Object* exception) { + std::string className(dvmHumanReadableDescriptor(exception->clazz->descriptor)); + StringObject* messageStr = getExceptionMessage(exception); + if (messageStr != NULL) { + char* cp = dvmCreateCstrFromString(messageStr); + dvmReleaseTrackedAlloc((Object*) messageStr, dvmThreadSelf()); + messageStr = NULL; + + LOGI("%s: %s", className.c_str(), cp); + free(cp); + } else { + LOGI("%s:", className.c_str()); + } + + /* + * This relies on the stackState field, which contains the "raw" + * form of the stack. The Throwable class may clear this field + * after it generates the "cooked" form, in which case we'll have + * nothing to show. + */ + const ArrayObject* stackData = (const ArrayObject*) dvmGetFieldObject(exception, + gDvm.offJavaLangThrowable_stackState); + if (stackData == NULL) { + LOGI(" (raw stack trace not found)"); + return; + } + + int stackSize = stackData->length / 2; + const int* intVals = (const int*)(void*)stackData->contents; + + dvmLogRawStackTrace(intVals, stackSize); +} + +/* + * Print the stack trace of the current thread's exception, as well as + * the stack traces of any chained exceptions, to the log. We extract + * the stored stack trace and process it internally instead of calling + * interpreted code. + */ +void dvmLogExceptionStackTrace() +{ + Object* exception = dvmThreadSelf()->exception; + Object* cause; + + if (exception == NULL) { + LOGW("tried to log a null exception?"); + return; + } + + for (;;) { + logStackTraceOf(exception); + cause = dvmGetExceptionCause(exception); + if (cause == NULL) { + break; + } + LOGI("Caused by:"); + exception = cause; + } +} + +/* + * Helper for a few of the throw functions defined below. This throws + * the indicated exception, with a message based on a format in which + * "%s" is used exactly twice, first for a received class and second + * for the expected class. + */ +static void throwTypeError(ClassObject* exceptionClass, const char* fmt, + ClassObject* actual, ClassObject* desired) +{ + std::string actualClassName(dvmHumanReadableDescriptor(actual->descriptor)); + std::string desiredClassName(dvmHumanReadableDescriptor(desired->descriptor)); + dvmThrowExceptionFmt(exceptionClass, fmt, actualClassName.c_str(), desiredClassName.c_str()); +} + +void dvmThrowAbstractMethodError(const char* msg) { + dvmThrowException(gDvm.exAbstractMethodError, msg); +} + +void dvmThrowArithmeticException(const char* msg) { + dvmThrowException(gDvm.exArithmeticException, msg); +} + +void dvmThrowArrayIndexOutOfBoundsException(int length, int index) +{ + dvmThrowExceptionFmt(gDvm.exArrayIndexOutOfBoundsException, + "length=%d; index=%d", length, index); +} + +void dvmThrowArrayStoreExceptionIncompatibleElement(ClassObject* objectType, + ClassObject* arrayType) +{ + throwTypeError(gDvm.exArrayStoreException, + "%s cannot be stored in an array of type %s", + objectType, arrayType); +} + +void dvmThrowArrayStoreExceptionNotArray(ClassObject* actual, const char* label) { + std::string actualClassName(dvmHumanReadableDescriptor(actual->descriptor)); + dvmThrowExceptionFmt(gDvm.exArrayStoreException, "%s of type %s is not an array", + label, actualClassName.c_str()); +} + +void dvmThrowArrayStoreExceptionIncompatibleArrays(ClassObject* source, ClassObject* destination) +{ + throwTypeError(gDvm.exArrayStoreException, + "%s and %s are incompatible array types", + source, destination); +} + +void dvmThrowArrayStoreExceptionIncompatibleArrayElement(s4 index, ClassObject* objectType, + ClassObject* arrayType) +{ + std::string objectClassName(dvmHumanReadableDescriptor(objectType->descriptor)); + std::string arrayClassName(dvmHumanReadableDescriptor(arrayType->descriptor)); + dvmThrowExceptionFmt(gDvm.exArrayStoreException, + "source[%d] of type %s cannot be stored in destination array of type %s", + index, objectClassName.c_str(), arrayClassName.c_str()); +} + +void dvmThrowClassCastException(ClassObject* actual, ClassObject* desired) +{ + throwTypeError(gDvm.exClassCastException, + "%s cannot be cast to %s", actual, desired); +} + +void dvmThrowClassCircularityError(const char* descriptor) { + dvmThrowExceptionWithClassMessage(gDvm.exClassCircularityError, + descriptor); +} + +void dvmThrowClassFormatError(const char* msg) { + dvmThrowException(gDvm.exClassFormatError, msg); +} + +void dvmThrowClassNotFoundException(const char* name) { + dvmThrowChainedClassNotFoundException(name, NULL); +} + +void dvmThrowChainedClassNotFoundException(const char* name, Object* cause) { + /* + * Note: This exception is thrown in response to a request coming + * from client code for the name as given, so it is preferable to + * make the exception message be that string, per se, instead of + * trying to prettify it. + */ + dvmThrowChainedException(gDvm.exClassNotFoundException, name, cause); +} + +void dvmThrowExceptionInInitializerError() +{ + /* + * TODO: Do we want to wrap it if the original is an Error rather than + * an Exception? + * + * TODO: Should this just use dvmWrapException()? + */ + + if (gDvm.exExceptionInInitializerError == NULL) { + /* + * ExceptionInInitializerError isn't itself initialized. This + * can happen very early during VM startup if there is a + * problem with one of the corest-of-the-core classes, and it + * can possibly happen during a dexopt run. Rather than do + * anything fancier, we just abort here with a blatant + * message. + */ + LOGE("Fatal error during early class initialization:"); + dvmLogExceptionStackTrace(); + dvmAbort(); + } + + Thread* self = dvmThreadSelf(); + Object* exception = dvmGetException(self); + + dvmAddTrackedAlloc(exception, self); + dvmClearException(self); + + dvmThrowChainedException(gDvm.exExceptionInInitializerError, + NULL, exception); + dvmReleaseTrackedAlloc(exception, self); +} + +void dvmThrowFileNotFoundException(const char* msg) { + dvmThrowException(gDvm.exFileNotFoundException, msg); +} + +void dvmThrowIOException(const char* msg) { + dvmThrowException(gDvm.exIOException, msg); +} + +void dvmThrowIllegalAccessException(const char* msg) { + dvmThrowException(gDvm.exIllegalAccessException, msg); +} + +void dvmThrowIllegalAccessError(const char* msg) { + dvmThrowException(gDvm.exIllegalAccessError, msg); +} + +void dvmThrowIllegalArgumentException(const char* msg) { + dvmThrowException(gDvm.exIllegalArgumentException, msg); +} + +void dvmThrowIllegalMonitorStateException(const char* msg) { + dvmThrowException(gDvm.exIllegalMonitorStateException, msg); +} + +void dvmThrowIllegalStateException(const char* msg) { + dvmThrowException(gDvm.exIllegalStateException, msg); +} + +void dvmThrowIllegalThreadStateException(const char* msg) { + dvmThrowException(gDvm.exIllegalThreadStateException, msg); +} + +void dvmThrowIncompatibleClassChangeError(const char* msg) { + dvmThrowException(gDvm.exIncompatibleClassChangeError, msg); +} + +void dvmThrowIncompatibleClassChangeErrorWithClassMessage( + const char* descriptor) +{ + dvmThrowExceptionWithClassMessage( + gDvm.exIncompatibleClassChangeError, descriptor); +} + +void dvmThrowInstantiationException(ClassObject* clazz, const char* extraDetail) { + std::string className(dvmHumanReadableDescriptor(clazz->descriptor)); + dvmThrowExceptionFmt(gDvm.exInstantiationException, + "can't instantiate class %s%s%s", className.c_str(), + (extraDetail == NULL) ? "" : "; ", + (extraDetail == NULL) ? "" : extraDetail); +} + +void dvmThrowInternalError(const char* msg) { + dvmThrowException(gDvm.exInternalError, msg); +} + +void dvmThrowInterruptedException(const char* msg) { + dvmThrowException(gDvm.exInterruptedException, msg); +} + +void dvmThrowLinkageError(const char* msg) { + dvmThrowException(gDvm.exLinkageError, msg); +} + +void dvmThrowNegativeArraySizeException(s4 size) { + dvmThrowExceptionFmt(gDvm.exNegativeArraySizeException, "%d", size); +} + +void dvmThrowNoClassDefFoundError(const char* descriptor) { + dvmThrowExceptionWithClassMessage(gDvm.exNoClassDefFoundError, + descriptor); +} + +void dvmThrowChainedNoClassDefFoundError(const char* descriptor, + Object* cause) { + dvmThrowChainedExceptionWithClassMessage( + gDvm.exNoClassDefFoundError, descriptor, cause); +} + +void dvmThrowNoSuchFieldError(const char* msg) { + dvmThrowException(gDvm.exNoSuchFieldError, msg); +} + +void dvmThrowNoSuchFieldException(const char* msg) { + dvmThrowException(gDvm.exNoSuchFieldException, msg); +} + +void dvmThrowNoSuchMethodError(const char* msg) { + dvmThrowException(gDvm.exNoSuchMethodError, msg); +} + +void dvmThrowNullPointerException(const char* msg) { + dvmThrowException(gDvm.exNullPointerException, msg); +} + +void dvmThrowOutOfMemoryError(const char* msg) { + dvmThrowException(gDvm.exOutOfMemoryError, msg); +} + +void dvmThrowRuntimeException(const char* msg) { + dvmThrowException(gDvm.exRuntimeException, msg); +} + +void dvmThrowStaleDexCacheError(const char* msg) { + dvmThrowException(gDvm.exStaleDexCacheError, msg); +} + +void dvmThrowStringIndexOutOfBoundsExceptionWithIndex(jsize stringLength, + jsize requestIndex) { + dvmThrowExceptionFmt(gDvm.exStringIndexOutOfBoundsException, + "length=%d; index=%d", stringLength, requestIndex); +} + +void dvmThrowStringIndexOutOfBoundsExceptionWithRegion(jsize stringLength, + jsize requestStart, jsize requestLength) { + dvmThrowExceptionFmt(gDvm.exStringIndexOutOfBoundsException, + "length=%d; regionStart=%d; regionLength=%d", + stringLength, requestStart, requestLength); +} + +void dvmThrowTypeNotPresentException(const char* descriptor) { + dvmThrowExceptionWithClassMessage(gDvm.exTypeNotPresentException, + descriptor); +} + +void dvmThrowUnsatisfiedLinkError(const char* msg) { + dvmThrowException(gDvm.exUnsatisfiedLinkError, msg); +} + +void dvmThrowUnsupportedOperationException(const char* msg) { + dvmThrowException(gDvm.exUnsupportedOperationException, msg); +} + +void dvmThrowVerifyError(const char* descriptor) { + dvmThrowExceptionWithClassMessage(gDvm.exVerifyError, descriptor); +} + +void dvmThrowVirtualMachineError(const char* msg) { + dvmThrowException(gDvm.exVirtualMachineError, msg); +} |