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