/* * Open up the reserved area and throw an exception. The reserved area * should only be needed to create and initialize the exception itself. * * If we already opened it and we're continuing to overflow, abort the VM. * * We have to leave the "reserved" area open until the "catch" handler has * finished doing its processing. This is because the catch handler may * need to resolve classes, which requires calling into the class loader if * the classes aren't already in the "initiating loader" list. */ void dvmHandleStackOverflow(Thread* self) { /* * Can we make the reserved area available? */ if (self->stackOverflowed) { /* * Already did, nothing to do but bail. */ LOGE("DalvikVM: double-overflow of stack in threadid=%d; aborting\n", self->threadId); dvmDumpThread(self, false); dvmAbort(); } /* open it up to the full range */ LOGI("Stack overflow, expanding (%p to %p)\n", self->interpStackEnd, self->interpStackStart - self->interpStackSize); //dvmDumpThread(self, false); self->interpStackEnd = self->interpStackStart - self->interpStackSize; self->stackOverflowed = true; /* * If we were trying to throw an exception when the stack overflowed, * we will blow up when doing the class lookup on StackOverflowError * because of the pending exception. So, we clear it and make it * the cause of the SOE. */ Object* excep = dvmGetException(self); if (excep != NULL) { LOGW("Stack overflow while throwing exception\n"); dvmClearException(self); } dvmThrowChainedException("Ljava/lang/StackOverflowError;", NULL, excep); }
/* * Throw the named exception using the dotted form of the class * descriptor as the exception message, and with the specified cause. */ void dvmThrowChainedExceptionWithClassMessage(const char* exceptionDescriptor, const char* messageDescriptor, Object* cause) { char* message = dvmDescriptorToDot(messageDescriptor); dvmThrowChainedException(exceptionDescriptor, message, cause); free(message); }
/* * Format the message into a small buffer and pass it along. */ void dvmThrowExceptionFmtV(const char* exceptionDescriptor, const char* fmt, va_list args) { char msgBuf[512]; vsnprintf(msgBuf, sizeof(msgBuf), fmt, args); dvmThrowChainedException(exceptionDescriptor, msgBuf, NULL); }
/* * Find a class by name, initializing it if requested. */ ClassObject* dvmFindClassByName(StringObject* nameObj, Object* loader, bool doInit) { ClassObject* clazz = NULL; char* name = NULL; char* descriptor = NULL; if (nameObj == NULL) { dvmThrowException("Ljava/lang/NullPointerException;", NULL); goto bail; } name = dvmCreateCstrFromString(nameObj); /* * We need to validate and convert the name (from x.y.z to x/y/z). This * is especially handy for array types, since we want to avoid * auto-generating bogus array classes. */ if (!validateClassName(name)) { LOGW("dvmFindClassByName rejecting '%s'\n", name); dvmThrowException("Ljava/lang/ClassNotFoundException;", name); goto bail; } descriptor = dvmDotToDescriptor(name); if (descriptor == NULL) { goto bail; } if (doInit) clazz = dvmFindClass(descriptor, loader); else clazz = dvmFindClassNoInit(descriptor, loader); if (clazz == NULL) { LOGVV("FAIL: load %s (%d)\n", descriptor, doInit); Thread* self = dvmThreadSelf(); Object* oldExcep = dvmGetException(self); dvmAddTrackedAlloc(oldExcep, self); /* don't let this be GCed */ dvmClearException(self); dvmThrowChainedException("Ljava/lang/ClassNotFoundException;", name, oldExcep); dvmReleaseTrackedAlloc(oldExcep, self); } else { LOGVV("GOOD: load %s (%d) --> %p ldr=%p\n", descriptor, doInit, clazz, clazz->classLoader); } bail: free(name); free(descriptor); return clazz; }
/* * Open up the reserved area and throw an exception. The reserved area * should only be needed to create and initialize the exception itself. * * If we already opened it and we're continuing to overflow, abort the VM. * * We have to leave the "reserved" area open until the "catch" handler has * finished doing its processing. This is because the catch handler may * need to resolve classes, which requires calling into the class loader if * the classes aren't already in the "initiating loader" list. */ void dvmHandleStackOverflow(Thread* self, const Method* method) { /* * Can we make the reserved area available? */ if (self->stackOverflowed) { /* * Already did, nothing to do but bail. */ ALOGE("DalvikVM: double-overflow of stack in threadid=%d; aborting", self->threadId); dvmDumpThread(self, false); dvmAbort(); } /* open it up to the full range */ ALOGI("threadid=%d: stack overflow on call to %s.%s:%s", self->threadId, method->clazz->descriptor, method->name, method->shorty); StackSaveArea* saveArea = SAVEAREA_FROM_FP(self->interpSave.curFrame); ALOGI(" method requires %d+%d+%d=%d bytes, fp is %p (%d left)", method->registersSize * 4, sizeof(StackSaveArea), method->outsSize * 4, (method->registersSize + method->outsSize) * 4 + sizeof(StackSaveArea), saveArea, (u1*) saveArea - self->interpStackEnd); ALOGI(" expanding stack end (%p to %p)", self->interpStackEnd, self->interpStackStart - self->interpStackSize); //dvmDumpThread(self, false); self->interpStackEnd = self->interpStackStart - self->interpStackSize; self->stackOverflowed = true; /* * If we were trying to throw an exception when the stack overflowed, * we will blow up when doing the class lookup on StackOverflowError * because of the pending exception. So, we clear it and make it * the cause of the SOE. */ Object* excep = dvmGetException(self); if (excep != NULL) { ALOGW("Stack overflow while throwing exception"); dvmClearException(self); } dvmThrowChainedException(gDvm.exStackOverflowError, NULL, excep); }
/* * 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)\n", msg, excepClass->descriptor); goto bail; } } if (cause != NULL) { if (!dvmInstanceof(cause->clazz, gDvm.classJavaLangThrowable)) { LOGE("Tried to init exception with cause '%s'\n", 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)\n", excepClass->descriptor, msg, initKind); assert(strcmp(excepClass->descriptor, "Ljava/lang/RuntimeException;") != 0); dvmThrowChainedException("Ljava/lang/RuntimeException;", "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)\n", needInitCause); dvmCallMethod(self, initMethod, exception, &unused); break; case kInitMsg: LOGVV("+++ exc msg (ic=%d)\n", 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)\n", 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)\n", self->exception->clazz->descriptor, exception->clazz->descriptor); goto bail; } } else { LOGW("WARNING: couldn't find initCause in '%s'\n", excepClass->descriptor); } } result = true; bail: dvmReleaseTrackedAlloc((Object*) msgStr, self); // NULL is ok return result; }
/* * Start/continue throwing process now that we have a class reference. */ void dvmThrowChainedExceptionByClass(ClassObject* excepClass, const char* msg, Object* cause) { Thread* self = dvmThreadSelf(); Object* exception; /* make sure the exception is initialized */ if (!dvmIsClassInitialized(excepClass) && !dvmInitClass(excepClass)) { LOGE("ERROR: unable to initialize exception class '%s'\n", excepClass->descriptor); if (strcmp(excepClass->descriptor, "Ljava/lang/InternalError;") == 0) dvmAbort(); dvmThrowChainedException("Ljava/lang/InternalError;", "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'\n", 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'\n", 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); }