• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  * Note this is NOT called during dexopt optimization.  Some of the fields
107  * are initialized by the verifier (dvmVerifyCodeFlow).
108  */
dvmExceptionStartup(void)109 bool dvmExceptionStartup(void)
110 {
111     gDvm.classJavaLangThrowable =
112         dvmFindSystemClassNoInit("Ljava/lang/Throwable;");
113     gDvm.classJavaLangRuntimeException =
114         dvmFindSystemClassNoInit("Ljava/lang/RuntimeException;");
115     gDvm.classJavaLangStackOverflowError =
116         dvmFindSystemClassNoInit("Ljava/lang/StackOverflowError;");
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  */
dvmExceptionShutdown(void)179 void dvmExceptionShutdown(void)
180 {
181     // nothing to do
182 }
183 
184 
185 /*
186  * Format the message into a small buffer and pass it along.
187  */
dvmThrowExceptionFmtV(const char * exceptionDescriptor,const char * fmt,va_list args)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 /*
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  */
dvmThrowChainedException(const char * exceptionDescriptor,const char * msg,Object * cause)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  */
dvmThrowChainedExceptionByClass(ClassObject * excepClass,const char * msg,Object * cause)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  */
dvmThrowChainedExceptionWithClassMessage(const char * exceptionDescriptor,const char * messageDescriptor,Object * cause)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  */
dvmThrowExceptionByClassWithClassMessage(ClassObject * exceptionClass,const char * messageDescriptor)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 /*
355  * Find and return an exception constructor method that can take the
356  * indicated parameters, or return NULL if no such constructor exists.
357  */
findExceptionInitMethod(ClassObject * excepClass,bool hasMessage,bool hasCause)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 /*
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  */
initException(Object * exception,const char * msg,Object * cause,Thread * self)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);
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 
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 
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.)
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.
480      */
481     if (cause == NULL) {
482         if (msgStr == NULL) {
483             initMethod = findExceptionInitMethod(excepClass, false, false);
484             initKind = kInitNoarg;
485         } else {
486             initMethod = findExceptionInitMethod(excepClass, true, false);
487             if (initMethod != NULL) {
488                 initKind = kInitMsg;
489             } else {
490                 /* no #2, try #3 */
491                 initMethod = findExceptionInitMethod(excepClass, true, true);
492                 if (initMethod != NULL) {
493                     initKind = kInitMsgThrow;
494                 }
495             }
496         }
497     } else {
498         if (msgStr == NULL) {
499             initMethod = findExceptionInitMethod(excepClass, false, true);
500             if (initMethod != NULL) {
501                 initKind = kInitThrow;
502             } else {
503                 initMethod = findExceptionInitMethod(excepClass, false, false);
504                 initKind = kInitNoarg;
505                 needInitCause = true;
506             }
507         } else {
508             initMethod = findExceptionInitMethod(excepClass, true, true);
509             if (initMethod != NULL) {
510                 initKind = kInitMsgThrow;
511             } else {
512                 initMethod = findExceptionInitMethod(excepClass, true, false);
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);
531         dvmThrowChainedException("Ljava/lang/RuntimeException;",
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  */
dvmClearOptException(Thread * self)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  */
dvmIsCheckedException(const Object * exception)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  */
dvmWrapException(const char * newExcepStr)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 /*
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  */
dvmGetExceptionCause(const Object * exception)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 /*
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  */
dvmPrintExceptionStackTrace(void)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         LOGW("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  */
findCatchInMethod(Thread * self,const Method * method,int relPc,ClassObject * excepClass)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             }
784 
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  */
dvmFindCatchBlock(Thread * self,int relPc,Object * exception,bool scanOnly,void ** newFrame)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  */
dvmFillInStackTraceInternal(Thread * thread,bool wantObject,int * pCount)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 &&
1078                         saveArea->xtra.currentPc <
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  */
dvmGetStackTrace(const Object * ostackData)1108 ArrayObject* dvmGetStackTrace(const Object* ostackData)
1109 {
1110     const ArrayObject* stackData = (const ArrayObject*) ostackData;
1111     const int* intVals;
1112     int stackSize;
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  */
dvmGetStackTraceRaw(const int * intVals,int stackDepth)1127 ArrayObject* dvmGetStackTraceRaw(const int* intVals, int stackDepth)
1128 {
1129     ArrayObject* steArray = NULL;
1130     int i;
1131 
1132     /* init this if we haven't yet */
1133     if (!dvmIsClassInitialized(gDvm.classJavaLangStackTraceElement))
1134         dvmInitClass(gDvm.classJavaLangStackTraceElement);
1135 
1136     /* allocate a StackTraceElement array */
1137     steArray = dvmAllocArray(gDvm.classJavaLangStackTraceElementArray,
1138                     stackDepth, kObjectArrayRefWidth, ALLOC_DEFAULT);
1139     if (steArray == NULL)
1140         goto bail;
1141 
1142     /*
1143      * Allocate and initialize a StackTraceElement for each stack frame.
1144      * We use the standard constructor to configure the object.
1145      */
1146     for (i = 0; i < stackDepth; i++) {
1147         Object* ste;
1148         Method* meth;
1149         StringObject* className;
1150         StringObject* methodName;
1151         StringObject* fileName;
1152         int lineNumber, pc;
1153         const char* sourceFile;
1154         char* dotName;
1155 
1156         ste = dvmAllocObject(gDvm.classJavaLangStackTraceElement,ALLOC_DEFAULT);
1157         if (ste == NULL)
1158             goto bail;
1159 
1160         meth = (Method*) *intVals++;
1161         pc = *intVals++;
1162 
1163         if (pc == -1)      // broken top frame?
1164             lineNumber = 0;
1165         else
1166             lineNumber = dvmLineNumFromPC(meth, pc);
1167 
1168         dotName = dvmDescriptorToDot(meth->clazz->descriptor);
1169         className = dvmCreateStringFromCstr(dotName);
1170         free(dotName);
1171 
1172         methodName = dvmCreateStringFromCstr(meth->name);
1173         sourceFile = dvmGetMethodSourceFile(meth);
1174         if (sourceFile != NULL)
1175             fileName = dvmCreateStringFromCstr(sourceFile);
1176         else
1177             fileName = NULL;
1178 
1179         /*
1180          * Invoke:
1181          *  public StackTraceElement(String declaringClass, String methodName,
1182          *      String fileName, int lineNumber)
1183          * (where lineNumber==-2 means "native")
1184          */
1185         JValue unused;
1186         dvmCallMethod(dvmThreadSelf(), gDvm.methJavaLangStackTraceElement_init,
1187             ste, &unused, className, methodName, fileName, lineNumber);
1188 
1189         dvmReleaseTrackedAlloc(ste, NULL);
1190         dvmReleaseTrackedAlloc((Object*) className, NULL);
1191         dvmReleaseTrackedAlloc((Object*) methodName, NULL);
1192         dvmReleaseTrackedAlloc((Object*) fileName, NULL);
1193 
1194         if (dvmCheckException(dvmThreadSelf()))
1195             goto bail;
1196 
1197         dvmSetObjectArrayElement(steArray, i, ste);
1198     }
1199 
1200 bail:
1201     dvmReleaseTrackedAlloc((Object*) steArray, NULL);
1202     return steArray;
1203 }
1204 
1205 /*
1206  * Dump the contents of a raw stack trace to the log.
1207  */
dvmLogRawStackTrace(const int * intVals,int stackDepth)1208 void dvmLogRawStackTrace(const int* intVals, int stackDepth)
1209 {
1210     int i;
1211 
1212     /*
1213      * Run through the array of stack frame data.
1214      */
1215     for (i = 0; i < stackDepth; i++) {
1216         Method* meth;
1217         int lineNumber, pc;
1218         const char* sourceFile;
1219         char* dotName;
1220 
1221         meth = (Method*) *intVals++;
1222         pc = *intVals++;
1223 
1224         if (pc == -1)      // broken top frame?
1225             lineNumber = 0;
1226         else
1227             lineNumber = dvmLineNumFromPC(meth, pc);
1228 
1229         // probably don't need to do this, but it looks nicer
1230         dotName = dvmDescriptorToDot(meth->clazz->descriptor);
1231 
1232         if (dvmIsNativeMethod(meth)) {
1233             LOGI("\tat %s.%s(Native Method)\n", dotName, meth->name);
1234         } else {
1235             LOGI("\tat %s.%s(%s:%d)\n",
1236                 dotName, meth->name, dvmGetMethodSourceFile(meth),
1237                 dvmLineNumFromPC(meth, pc));
1238         }
1239 
1240         free(dotName);
1241 
1242         sourceFile = dvmGetMethodSourceFile(meth);
1243     }
1244 }
1245 
1246 /*
1247  * Print the direct stack trace of the given exception to the log.
1248  */
logStackTraceOf(Object * exception)1249 static void logStackTraceOf(Object* exception)
1250 {
1251     const ArrayObject* stackData;
1252     StringObject* messageStr;
1253     int stackSize;
1254     const int* intVals;
1255 
1256     messageStr = (StringObject*) dvmGetFieldObject(exception,
1257                     gDvm.offJavaLangThrowable_message);
1258     if (messageStr != NULL) {
1259         char* cp = dvmCreateCstrFromString(messageStr);
1260         LOGI("%s: %s\n", exception->clazz->descriptor, cp);
1261         free(cp);
1262     } else {
1263         LOGI("%s:\n", exception->clazz->descriptor);
1264     }
1265 
1266     stackData = (const ArrayObject*) dvmGetFieldObject(exception,
1267                     gDvm.offJavaLangThrowable_stackState);
1268     if (stackData == NULL) {
1269         LOGI("  (no stack trace data found)\n");
1270         return;
1271     }
1272 
1273     stackSize = stackData->length / 2;
1274     intVals = (const int*) stackData->contents;
1275 
1276     dvmLogRawStackTrace(intVals, stackSize);
1277 }
1278 
1279 /*
1280  * Print the stack trace of the current thread's exception, as well as
1281  * the stack traces of any chained exceptions, to the log. We extract
1282  * the stored stack trace and process it internally instead of calling
1283  * interpreted code.
1284  */
dvmLogExceptionStackTrace(void)1285 void dvmLogExceptionStackTrace(void)
1286 {
1287     Object* exception = dvmThreadSelf()->exception;
1288     Object* cause;
1289 
1290     if (exception == NULL) {
1291         LOGW("tried to log a null exception?\n");
1292         return;
1293     }
1294 
1295     for (;;) {
1296         logStackTraceOf(exception);
1297         cause = dvmGetExceptionCause(exception);
1298         if (cause == NULL) {
1299             break;
1300         }
1301         LOGI("Caused by:\n");
1302         exception = cause;
1303     }
1304 }
1305