| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2008 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | /* |
| 17 | * Exception handling. |
| 18 | */ |
| 19 | #include "Dalvik.h" |
| 20 | #include "libdex/DexCatch.h" |
| 21 | |
| 22 | #include <stdlib.h> |
| 23 | |
| 24 | /* |
| 25 | Notes on Exception Handling |
| 26 | |
| 27 | We have one fairly sticky issue to deal with: creating the exception stack |
| 28 | trace. The trouble is that we need the current value of the program |
| 29 | counter for the method now being executed, but that's only held in a local |
| 30 | variable or hardware register in the main interpreter loop. |
| 31 | |
| 32 | The exception mechanism requires that the current stack trace be associated |
| 33 | with a Throwable at the time the Throwable is constructed. The construction |
| 34 | may or may not be associated with a throw. We have three situations to |
| 35 | consider: |
| 36 | |
| 37 | (1) A Throwable is created with a "new Throwable" statement in the |
| 38 | application code, for immediate or deferred use with a "throw" statement. |
| 39 | (2) The VM throws an exception from within the interpreter core, e.g. |
| 40 | after an integer divide-by-zero. |
| 41 | (3) The VM throws an exception from somewhere deeper down, e.g. while |
| 42 | trying to link a class. |
| 43 | |
| 44 | We need to have the current value for the PC, which means that for |
| 45 | situation (3) the interpreter loop must copy it to an externally-accessible |
| 46 | location before handling any opcode that could cause the VM to throw |
| 47 | an exception. We can't store it globally, because the various threads |
| 48 | would trample each other. We can't store it in the Thread structure, |
| 49 | because it'll get overwritten as soon as the Throwable constructor starts |
| 50 | executing. It needs to go on the stack, but our stack frames hold the |
| 51 | caller's *saved* PC, not the current PC. |
| 52 | |
| 53 | Situation #1 doesn't require special handling. Situation #2 could be dealt |
| 54 | with by passing the PC into the exception creation function. The trick |
| 55 | is to solve situation #3 in a way that adds minimal overhead to common |
| 56 | operations. Making it more costly to throw an exception is acceptable. |
| 57 | |
| 58 | There are a few ways to deal with this: |
| 59 | |
| 60 | (a) Change "savedPc" to "currentPc" in the stack frame. All of the |
| 61 | stack logic gets offset by one frame. The current PC is written |
| 62 | to the current stack frame when necessary. |
| 63 | (b) Write the current PC into the current stack frame, but without |
| 64 | replacing "savedPc". The JNI local refs pointer, which is only |
| 65 | used for native code, can be overloaded to save space. |
| 66 | (c) In dvmThrowException(), push an extra stack frame on, with the |
| 67 | current PC in it. The current PC is written into the Thread struct |
| 68 | when necessary, and copied out when the VM throws. |
| 69 | (d) Before doing something that might throw an exception, push a |
| 70 | temporary frame on with the saved PC in it. |
| 71 | |
| 72 | Solution (a) is the simplest, but breaks Dalvik's goal of mingling native |
| 73 | and interpreted stacks. |
| 74 | |
| 75 | Solution (b) retains the simplicity of (a) without rearranging the stack, |
| 76 | but now in some cases we're storing the PC twice, which feels wrong. |
| 77 | |
| 78 | Solution (c) usually works, because we push the saved PC onto the stack |
| 79 | before the Throwable construction can overwrite the copy in Thread. One |
| 80 | way solution (c) could break is: |
| 81 | - Interpreter saves the PC |
| 82 | - Execute some bytecode, which runs successfully (and alters the saved PC) |
| 83 | - Throw an exception before re-saving the PC (i.e in the same opcode) |
| 84 | This is a risk for anything that could cause <clinit> to execute, e.g. |
| 85 | executing a static method or accessing a static field. Attemping to access |
| 86 | a field that doesn't exist in a class that does exist might cause this. |
| 87 | It may be possible to simply bracket the dvmCallMethod*() functions to |
| 88 | save/restore it. |
| 89 | |
| 90 | Solution (d) incurs additional overhead, but may have other benefits (e.g. |
| 91 | it's easy to find the stack frames that should be removed before storage |
| 92 | in the Throwable). |
| 93 | |
| 94 | Current plan is option (b), because it's simple, fast, and doesn't change |
| 95 | the way the stack works. |
| 96 | */ |
| 97 | |
| 98 | /* fwd */ |
| 99 | static bool initException(Object* exception, const char* msg, Object* cause, |
| 100 | Thread* self); |
| 101 | |
| 102 | |
| 103 | /* |
| 104 | * Cache pointers to some of the exception classes we use locally. |
| Andy McFadden | 686e1e2 | 2009-05-26 16:56:30 -0700 | [diff] [blame] | 105 | * |
| 106 | * Note this is NOT called during dexopt optimization. Some of the fields |
| 107 | * are initialized by the verifier (dvmVerifyCodeFlow). |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 108 | */ |
| 109 | bool dvmExceptionStartup(void) |
| 110 | { |
| 111 | gDvm.classJavaLangThrowable = |
| 112 | dvmFindSystemClassNoInit("Ljava/lang/Throwable;"); |
| 113 | gDvm.classJavaLangRuntimeException = |
| 114 | dvmFindSystemClassNoInit("Ljava/lang/RuntimeException;"); |
| Andy McFadden | 4fbba1f | 2010-02-03 07:21:14 -0800 | [diff] [blame] | 115 | gDvm.classJavaLangStackOverflowError = |
| 116 | dvmFindSystemClassNoInit("Ljava/lang/StackOverflowError;"); |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 117 | gDvm.classJavaLangError = |
| 118 | dvmFindSystemClassNoInit("Ljava/lang/Error;"); |
| 119 | gDvm.classJavaLangStackTraceElement = |
| 120 | dvmFindSystemClassNoInit("Ljava/lang/StackTraceElement;"); |
| 121 | gDvm.classJavaLangStackTraceElementArray = |
| 122 | dvmFindArrayClass("[Ljava/lang/StackTraceElement;", NULL); |
| 123 | if (gDvm.classJavaLangThrowable == NULL || |
| 124 | gDvm.classJavaLangStackTraceElement == NULL || |
| 125 | gDvm.classJavaLangStackTraceElementArray == NULL) |
| 126 | { |
| 127 | LOGE("Could not find one or more essential exception classes\n"); |
| 128 | return false; |
| 129 | } |
| 130 | |
| 131 | /* |
| 132 | * Find the constructor. Note that, unlike other saved method lookups, |
| 133 | * we're using a Method* instead of a vtable offset. This is because |
| 134 | * constructors don't have vtable offsets. (Also, since we're creating |
| 135 | * the object in question, it's impossible for anyone to sub-class it.) |
| 136 | */ |
| 137 | Method* meth; |
| 138 | meth = dvmFindDirectMethodByDescriptor(gDvm.classJavaLangStackTraceElement, |
| 139 | "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V"); |
| 140 | if (meth == NULL) { |
| 141 | LOGE("Unable to find constructor for StackTraceElement\n"); |
| 142 | return false; |
| 143 | } |
| 144 | gDvm.methJavaLangStackTraceElement_init = meth; |
| 145 | |
| 146 | /* grab an offset for the stackData field */ |
| 147 | gDvm.offJavaLangThrowable_stackState = |
| 148 | dvmFindFieldOffset(gDvm.classJavaLangThrowable, |
| 149 | "stackState", "Ljava/lang/Object;"); |
| 150 | if (gDvm.offJavaLangThrowable_stackState < 0) { |
| 151 | LOGE("Unable to find Throwable.stackState\n"); |
| 152 | return false; |
| 153 | } |
| 154 | |
| 155 | /* and one for the message field, in case we want to show it */ |
| 156 | gDvm.offJavaLangThrowable_message = |
| 157 | dvmFindFieldOffset(gDvm.classJavaLangThrowable, |
| 158 | "detailMessage", "Ljava/lang/String;"); |
| 159 | if (gDvm.offJavaLangThrowable_message < 0) { |
| 160 | LOGE("Unable to find Throwable.detailMessage\n"); |
| 161 | return false; |
| 162 | } |
| 163 | |
| 164 | /* and one for the cause field, just 'cause */ |
| 165 | gDvm.offJavaLangThrowable_cause = |
| 166 | dvmFindFieldOffset(gDvm.classJavaLangThrowable, |
| 167 | "cause", "Ljava/lang/Throwable;"); |
| 168 | if (gDvm.offJavaLangThrowable_cause < 0) { |
| 169 | LOGE("Unable to find Throwable.cause\n"); |
| 170 | return false; |
| 171 | } |
| 172 | |
| 173 | return true; |
| 174 | } |
| 175 | |
| 176 | /* |
| 177 | * Clean up. |
| 178 | */ |
| 179 | void dvmExceptionShutdown(void) |
| 180 | { |
| 181 | // nothing to do |
| 182 | } |
| 183 | |
| 184 | |
| 185 | /* |
| Andy McFadden | 0171812 | 2010-01-22 16:36:30 -0800 | [diff] [blame] | 186 | * Format the message into a small buffer and pass it along. |
| 187 | */ |
| 188 | void dvmThrowExceptionFmtV(const char* exceptionDescriptor, const char* fmt, |
| 189 | va_list args) |
| 190 | { |
| 191 | char msgBuf[512]; |
| 192 | |
| 193 | vsnprintf(msgBuf, sizeof(msgBuf), fmt, args); |
| 194 | dvmThrowChainedException(exceptionDescriptor, msgBuf, NULL); |
| 195 | } |
| 196 | |
| 197 | /* |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 198 | * Create a Throwable and throw an exception in the current thread (where |
| 199 | * "throwing" just means "set the thread's exception pointer"). |
| 200 | * |
| 201 | * "msg" and/or "cause" may be NULL. |
| 202 | * |
| 203 | * If we have a bad exception hierarchy -- something in Throwable.<init> |
| 204 | * is missing -- then every attempt to throw an exception will result |
| 205 | * in another exception. Exceptions are generally allowed to "chain" |
| 206 | * to other exceptions, so it's hard to auto-detect this problem. It can |
| 207 | * only happen if the system classes are broken, so it's probably not |
| 208 | * worth spending cycles to detect it. |
| 209 | * |
| 210 | * We do have one case to worry about: if the classpath is completely |
| 211 | * wrong, we'll go into a death spin during startup because we can't find |
| 212 | * the initial class and then we can't find NoClassDefFoundError. We have |
| 213 | * to handle this case. |
| 214 | * |
| 215 | * [Do we want to cache pointers to common exception classes?] |
| 216 | */ |
| 217 | void dvmThrowChainedException(const char* exceptionDescriptor, const char* msg, |
| 218 | Object* cause) |
| 219 | { |
| 220 | ClassObject* excepClass; |
| 221 | |
| 222 | LOGV("THROW '%s' msg='%s' cause=%s\n", |
| 223 | exceptionDescriptor, msg, |
| 224 | (cause != NULL) ? cause->clazz->descriptor : "(none)"); |
| 225 | |
| 226 | if (gDvm.initializing) { |
| 227 | if (++gDvm.initExceptionCount >= 2) { |
| 228 | LOGE("Too many exceptions during init (failed on '%s' '%s')\n", |
| 229 | exceptionDescriptor, msg); |
| 230 | dvmAbort(); |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | excepClass = dvmFindSystemClass(exceptionDescriptor); |
| 235 | if (excepClass == NULL) { |
| 236 | /* |
| 237 | * We couldn't find the exception class. The attempt to find a |
| 238 | * nonexistent class should have raised an exception. If no |
| 239 | * exception is currently raised, then we're pretty clearly unable |
| 240 | * to throw ANY sort of exception, and we need to pack it in. |
| 241 | * |
| 242 | * If we were able to throw the "class load failed" exception, |
| 243 | * stick with that. Ideally we'd stuff the original exception |
| 244 | * into the "cause" field, but since we can't find it we can't |
| 245 | * do that. The exception class name should be in the "message" |
| 246 | * field. |
| 247 | */ |
| 248 | if (!dvmCheckException(dvmThreadSelf())) { |
| 249 | LOGE("FATAL: unable to throw exception (failed on '%s' '%s')\n", |
| 250 | exceptionDescriptor, msg); |
| 251 | dvmAbort(); |
| 252 | } |
| 253 | return; |
| 254 | } |
| 255 | |
| 256 | dvmThrowChainedExceptionByClass(excepClass, msg, cause); |
| 257 | } |
| 258 | |
| 259 | /* |
| 260 | * Start/continue throwing process now that we have a class reference. |
| 261 | */ |
| 262 | void dvmThrowChainedExceptionByClass(ClassObject* excepClass, const char* msg, |
| 263 | Object* cause) |
| 264 | { |
| 265 | Thread* self = dvmThreadSelf(); |
| 266 | Object* exception; |
| 267 | |
| 268 | /* make sure the exception is initialized */ |
| 269 | if (!dvmIsClassInitialized(excepClass) && !dvmInitClass(excepClass)) { |
| 270 | LOGE("ERROR: unable to initialize exception class '%s'\n", |
| 271 | excepClass->descriptor); |
| 272 | if (strcmp(excepClass->descriptor, "Ljava/lang/InternalError;") == 0) |
| 273 | dvmAbort(); |
| 274 | dvmThrowChainedException("Ljava/lang/InternalError;", |
| 275 | "failed to init original exception class", cause); |
| 276 | return; |
| 277 | } |
| 278 | |
| 279 | exception = dvmAllocObject(excepClass, ALLOC_DEFAULT); |
| 280 | if (exception == NULL) { |
| 281 | /* |
| 282 | * We're in a lot of trouble. We might be in the process of |
| 283 | * throwing an out-of-memory exception, in which case the |
| 284 | * pre-allocated object will have been thrown when our object alloc |
| 285 | * failed. So long as there's an exception raised, return and |
| 286 | * allow the system to try to recover. If not, something is broken |
| 287 | * and we need to bail out. |
| 288 | */ |
| 289 | if (dvmCheckException(self)) |
| 290 | goto bail; |
| 291 | LOGE("FATAL: unable to allocate exception '%s' '%s'\n", |
| 292 | excepClass->descriptor, msg != NULL ? msg : "(no msg)"); |
| 293 | dvmAbort(); |
| 294 | } |
| 295 | |
| 296 | /* |
| 297 | * Init the exception. |
| 298 | */ |
| 299 | if (gDvm.optimizing) { |
| 300 | /* need the exception object, but can't invoke interpreted code */ |
| 301 | LOGV("Skipping init of exception %s '%s'\n", |
| 302 | excepClass->descriptor, msg); |
| 303 | } else { |
| 304 | assert(excepClass == exception->clazz); |
| 305 | if (!initException(exception, msg, cause, self)) { |
| 306 | /* |
| 307 | * Whoops. If we can't initialize the exception, we can't use |
| 308 | * it. If there's an exception already set, the constructor |
| 309 | * probably threw an OutOfMemoryError. |
| 310 | */ |
| 311 | if (!dvmCheckException(self)) { |
| 312 | /* |
| 313 | * We're required to throw something, so we just |
| 314 | * throw the pre-constructed internal error. |
| 315 | */ |
| 316 | self->exception = gDvm.internalErrorObj; |
| 317 | } |
| 318 | goto bail; |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | self->exception = exception; |
| 323 | |
| 324 | bail: |
| 325 | dvmReleaseTrackedAlloc(exception, self); |
| 326 | } |
| 327 | |
| 328 | /* |
| 329 | * Throw the named exception using the dotted form of the class |
| 330 | * descriptor as the exception message, and with the specified cause. |
| 331 | */ |
| 332 | void dvmThrowChainedExceptionWithClassMessage(const char* exceptionDescriptor, |
| 333 | const char* messageDescriptor, Object* cause) |
| 334 | { |
| 335 | char* message = dvmDescriptorToDot(messageDescriptor); |
| 336 | |
| 337 | dvmThrowChainedException(exceptionDescriptor, message, cause); |
| 338 | free(message); |
| 339 | } |
| 340 | |
| 341 | /* |
| 342 | * Like dvmThrowExceptionWithMessageFromDescriptor, but take a |
| 343 | * class object instead of a name. |
| 344 | */ |
| 345 | void dvmThrowExceptionByClassWithClassMessage(ClassObject* exceptionClass, |
| 346 | const char* messageDescriptor) |
| 347 | { |
| 348 | char* message = dvmDescriptorToName(messageDescriptor); |
| 349 | |
| 350 | dvmThrowExceptionByClass(exceptionClass, message); |
| 351 | free(message); |
| 352 | } |
| 353 | |
| 354 | /* |
| Dan Bornstein | 4a888b0 | 2009-12-07 15:46:23 -0800 | [diff] [blame] | 355 | * Find and return an exception constructor method that can take the |
| 356 | * indicated parameters, or return NULL if no such constructor exists. |
| 357 | */ |
| 358 | static Method* findExceptionInitMethod(ClassObject* excepClass, |
| 359 | bool hasMessage, bool hasCause) |
| 360 | { |
| 361 | if (hasMessage) { |
| 362 | Method* result; |
| 363 | |
| 364 | if (hasCause) { |
| 365 | result = dvmFindDirectMethodByDescriptor( |
| 366 | excepClass, "<init>", |
| 367 | "(Ljava/lang/String;Ljava/lang/Throwable;)V"); |
| 368 | } else { |
| 369 | result = dvmFindDirectMethodByDescriptor( |
| 370 | excepClass, "<init>", "(Ljava/lang/String;)V"); |
| 371 | } |
| 372 | |
| 373 | if (result != NULL) { |
| 374 | return result; |
| 375 | } |
| 376 | |
| 377 | if (hasCause) { |
| 378 | return dvmFindDirectMethodByDescriptor( |
| 379 | excepClass, "<init>", |
| 380 | "(Ljava/lang/Object;Ljava/lang/Throwable;)V"); |
| 381 | } else { |
| 382 | return dvmFindDirectMethodByDescriptor( |
| 383 | excepClass, "<init>", "(Ljava/lang/Object;)V"); |
| 384 | } |
| 385 | } else if (hasCause) { |
| 386 | return dvmFindDirectMethodByDescriptor( |
| 387 | excepClass, "<init>", "(Ljava/lang/Throwable;)V"); |
| 388 | } else { |
| 389 | return dvmFindDirectMethodByDescriptor(excepClass, "<init>", "()V"); |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | /* |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 394 | * Initialize an exception with an appropriate constructor. |
| 395 | * |
| 396 | * "exception" is the exception object to initialize. |
| 397 | * Either or both of "msg" and "cause" may be null. |
| 398 | * "self" is dvmThreadSelf(), passed in so we don't have to look it up again. |
| 399 | * |
| 400 | * If the process of initializing the exception causes another |
| 401 | * exception (e.g., OutOfMemoryError) to be thrown, return an error |
| 402 | * and leave self->exception intact. |
| 403 | */ |
| 404 | static bool initException(Object* exception, const char* msg, Object* cause, |
| 405 | Thread* self) |
| 406 | { |
| 407 | enum { |
| 408 | kInitUnknown, |
| 409 | kInitNoarg, |
| 410 | kInitMsg, |
| 411 | kInitMsgThrow, |
| 412 | kInitThrow |
| 413 | } initKind = kInitUnknown; |
| 414 | Method* initMethod = NULL; |
| 415 | ClassObject* excepClass = exception->clazz; |
| 416 | StringObject* msgStr = NULL; |
| 417 | bool result = false; |
| 418 | bool needInitCause = false; |
| 419 | |
| 420 | assert(self != NULL); |
| 421 | assert(self->exception == NULL); |
| 422 | |
| 423 | /* if we have a message, create a String */ |
| 424 | if (msg == NULL) |
| 425 | msgStr = NULL; |
| 426 | else { |
| 427 | msgStr = dvmCreateStringFromCstr(msg, ALLOC_DEFAULT); |
| 428 | if (msgStr == NULL) { |
| 429 | LOGW("Could not allocate message string \"%s\" while " |
| 430 | "throwing internal exception (%s)\n", |
| 431 | msg, excepClass->descriptor); |
| 432 | goto bail; |
| 433 | } |
| 434 | } |
| 435 | |
| Andy McFadden | 686e1e2 | 2009-05-26 16:56:30 -0700 | [diff] [blame] | 436 | if (cause != NULL) { |
| 437 | if (!dvmInstanceof(cause->clazz, gDvm.classJavaLangThrowable)) { |
| 438 | LOGE("Tried to init exception with cause '%s'\n", |
| 439 | cause->clazz->descriptor); |
| 440 | dvmAbort(); |
| 441 | } |
| 442 | } |
| 443 | |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 444 | /* |
| 445 | * The Throwable class has four public constructors: |
| 446 | * (1) Throwable() |
| 447 | * (2) Throwable(String message) |
| 448 | * (3) Throwable(String message, Throwable cause) (added in 1.4) |
| 449 | * (4) Throwable(Throwable cause) (added in 1.4) |
| 450 | * |
| 451 | * The first two are part of the original design, and most exception |
| 452 | * classes should support them. The third prototype was used by |
| 453 | * individual exceptions. e.g. ClassNotFoundException added it in 1.2. |
| 454 | * The general "cause" mechanism was added in 1.4. Some classes, |
| 455 | * such as IllegalArgumentException, initially supported the first |
| 456 | * two, but added the second two in a later release. |
| 457 | * |
| 458 | * Exceptions may be picky about how their "cause" field is initialized. |
| 459 | * If you call ClassNotFoundException(String), it may choose to |
| 460 | * initialize its "cause" field to null. Doing so prevents future |
| 461 | * calls to Throwable.initCause(). |
| 462 | * |
| 463 | * So, if "cause" is not NULL, we need to look for a constructor that |
| 464 | * takes a throwable. If we can't find one, we fall back on calling |
| 465 | * #1/#2 and making a separate call to initCause(). Passing a null ref |
| 466 | * for "message" into Throwable(String, Throwable) is allowed, but we |
| 467 | * prefer to use the Throwable-only version because it has different |
| 468 | * behavior. |
| 469 | * |
| 470 | * java.lang.TypeNotPresentException is a strange case -- it has #3 but |
| 471 | * not #2. (Some might argue that the constructor is actually not #3, |
| 472 | * because it doesn't take the message string as an argument, but it |
| 473 | * has the same effect and we can work with it here.) |
| Dan Bornstein | 4a888b0 | 2009-12-07 15:46:23 -0800 | [diff] [blame] | 474 | * |
| 475 | * java.lang.AssertionError is also a strange case -- it has a |
| 476 | * constructor that takes an Object, but not one that takes a String. |
| 477 | * There may be other cases like this, as well, so we generally look |
| 478 | * for an Object-taking constructor if we can't find one that takes |
| 479 | * a String. |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 480 | */ |
| 481 | if (cause == NULL) { |
| 482 | if (msgStr == NULL) { |
| Dan Bornstein | 4a888b0 | 2009-12-07 15:46:23 -0800 | [diff] [blame] | 483 | initMethod = findExceptionInitMethod(excepClass, false, false); |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 484 | initKind = kInitNoarg; |
| 485 | } else { |
| Dan Bornstein | 4a888b0 | 2009-12-07 15:46:23 -0800 | [diff] [blame] | 486 | initMethod = findExceptionInitMethod(excepClass, true, false); |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 487 | if (initMethod != NULL) { |
| 488 | initKind = kInitMsg; |
| 489 | } else { |
| 490 | /* no #2, try #3 */ |
| Dan Bornstein | 4a888b0 | 2009-12-07 15:46:23 -0800 | [diff] [blame] | 491 | initMethod = findExceptionInitMethod(excepClass, true, true); |
| 492 | if (initMethod != NULL) { |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 493 | initKind = kInitMsgThrow; |
| Dan Bornstein | 4a888b0 | 2009-12-07 15:46:23 -0800 | [diff] [blame] | 494 | } |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 495 | } |
| 496 | } |
| 497 | } else { |
| 498 | if (msgStr == NULL) { |
| Dan Bornstein | 4a888b0 | 2009-12-07 15:46:23 -0800 | [diff] [blame] | 499 | initMethod = findExceptionInitMethod(excepClass, false, true); |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 500 | if (initMethod != NULL) { |
| 501 | initKind = kInitThrow; |
| 502 | } else { |
| Dan Bornstein | 4a888b0 | 2009-12-07 15:46:23 -0800 | [diff] [blame] | 503 | initMethod = findExceptionInitMethod(excepClass, false, false); |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 504 | initKind = kInitNoarg; |
| 505 | needInitCause = true; |
| 506 | } |
| 507 | } else { |
| Dan Bornstein | 4a888b0 | 2009-12-07 15:46:23 -0800 | [diff] [blame] | 508 | initMethod = findExceptionInitMethod(excepClass, true, true); |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 509 | if (initMethod != NULL) { |
| 510 | initKind = kInitMsgThrow; |
| 511 | } else { |
| Dan Bornstein | 4a888b0 | 2009-12-07 15:46:23 -0800 | [diff] [blame] | 512 | initMethod = findExceptionInitMethod(excepClass, true, false); |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 513 | initKind = kInitMsg; |
| 514 | needInitCause = true; |
| 515 | } |
| 516 | } |
| 517 | } |
| 518 | |
| 519 | if (initMethod == NULL) { |
| 520 | /* |
| 521 | * We can't find the desired constructor. This can happen if a |
| 522 | * subclass of java/lang/Throwable doesn't define an expected |
| 523 | * constructor, e.g. it doesn't provide one that takes a string |
| 524 | * when a message has been provided. |
| 525 | */ |
| 526 | LOGW("WARNING: exception class '%s' missing constructor " |
| 527 | "(msg='%s' kind=%d)\n", |
| 528 | excepClass->descriptor, msg, initKind); |
| 529 | assert(strcmp(excepClass->descriptor, |
| 530 | "Ljava/lang/RuntimeException;") != 0); |
| Carl Shapiro | de75089 | 2010-06-08 16:37:12 -0700 | [diff] [blame^] | 531 | dvmThrowChainedException("Ljava/lang/RuntimeException;", |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 532 | "re-throw on exception class missing constructor", NULL); |
| 533 | goto bail; |
| 534 | } |
| 535 | |
| 536 | /* |
| 537 | * Call the constructor with the appropriate arguments. |
| 538 | */ |
| 539 | JValue unused; |
| 540 | switch (initKind) { |
| 541 | case kInitNoarg: |
| 542 | LOGVV("+++ exc noarg (ic=%d)\n", needInitCause); |
| 543 | dvmCallMethod(self, initMethod, exception, &unused); |
| 544 | break; |
| 545 | case kInitMsg: |
| 546 | LOGVV("+++ exc msg (ic=%d)\n", needInitCause); |
| 547 | dvmCallMethod(self, initMethod, exception, &unused, msgStr); |
| 548 | break; |
| 549 | case kInitThrow: |
| 550 | LOGVV("+++ exc throw"); |
| 551 | assert(!needInitCause); |
| 552 | dvmCallMethod(self, initMethod, exception, &unused, cause); |
| 553 | break; |
| 554 | case kInitMsgThrow: |
| 555 | LOGVV("+++ exc msg+throw"); |
| 556 | assert(!needInitCause); |
| 557 | dvmCallMethod(self, initMethod, exception, &unused, msgStr, cause); |
| 558 | break; |
| 559 | default: |
| 560 | assert(false); |
| 561 | goto bail; |
| 562 | } |
| 563 | |
| 564 | /* |
| 565 | * It's possible the constructor has thrown an exception. If so, we |
| 566 | * return an error and let our caller deal with it. |
| 567 | */ |
| 568 | if (self->exception != NULL) { |
| 569 | LOGW("Exception thrown (%s) while throwing internal exception (%s)\n", |
| 570 | self->exception->clazz->descriptor, exception->clazz->descriptor); |
| 571 | goto bail; |
| 572 | } |
| 573 | |
| 574 | /* |
| 575 | * If this exception was caused by another exception, and we weren't |
| 576 | * able to find a cause-setting constructor, set the "cause" field |
| 577 | * with an explicit call. |
| 578 | */ |
| 579 | if (needInitCause) { |
| 580 | Method* initCause; |
| 581 | initCause = dvmFindVirtualMethodHierByDescriptor(excepClass, "initCause", |
| 582 | "(Ljava/lang/Throwable;)Ljava/lang/Throwable;"); |
| 583 | if (initCause != NULL) { |
| 584 | dvmCallMethod(self, initCause, exception, &unused, cause); |
| 585 | if (self->exception != NULL) { |
| 586 | /* initCause() threw an exception; return an error and |
| 587 | * let the caller deal with it. |
| 588 | */ |
| 589 | LOGW("Exception thrown (%s) during initCause() " |
| 590 | "of internal exception (%s)\n", |
| 591 | self->exception->clazz->descriptor, |
| 592 | exception->clazz->descriptor); |
| 593 | goto bail; |
| 594 | } |
| 595 | } else { |
| 596 | LOGW("WARNING: couldn't find initCause in '%s'\n", |
| 597 | excepClass->descriptor); |
| 598 | } |
| 599 | } |
| 600 | |
| 601 | |
| 602 | result = true; |
| 603 | |
| 604 | bail: |
| 605 | dvmReleaseTrackedAlloc((Object*) msgStr, self); // NULL is ok |
| 606 | return result; |
| 607 | } |
| 608 | |
| 609 | |
| 610 | /* |
| 611 | * Clear the pending exception and the "initExceptionCount" counter. This |
| 612 | * is used by the optimization and verification code, which has to run with |
| 613 | * "initializing" set to avoid going into a death-spin if the "class not |
| 614 | * found" exception can't be found. |
| 615 | * |
| 616 | * This can also be called when the VM is in a "normal" state, e.g. when |
| 617 | * verifying classes that couldn't be verified at optimization time. The |
| 618 | * reset of initExceptionCount should be harmless in that case. |
| 619 | */ |
| 620 | void dvmClearOptException(Thread* self) |
| 621 | { |
| 622 | self->exception = NULL; |
| 623 | gDvm.initExceptionCount = 0; |
| 624 | } |
| 625 | |
| 626 | /* |
| 627 | * Returns "true" if this is a "checked" exception, i.e. it's a subclass |
| 628 | * of Throwable (assumed) but not a subclass of RuntimeException or Error. |
| 629 | */ |
| 630 | bool dvmIsCheckedException(const Object* exception) |
| 631 | { |
| 632 | if (dvmInstanceof(exception->clazz, gDvm.classJavaLangError) || |
| 633 | dvmInstanceof(exception->clazz, gDvm.classJavaLangRuntimeException)) |
| 634 | { |
| 635 | return false; |
| 636 | } else { |
| 637 | return true; |
| 638 | } |
| 639 | } |
| 640 | |
| 641 | /* |
| 642 | * Wrap the now-pending exception in a different exception. This is useful |
| 643 | * for reflection stuff that wants to hand a checked exception back from a |
| 644 | * method that doesn't declare it. |
| 645 | * |
| 646 | * If something fails, an (unchecked) exception related to that failure |
| 647 | * will be pending instead. |
| 648 | */ |
| 649 | void dvmWrapException(const char* newExcepStr) |
| 650 | { |
| 651 | Thread* self = dvmThreadSelf(); |
| 652 | Object* origExcep; |
| 653 | ClassObject* iteClass; |
| 654 | |
| 655 | origExcep = dvmGetException(self); |
| 656 | dvmAddTrackedAlloc(origExcep, self); // don't let the GC free it |
| 657 | |
| 658 | dvmClearException(self); // clear before class lookup |
| 659 | iteClass = dvmFindSystemClass(newExcepStr); |
| 660 | if (iteClass != NULL) { |
| 661 | Object* iteExcep; |
| 662 | Method* initMethod; |
| 663 | |
| 664 | iteExcep = dvmAllocObject(iteClass, ALLOC_DEFAULT); |
| 665 | if (iteExcep != NULL) { |
| 666 | initMethod = dvmFindDirectMethodByDescriptor(iteClass, "<init>", |
| 667 | "(Ljava/lang/Throwable;)V"); |
| 668 | if (initMethod != NULL) { |
| 669 | JValue unused; |
| 670 | dvmCallMethod(self, initMethod, iteExcep, &unused, |
| 671 | origExcep); |
| 672 | |
| 673 | /* if <init> succeeded, replace the old exception */ |
| 674 | if (!dvmCheckException(self)) |
| 675 | dvmSetException(self, iteExcep); |
| 676 | } |
| 677 | dvmReleaseTrackedAlloc(iteExcep, NULL); |
| 678 | |
| 679 | /* if initMethod doesn't exist, or failed... */ |
| 680 | if (!dvmCheckException(self)) |
| 681 | dvmSetException(self, origExcep); |
| 682 | } else { |
| 683 | /* leave OutOfMemoryError pending */ |
| 684 | } |
| 685 | } else { |
| 686 | /* leave ClassNotFoundException pending */ |
| 687 | } |
| 688 | |
| 689 | assert(dvmCheckException(self)); |
| 690 | dvmReleaseTrackedAlloc(origExcep, self); |
| 691 | } |
| 692 | |
| 693 | /* |
| Andy McFadden | 686e1e2 | 2009-05-26 16:56:30 -0700 | [diff] [blame] | 694 | * Get the "cause" field from an exception. |
| 695 | * |
| 696 | * The Throwable class initializes the "cause" field to "this" to |
| 697 | * differentiate between being initialized to null and never being |
| 698 | * initialized. We check for that here and convert it to NULL. |
| 699 | */ |
| 700 | Object* dvmGetExceptionCause(const Object* exception) |
| 701 | { |
| 702 | if (!dvmInstanceof(exception->clazz, gDvm.classJavaLangThrowable)) { |
| 703 | LOGE("Tried to get cause from object of type '%s'\n", |
| 704 | exception->clazz->descriptor); |
| 705 | dvmAbort(); |
| 706 | } |
| 707 | Object* cause = |
| 708 | dvmGetFieldObject(exception, gDvm.offJavaLangThrowable_cause); |
| 709 | if (cause == exception) |
| 710 | return NULL; |
| 711 | else |
| 712 | return cause; |
| 713 | } |
| 714 | |
| 715 | /* |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 716 | * Print the stack trace of the current exception on stderr. This is called |
| 717 | * from the JNI ExceptionDescribe call. |
| 718 | * |
| 719 | * For consistency we just invoke the Throwable printStackTrace method, |
| 720 | * which might be overridden in the exception object. |
| 721 | * |
| 722 | * Exceptions thrown during the course of printing the stack trace are |
| 723 | * ignored. |
| 724 | */ |
| 725 | void dvmPrintExceptionStackTrace(void) |
| 726 | { |
| 727 | Thread* self = dvmThreadSelf(); |
| 728 | Object* exception; |
| 729 | Method* printMethod; |
| 730 | |
| 731 | exception = self->exception; |
| 732 | if (exception == NULL) |
| 733 | return; |
| 734 | |
| 735 | self->exception = NULL; |
| 736 | printMethod = dvmFindVirtualMethodHierByDescriptor(exception->clazz, |
| 737 | "printStackTrace", "()V"); |
| 738 | if (printMethod != NULL) { |
| 739 | JValue unused; |
| 740 | dvmCallMethod(self, printMethod, exception, &unused); |
| 741 | } else { |
| 742 | LOGW("WARNING: could not find printStackTrace in %s\n", |
| 743 | exception->clazz->descriptor); |
| 744 | } |
| 745 | |
| 746 | if (self->exception != NULL) { |
| 747 | LOGI("NOTE: exception thrown while printing stack trace: %s\n", |
| 748 | self->exception->clazz->descriptor); |
| 749 | } |
| 750 | |
| 751 | self->exception = exception; |
| 752 | } |
| 753 | |
| 754 | /* |
| 755 | * Search the method's list of exceptions for a match. |
| 756 | * |
| 757 | * Returns the offset of the catch block on success, or -1 on failure. |
| 758 | */ |
| 759 | static int findCatchInMethod(Thread* self, const Method* method, int relPc, |
| 760 | ClassObject* excepClass) |
| 761 | { |
| 762 | /* |
| 763 | * Need to clear the exception before entry. Otherwise, dvmResolveClass |
| 764 | * might think somebody threw an exception while it was loading a class. |
| 765 | */ |
| 766 | assert(!dvmCheckException(self)); |
| 767 | assert(!dvmIsNativeMethod(method)); |
| 768 | |
| 769 | LOGVV("findCatchInMethod %s.%s excep=%s depth=%d\n", |
| 770 | method->clazz->descriptor, method->name, excepClass->descriptor, |
| 771 | dvmComputeExactFrameDepth(self->curFrame)); |
| 772 | |
| 773 | DvmDex* pDvmDex = method->clazz->pDvmDex; |
| 774 | const DexCode* pCode = dvmGetMethodCode(method); |
| 775 | DexCatchIterator iterator; |
| 776 | |
| 777 | if (dexFindCatchHandler(&iterator, pCode, relPc)) { |
| 778 | for (;;) { |
| 779 | DexCatchHandler* handler = dexCatchIteratorNext(&iterator); |
| 780 | |
| 781 | if (handler == NULL) { |
| 782 | break; |
| 783 | } |
| Carl Shapiro | de75089 | 2010-06-08 16:37:12 -0700 | [diff] [blame^] | 784 | |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 785 | if (handler->typeIdx == kDexNoIndex) { |
| 786 | /* catch-all */ |
| 787 | LOGV("Match on catch-all block at 0x%02x in %s.%s for %s\n", |
| 788 | relPc, method->clazz->descriptor, |
| 789 | method->name, excepClass->descriptor); |
| 790 | return handler->address; |
| 791 | } |
| 792 | |
| 793 | ClassObject* throwable = |
| 794 | dvmDexGetResolvedClass(pDvmDex, handler->typeIdx); |
| 795 | if (throwable == NULL) { |
| 796 | /* |
| 797 | * TODO: this behaves badly if we run off the stack |
| 798 | * while trying to throw an exception. The problem is |
| 799 | * that, if we're in a class loaded by a class loader, |
| 800 | * the call to dvmResolveClass has to ask the class |
| 801 | * loader for help resolving any previously-unresolved |
| 802 | * classes. If this particular class loader hasn't |
| 803 | * resolved StackOverflowError, it will call into |
| 804 | * interpreted code, and blow up. |
| 805 | * |
| 806 | * We currently replace the previous exception with |
| 807 | * the StackOverflowError, which means they won't be |
| 808 | * catching it *unless* they explicitly catch |
| 809 | * StackOverflowError, in which case we'll be unable |
| 810 | * to resolve the class referred to by the "catch" |
| 811 | * block. |
| 812 | * |
| 813 | * We end up getting a huge pile of warnings if we do |
| 814 | * a simple synthetic test, because this method gets |
| 815 | * called on every stack frame up the tree, and it |
| 816 | * fails every time. |
| 817 | * |
| 818 | * This eventually bails out, effectively becoming an |
| 819 | * uncatchable exception, so other than the flurry of |
| 820 | * warnings it's not really a problem. Still, we could |
| 821 | * probably handle this better. |
| 822 | */ |
| 823 | throwable = dvmResolveClass(method->clazz, handler->typeIdx, |
| 824 | true); |
| 825 | if (throwable == NULL) { |
| 826 | /* |
| 827 | * We couldn't find the exception they wanted in |
| 828 | * our class files (or, perhaps, the stack blew up |
| 829 | * while we were querying a class loader). Cough |
| 830 | * up a warning, then move on to the next entry. |
| 831 | * Keep the exception status clear. |
| 832 | */ |
| 833 | LOGW("Could not resolve class ref'ed in exception " |
| 834 | "catch list (class index %d, exception %s)\n", |
| 835 | handler->typeIdx, |
| 836 | (self->exception != NULL) ? |
| 837 | self->exception->clazz->descriptor : "(none)"); |
| 838 | dvmClearException(self); |
| 839 | continue; |
| 840 | } |
| 841 | } |
| 842 | |
| 843 | //LOGD("ADDR MATCH, check %s instanceof %s\n", |
| 844 | // excepClass->descriptor, pEntry->excepClass->descriptor); |
| 845 | |
| 846 | if (dvmInstanceof(excepClass, throwable)) { |
| 847 | LOGV("Match on catch block at 0x%02x in %s.%s for %s\n", |
| 848 | relPc, method->clazz->descriptor, |
| 849 | method->name, excepClass->descriptor); |
| 850 | return handler->address; |
| 851 | } |
| 852 | } |
| 853 | } |
| 854 | |
| 855 | LOGV("No matching catch block at 0x%02x in %s for %s\n", |
| 856 | relPc, method->name, excepClass->descriptor); |
| 857 | return -1; |
| 858 | } |
| 859 | |
| 860 | /* |
| 861 | * Find a matching "catch" block. "pc" is the relative PC within the |
| 862 | * current method, indicating the offset from the start in 16-bit units. |
| 863 | * |
| 864 | * Returns the offset to the catch block, or -1 if we run up against a |
| 865 | * break frame without finding anything. |
| 866 | * |
| 867 | * The class resolution stuff we have to do while evaluating the "catch" |
| 868 | * blocks could cause an exception. The caller should clear the exception |
| 869 | * before calling here and restore it after. |
| 870 | * |
| 871 | * Sets *newFrame to the frame pointer of the frame with the catch block. |
| 872 | * If "scanOnly" is false, self->curFrame is also set to this value. |
| 873 | */ |
| 874 | int dvmFindCatchBlock(Thread* self, int relPc, Object* exception, |
| 875 | bool scanOnly, void** newFrame) |
| 876 | { |
| 877 | void* fp = self->curFrame; |
| 878 | int catchAddr = -1; |
| 879 | |
| 880 | assert(!dvmCheckException(self)); |
| 881 | |
| 882 | while (true) { |
| 883 | StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); |
| 884 | catchAddr = findCatchInMethod(self, saveArea->method, relPc, |
| 885 | exception->clazz); |
| 886 | if (catchAddr >= 0) |
| 887 | break; |
| 888 | |
| 889 | /* |
| 890 | * Normally we'd check for ACC_SYNCHRONIZED methods and unlock |
| 891 | * them as we unroll. Dalvik uses what amount to generated |
| 892 | * "finally" blocks to take care of this for us. |
| 893 | */ |
| 894 | |
| 895 | /* output method profiling info */ |
| 896 | if (!scanOnly) { |
| 897 | TRACE_METHOD_UNROLL(self, saveArea->method); |
| 898 | } |
| 899 | |
| 900 | /* |
| 901 | * Move up one frame. If the next thing up is a break frame, |
| 902 | * break out now so we're left unrolled to the last method frame. |
| 903 | * We need to point there so we can roll up the JNI local refs |
| 904 | * if this was a native method. |
| 905 | */ |
| 906 | assert(saveArea->prevFrame != NULL); |
| 907 | if (dvmIsBreakFrame(saveArea->prevFrame)) { |
| 908 | if (!scanOnly) |
| 909 | break; // bail with catchAddr == -1 |
| 910 | |
| 911 | /* |
| 912 | * We're scanning for the debugger. It needs to know if this |
| 913 | * exception is going to be caught or not, and we need to figure |
| 914 | * out if it will be caught *ever* not just between the current |
| 915 | * position and the next break frame. We can't tell what native |
| 916 | * code is going to do, so we assume it never catches exceptions. |
| 917 | * |
| 918 | * Start by finding an interpreted code frame. |
| 919 | */ |
| 920 | fp = saveArea->prevFrame; // this is the break frame |
| 921 | saveArea = SAVEAREA_FROM_FP(fp); |
| 922 | fp = saveArea->prevFrame; // this may be a good one |
| 923 | while (fp != NULL) { |
| 924 | if (!dvmIsBreakFrame(fp)) { |
| 925 | saveArea = SAVEAREA_FROM_FP(fp); |
| 926 | if (!dvmIsNativeMethod(saveArea->method)) |
| 927 | break; |
| 928 | } |
| 929 | |
| 930 | fp = SAVEAREA_FROM_FP(fp)->prevFrame; |
| 931 | } |
| 932 | if (fp == NULL) |
| 933 | break; // bail with catchAddr == -1 |
| 934 | |
| 935 | /* |
| 936 | * Now fp points to the "good" frame. When the interp code |
| 937 | * invoked the native code, it saved a copy of its current PC |
| 938 | * into xtra.currentPc. Pull it out of there. |
| 939 | */ |
| 940 | relPc = |
| 941 | saveArea->xtra.currentPc - SAVEAREA_FROM_FP(fp)->method->insns; |
| 942 | } else { |
| 943 | fp = saveArea->prevFrame; |
| 944 | |
| 945 | /* savedPc in was-current frame goes with method in now-current */ |
| 946 | relPc = saveArea->savedPc - SAVEAREA_FROM_FP(fp)->method->insns; |
| 947 | } |
| 948 | } |
| 949 | |
| 950 | if (!scanOnly) |
| 951 | self->curFrame = fp; |
| 952 | |
| 953 | /* |
| 954 | * The class resolution in findCatchInMethod() could cause an exception. |
| 955 | * Clear it to be safe. |
| 956 | */ |
| 957 | self->exception = NULL; |
| 958 | |
| 959 | *newFrame = fp; |
| 960 | return catchAddr; |
| 961 | } |
| 962 | |
| 963 | /* |
| 964 | * We have to carry the exception's stack trace around, but in many cases |
| 965 | * it will never be examined. It makes sense to keep it in a compact, |
| 966 | * VM-specific object, rather than an array of Objects with strings. |
| 967 | * |
| 968 | * Pass in the thread whose stack we're interested in. If "thread" is |
| 969 | * not self, the thread must be suspended. This implies that the thread |
| 970 | * list lock is held, which means we can't allocate objects or we risk |
| 971 | * jamming the GC. So, we allow this function to return different formats. |
| 972 | * (This shouldn't be called directly -- see the inline functions in the |
| 973 | * header file.) |
| 974 | * |
| 975 | * If "wantObject" is true, this returns a newly-allocated Object, which is |
| 976 | * presently an array of integers, but could become something else in the |
| 977 | * future. If "wantObject" is false, return plain malloc data. |
| 978 | * |
| 979 | * NOTE: if we support class unloading, we will need to scan the class |
| 980 | * object references out of these arrays. |
| 981 | */ |
| 982 | void* dvmFillInStackTraceInternal(Thread* thread, bool wantObject, int* pCount) |
| 983 | { |
| 984 | ArrayObject* stackData = NULL; |
| 985 | int* simpleData = NULL; |
| 986 | void* fp; |
| 987 | void* startFp; |
| 988 | int stackDepth; |
| 989 | int* intPtr; |
| 990 | |
| 991 | if (pCount != NULL) |
| 992 | *pCount = 0; |
| 993 | fp = thread->curFrame; |
| 994 | |
| 995 | assert(thread == dvmThreadSelf() || dvmIsSuspended(thread)); |
| 996 | |
| 997 | /* |
| 998 | * We're looking at a stack frame for code running below a Throwable |
| 999 | * constructor. We want to remove the Throwable methods and the |
| 1000 | * superclass initializations so the user doesn't see them when they |
| 1001 | * read the stack dump. |
| 1002 | * |
| 1003 | * TODO: this just scrapes off the top layers of Throwable. Might not do |
| 1004 | * the right thing if we create an exception object or cause a VM |
| 1005 | * exception while in a Throwable method. |
| 1006 | */ |
| 1007 | while (fp != NULL) { |
| 1008 | const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); |
| 1009 | const Method* method = saveArea->method; |
| 1010 | |
| 1011 | if (dvmIsBreakFrame(fp)) |
| 1012 | break; |
| 1013 | if (!dvmInstanceof(method->clazz, gDvm.classJavaLangThrowable)) |
| 1014 | break; |
| 1015 | //LOGD("EXCEP: ignoring %s.%s\n", |
| 1016 | // method->clazz->descriptor, method->name); |
| 1017 | fp = saveArea->prevFrame; |
| 1018 | } |
| 1019 | startFp = fp; |
| 1020 | |
| 1021 | /* |
| 1022 | * Compute the stack depth. |
| 1023 | */ |
| 1024 | stackDepth = 0; |
| 1025 | while (fp != NULL) { |
| 1026 | const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); |
| 1027 | |
| 1028 | if (!dvmIsBreakFrame(fp)) |
| 1029 | stackDepth++; |
| 1030 | |
| 1031 | assert(fp != saveArea->prevFrame); |
| 1032 | fp = saveArea->prevFrame; |
| 1033 | } |
| 1034 | //LOGD("EXCEP: stack depth is %d\n", stackDepth); |
| 1035 | |
| 1036 | if (!stackDepth) |
| 1037 | goto bail; |
| 1038 | |
| 1039 | /* |
| 1040 | * We need to store a pointer to the Method and the program counter. |
| 1041 | * We have 4-byte pointers, so we use '[I'. |
| 1042 | */ |
| 1043 | if (wantObject) { |
| 1044 | assert(sizeof(Method*) == 4); |
| 1045 | stackData = dvmAllocPrimitiveArray('I', stackDepth*2, ALLOC_DEFAULT); |
| 1046 | if (stackData == NULL) { |
| 1047 | assert(dvmCheckException(dvmThreadSelf())); |
| 1048 | goto bail; |
| 1049 | } |
| 1050 | intPtr = (int*) stackData->contents; |
| 1051 | } else { |
| 1052 | /* array of ints; first entry is stack depth */ |
| 1053 | assert(sizeof(Method*) == sizeof(int)); |
| 1054 | simpleData = (int*) malloc(sizeof(int) * stackDepth*2); |
| 1055 | if (simpleData == NULL) |
| 1056 | goto bail; |
| 1057 | |
| 1058 | assert(pCount != NULL); |
| 1059 | intPtr = simpleData; |
| 1060 | } |
| 1061 | if (pCount != NULL) |
| 1062 | *pCount = stackDepth; |
| 1063 | |
| 1064 | fp = startFp; |
| 1065 | while (fp != NULL) { |
| 1066 | const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); |
| 1067 | const Method* method = saveArea->method; |
| 1068 | |
| 1069 | if (!dvmIsBreakFrame(fp)) { |
| 1070 | //LOGD("EXCEP keeping %s.%s\n", method->clazz->descriptor, |
| 1071 | // method->name); |
| 1072 | |
| 1073 | *intPtr++ = (int) method; |
| 1074 | if (dvmIsNativeMethod(method)) { |
| 1075 | *intPtr++ = 0; /* no saved PC for native methods */ |
| 1076 | } else { |
| 1077 | assert(saveArea->xtra.currentPc >= method->insns && |
| Carl Shapiro | de75089 | 2010-06-08 16:37:12 -0700 | [diff] [blame^] | 1078 | saveArea->xtra.currentPc < |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 1079 | method->insns + dvmGetMethodInsnsSize(method)); |
| 1080 | *intPtr++ = (int) (saveArea->xtra.currentPc - method->insns); |
| 1081 | } |
| 1082 | |
| 1083 | stackDepth--; // for verification |
| 1084 | } |
| 1085 | |
| 1086 | assert(fp != saveArea->prevFrame); |
| 1087 | fp = saveArea->prevFrame; |
| 1088 | } |
| 1089 | assert(stackDepth == 0); |
| 1090 | |
| 1091 | bail: |
| 1092 | if (wantObject) { |
| 1093 | dvmReleaseTrackedAlloc((Object*) stackData, dvmThreadSelf()); |
| 1094 | return stackData; |
| 1095 | } else { |
| 1096 | return simpleData; |
| 1097 | } |
| 1098 | } |
| 1099 | |
| 1100 | |
| 1101 | /* |
| 1102 | * Given an Object previously created by dvmFillInStackTrace(), use the |
| 1103 | * contents of the saved stack trace to generate an array of |
| 1104 | * java/lang/StackTraceElement objects. |
| 1105 | * |
| 1106 | * The returned array is not added to the "local refs" list. |
| 1107 | */ |
| 1108 | ArrayObject* dvmGetStackTrace(const Object* ostackData) |
| 1109 | { |
| 1110 | const ArrayObject* stackData = (const ArrayObject*) ostackData; |
| 1111 | const int* intVals; |
| Carl Shapiro | e3c01da | 2010-05-20 22:54:18 -0700 | [diff] [blame] | 1112 | int stackSize; |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 1113 | |
| 1114 | stackSize = stackData->length / 2; |
| 1115 | intVals = (const int*) stackData->contents; |
| 1116 | return dvmGetStackTraceRaw(intVals, stackSize); |
| 1117 | } |
| 1118 | |
| 1119 | /* |
| 1120 | * Generate an array of StackTraceElement objects from the raw integer |
| 1121 | * data encoded by dvmFillInStackTrace(). |
| 1122 | * |
| 1123 | * "intVals" points to the first {method,pc} pair. |
| 1124 | * |
| 1125 | * The returned array is not added to the "local refs" list. |
| 1126 | */ |
| 1127 | ArrayObject* dvmGetStackTraceRaw(const int* intVals, int stackDepth) |
| 1128 | { |
| 1129 | ArrayObject* steArray = NULL; |
| 1130 | Object** stePtr; |
| 1131 | int i; |
| 1132 | |
| 1133 | /* init this if we haven't yet */ |
| 1134 | if (!dvmIsClassInitialized(gDvm.classJavaLangStackTraceElement)) |
| 1135 | dvmInitClass(gDvm.classJavaLangStackTraceElement); |
| 1136 | |
| 1137 | /* allocate a StackTraceElement array */ |
| 1138 | steArray = dvmAllocArray(gDvm.classJavaLangStackTraceElementArray, |
| 1139 | stackDepth, kObjectArrayRefWidth, ALLOC_DEFAULT); |
| 1140 | if (steArray == NULL) |
| 1141 | goto bail; |
| 1142 | stePtr = (Object**) steArray->contents; |
| 1143 | |
| 1144 | /* |
| 1145 | * Allocate and initialize a StackTraceElement for each stack frame. |
| 1146 | * We use the standard constructor to configure the object. |
| 1147 | */ |
| 1148 | for (i = 0; i < stackDepth; i++) { |
| 1149 | Object* ste; |
| 1150 | Method* meth; |
| 1151 | StringObject* className; |
| 1152 | StringObject* methodName; |
| 1153 | StringObject* fileName; |
| 1154 | int lineNumber, pc; |
| 1155 | const char* sourceFile; |
| 1156 | char* dotName; |
| 1157 | |
| 1158 | ste = dvmAllocObject(gDvm.classJavaLangStackTraceElement,ALLOC_DEFAULT); |
| 1159 | if (ste == NULL) |
| 1160 | goto bail; |
| 1161 | |
| 1162 | meth = (Method*) *intVals++; |
| 1163 | pc = *intVals++; |
| 1164 | |
| 1165 | if (pc == -1) // broken top frame? |
| 1166 | lineNumber = 0; |
| 1167 | else |
| 1168 | lineNumber = dvmLineNumFromPC(meth, pc); |
| 1169 | |
| 1170 | dotName = dvmDescriptorToDot(meth->clazz->descriptor); |
| 1171 | className = dvmCreateStringFromCstr(dotName, ALLOC_DEFAULT); |
| 1172 | free(dotName); |
| 1173 | |
| 1174 | methodName = dvmCreateStringFromCstr(meth->name, ALLOC_DEFAULT); |
| 1175 | sourceFile = dvmGetMethodSourceFile(meth); |
| 1176 | if (sourceFile != NULL) |
| 1177 | fileName = dvmCreateStringFromCstr(sourceFile, ALLOC_DEFAULT); |
| 1178 | else |
| 1179 | fileName = NULL; |
| 1180 | |
| 1181 | /* |
| 1182 | * Invoke: |
| 1183 | * public StackTraceElement(String declaringClass, String methodName, |
| 1184 | * String fileName, int lineNumber) |
| 1185 | * (where lineNumber==-2 means "native") |
| 1186 | */ |
| 1187 | JValue unused; |
| 1188 | dvmCallMethod(dvmThreadSelf(), gDvm.methJavaLangStackTraceElement_init, |
| 1189 | ste, &unused, className, methodName, fileName, lineNumber); |
| 1190 | |
| 1191 | dvmReleaseTrackedAlloc(ste, NULL); |
| 1192 | dvmReleaseTrackedAlloc((Object*) className, NULL); |
| 1193 | dvmReleaseTrackedAlloc((Object*) methodName, NULL); |
| 1194 | dvmReleaseTrackedAlloc((Object*) fileName, NULL); |
| 1195 | |
| 1196 | if (dvmCheckException(dvmThreadSelf())) |
| 1197 | goto bail; |
| 1198 | |
| 1199 | *stePtr++ = ste; |
| 1200 | } |
| 1201 | |
| 1202 | bail: |
| 1203 | dvmReleaseTrackedAlloc((Object*) steArray, NULL); |
| 1204 | return steArray; |
| 1205 | } |
| 1206 | |
| 1207 | /* |
| 1208 | * Dump the contents of a raw stack trace to the log. |
| 1209 | */ |
| 1210 | void dvmLogRawStackTrace(const int* intVals, int stackDepth) |
| 1211 | { |
| 1212 | int i; |
| 1213 | |
| 1214 | /* |
| 1215 | * Run through the array of stack frame data. |
| 1216 | */ |
| 1217 | for (i = 0; i < stackDepth; i++) { |
| 1218 | Method* meth; |
| 1219 | int lineNumber, pc; |
| 1220 | const char* sourceFile; |
| 1221 | char* dotName; |
| 1222 | |
| 1223 | meth = (Method*) *intVals++; |
| 1224 | pc = *intVals++; |
| 1225 | |
| 1226 | if (pc == -1) // broken top frame? |
| 1227 | lineNumber = 0; |
| 1228 | else |
| 1229 | lineNumber = dvmLineNumFromPC(meth, pc); |
| 1230 | |
| 1231 | // probably don't need to do this, but it looks nicer |
| 1232 | dotName = dvmDescriptorToDot(meth->clazz->descriptor); |
| 1233 | |
| 1234 | if (dvmIsNativeMethod(meth)) { |
| 1235 | LOGI("\tat %s.%s(Native Method)\n", dotName, meth->name); |
| 1236 | } else { |
| 1237 | LOGI("\tat %s.%s(%s:%d)\n", |
| 1238 | dotName, meth->name, dvmGetMethodSourceFile(meth), |
| 1239 | dvmLineNumFromPC(meth, pc)); |
| 1240 | } |
| 1241 | |
| 1242 | free(dotName); |
| 1243 | |
| 1244 | sourceFile = dvmGetMethodSourceFile(meth); |
| 1245 | } |
| 1246 | } |
| 1247 | |
| 1248 | /* |
| 1249 | * Print the direct stack trace of the given exception to the log. |
| 1250 | */ |
| 1251 | static void logStackTraceOf(Object* exception) |
| 1252 | { |
| 1253 | const ArrayObject* stackData; |
| 1254 | StringObject* messageStr; |
| 1255 | int stackSize; |
| 1256 | const int* intVals; |
| 1257 | |
| 1258 | messageStr = (StringObject*) dvmGetFieldObject(exception, |
| 1259 | gDvm.offJavaLangThrowable_message); |
| 1260 | if (messageStr != NULL) { |
| 1261 | char* cp = dvmCreateCstrFromString(messageStr); |
| 1262 | LOGI("%s: %s\n", exception->clazz->descriptor, cp); |
| 1263 | free(cp); |
| 1264 | } else { |
| 1265 | LOGI("%s:\n", exception->clazz->descriptor); |
| 1266 | } |
| 1267 | |
| 1268 | stackData = (const ArrayObject*) dvmGetFieldObject(exception, |
| 1269 | gDvm.offJavaLangThrowable_stackState); |
| 1270 | if (stackData == NULL) { |
| 1271 | LOGI(" (no stack trace data found)\n"); |
| 1272 | return; |
| 1273 | } |
| 1274 | |
| 1275 | stackSize = stackData->length / 2; |
| 1276 | intVals = (const int*) stackData->contents; |
| 1277 | |
| 1278 | dvmLogRawStackTrace(intVals, stackSize); |
| 1279 | } |
| 1280 | |
| 1281 | /* |
| 1282 | * Print the stack trace of the current thread's exception, as well as |
| 1283 | * the stack traces of any chained exceptions, to the log. We extract |
| 1284 | * the stored stack trace and process it internally instead of calling |
| 1285 | * interpreted code. |
| 1286 | */ |
| 1287 | void dvmLogExceptionStackTrace(void) |
| 1288 | { |
| 1289 | Object* exception = dvmThreadSelf()->exception; |
| 1290 | Object* cause; |
| 1291 | |
| 1292 | if (exception == NULL) { |
| 1293 | LOGW("tried to log a null exception?\n"); |
| 1294 | return; |
| 1295 | } |
| 1296 | |
| 1297 | for (;;) { |
| 1298 | logStackTraceOf(exception); |
| Andy McFadden | 686e1e2 | 2009-05-26 16:56:30 -0700 | [diff] [blame] | 1299 | cause = dvmGetExceptionCause(exception); |
| 1300 | if (cause == NULL) { |
| The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 1301 | break; |
| 1302 | } |
| 1303 | LOGI("Caused by:\n"); |
| 1304 | exception = cause; |
| 1305 | } |
| 1306 | } |
| 1307 | |