• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base;
6 
7 import org.jni_zero.CalledByNative;
8 
9 /** Provides Java-side code to back `jni_android` native logic. */
10 public final class JniAndroid {
JniAndroid()11     private JniAndroid() {}
12 
13     private static final String TAG = "JniAndroid";
14 
15     /**
16      * Returns a sanitized stacktrace (per {@link PiiElider#sanitizeStacktrace(String)}) for the
17      * given throwable. Returns null if an OutOfMemoryError occurs.
18      *
19      * <p>Since this is running inside an uncaught exception handler, this method will make every
20      * effort not to throw; instead, any failures will be surfaced through the returned string.
21      */
22     @CalledByNative
sanitizedStacktraceForUnhandledException(Throwable throwable)23     private static String sanitizedStacktraceForUnhandledException(Throwable throwable) {
24         try {
25             return PiiElider.sanitizeStacktrace(Log.getStackTraceString(throwable));
26         } catch (OutOfMemoryError oomError) {
27             return null;
28         } catch (Throwable stacktraceThrowable) {
29             try {
30                 return "Error while getting stack trace: "
31                         + Log.getStackTraceString(stacktraceThrowable);
32             } catch (OutOfMemoryError oomError) {
33                 return null;
34             }
35         }
36     }
37 
38     /**
39      * Indicates that native code was faced with an uncaught Java exception.
40      *
41      * <p>{@code #getCause} returns the original uncaught exception.
42      */
43     public static class UncaughtExceptionException extends RuntimeException {
UncaughtExceptionException(String nativeStackTrace, Throwable uncaughtException)44         public UncaughtExceptionException(String nativeStackTrace, Throwable uncaughtException) {
45             super(
46                     "Native stack trace:" + System.lineSeparator() + nativeStackTrace,
47                     uncaughtException);
48         }
49     }
50 
51     /**
52      * Called by the Chromium native JNI framework when faced with an uncaught Java exception while
53      * executing a Java method from native code.
54      *
55      * <p>This method is expected to terminate the process (but is not guaranteed to).
56      *
57      * <p>The goal of this method is to provide an opportunity to terminate the process from the
58      * Java side so that the crash looks like any other uncaught Java exception, and is handled
59      * accordingly by system crash handlers. This ensures the Java stack trace will be collected, as
60      * opposed to the native stack trace - the former is typically more useful as the true root
61      * cause of the crash is Java code, not native code. See https://crbug.com/1426888 for more
62      * discussion.
63      *
64      * <p>This method will make every effort not to throw to avoid re-entering the Chromium JNI
65      * native exception handler. Errors will be sent to the system log instead.
66      *
67      * @param throwable The uncaught Java exception that was thrown by a Java method called via JNI.
68      * @param nativeStackTrace The stack trace of the native code that called the Java method that
69      *     threw.
70      * @return null, unless the uncaught exception handler threw an exception other than
71      *     OutOfMemoryError exception, in which case that exception is returned.
72      */
73     @CalledByNative
handleException(Throwable throwable, String nativeStackTrace)74     private static Throwable handleException(Throwable throwable, String nativeStackTrace) {
75         try {
76             // Try to make sure the exception details at least make their way to the log even if the
77             // rest of this method goes horribly wrong.
78             Log.e(TAG, "Handling uncaught Java exception", throwable);
79 
80             // Wrap the original exception so that we can annotate it with native stack information,
81             // with the goal of including as much information in the Java crash report as possible.
82             // (The native caller might itself have been called from Java. We don't need to care
83             // about that because the stack trace in `throwable` includes the *entire* Java stack of
84             // the current thread, even if there are native calls in the middle.)
85             var wrappedThrowable = new UncaughtExceptionException(nativeStackTrace, throwable);
86 
87             // The Chromium JNI framework does not support resuming execution after a Java method
88             // called through JNI throws an exception - we have to terminate the process at some
89             // point, otherwise undefined behavior may result. The goal here is to provide as much
90             // useful information to the crash handler as we can.
91             //
92             // To that end, we try to call the global uncaught exception handler. Hopefully that
93             // will eventually reach the default Android uncaught exception handler (possibly going
94             // through JavaExceptionReporter first, if we set one up), which will terminate the
95             // process. If for any reason that doesn't happen (e.g. the app set up a different
96             // handler), then we just give up and return the new exception (if any) - the native
97             // code we're returning to will terminate the process for us. (Note that, even then,
98             // there is still a case where we might not terminate the process: if the uncaught
99             // exception handler deliberately terminates the current thread but not the entire
100             // process. This is very contrived though, and protecting against this would be
101             // complicated, so we don't even try.)
102             Thread.getDefaultUncaughtExceptionHandler()
103                     .uncaughtException(Thread.currentThread(), wrappedThrowable);
104             Log.e(TAG, "Global uncaught exception handler did not terminate the process.");
105             return null;
106         } catch (OutOfMemoryError e) {
107             // Don't call Log.e() so as to not risk throwing again.
108             return null;
109         } catch (Throwable e) {
110             // Log the new crash rather than the original crash, since if there is a bug in our
111             // crash handling logic, we need to know about it. If there is a crash in a webview
112             // app's crash handling logic, then we can rely on other apps to upload the underlying
113             // exception.
114             Log.e(TAG, "Exception in uncaught exception handler.", e);
115             return e;
116         }
117     }
118 }
119