chromium/base/android/java/src/org/chromium/base/JavaExceptionReporter.java

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.base;

import android.os.DeadSystemException;

import androidx.annotation.UiThread;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;

/**
 * This UncaughtExceptionHandler will create a breakpad minidump when there is an uncaught
 * exception.
 *
 * <p>The exception's stack trace will be added to the minidump's data. This allows java-only
 * crashes to be reported in the same way as other native crashes.
 */
@JNINamespace("base::android")
public class JavaExceptionReporter implements Thread.UncaughtExceptionHandler {
    private final Thread.UncaughtExceptionHandler mParent;
    private final boolean mCrashAfterReport;
    private boolean mHandlingException;

    private JavaExceptionReporter(
            Thread.UncaughtExceptionHandler parent, boolean crashAfterReport) {
        mParent = parent;
        mCrashAfterReport = crashAfterReport;
    }

    /**
     * Returns whether a given Throwable is meaningful and actionable and should be reported.
     *
     * <p>Removes the following exceptions:
     *
     * <ul>
     *   <li>DeadSystemException: The core Android system has died and is going through a restart.
     *       http://go/android-dev/reference/android/os/DeadSystemException
     * </ul>
     */
    public static boolean shouldReportThrowable(Throwable e) {
        return !(e instanceof DeadSystemException);
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        if (!mHandlingException && shouldReportThrowable(e)) {
            mHandlingException = true;
            JavaExceptionReporterJni.get()
                    .reportJavaException(
                            mCrashAfterReport,
                            // If we are dealing with a JNI uncaught exception, then `e` is just a
                            // wrapper around the true exception, annotated with the native stack
                            // trace. The native stack trace is redundant, since we're going to
                            // include it separately anyway. Remove it to make the report smaller,
                            // clearer and to prevent the true Java exception information from being
                            // truncated away.
                            e instanceof JniAndroid.UncaughtExceptionException ? e.getCause() : e);
        }
        if (mParent != null) {
            mParent.uncaughtException(t, e);
        }
    }

    /**
     * Report and upload the stack trace as if it was a crash. This is very expensive and should
     * be called rarely and only on the UI thread to avoid corrupting other crash uploads. Ideally
     * only called in idle handlers.
     *
     * @param stackTrace The stack trace to report.
     */
    @UiThread
    public static void reportStackTrace(String stackTrace) {
        assert ThreadUtils.runningOnUiThread();
        JavaExceptionReporterJni.get()
                .reportJavaStackTrace(PiiElider.sanitizeStacktrace(stackTrace));
    }

    /**
     * Report and upload the stack trace as if it was a crash. This is very expensive and should
     * be called rarely and only on the UI thread to avoid corrupting other crash uploads. Ideally
     * only called in idle handlers.
     *
     * @param exception The exception to report.
     */
    @UiThread
    public static void reportException(Throwable exception) {
        assert ThreadUtils.runningOnUiThread();
        JavaExceptionReporterJni.get().reportJavaException(false, exception);
    }

    @CalledByNative
    private static void installHandler(boolean crashAfterReport) {
        Thread.setDefaultUncaughtExceptionHandler(
                new JavaExceptionReporter(
                        Thread.getDefaultUncaughtExceptionHandler(), crashAfterReport));
    }

    @NativeMethods
    interface Natives {
        void reportJavaException(boolean crashAfterReport, Throwable e);

        void reportJavaStackTrace(@JniType("std::string") String stackTrace);
    }
}