#include "base/android/jni_android.h"
#include <stddef.h>
#include <sys/prctl.h>
#include "base/android/java_exception_reporter.h"
#include "base/android/jni_string.h"
#include "base/android/jni_utils.h"
#include "base/android_runtime_jni_headers/Throwable_jni.h"
#include "base/debug/debugging_buildflags.h"
#include "base/debug/stack_trace.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "build/robolectric_buildflags.h"
#include "third_party/jni_zero/jni_zero.h"
#include "base/jni_android_jni/JniAndroid_jni.h"
namespace base {
namespace android {
namespace {
BASE_FEATURE(kHandleExceptionsInJava,
"HandleJniExceptionsInJava",
base::FEATURE_ENABLED_BY_DEFAULT);
jclass g_out_of_memory_error_class = nullptr;
#if !BUILDFLAG(IS_ROBOLECTRIC)
jmethodID g_class_loader_load_class_method_id = nullptr;
jclass GetClassFromSplit(JNIEnv* env,
const char* class_name,
const char* split_name) {
DCHECK(IsStringASCII(class_name));
auto j_class_name =
ScopedJavaLocalRef<jstring>::Adopt(env, env->NewStringUTF(class_name));
return static_cast<jclass>(env->CallObjectMethod(
GetSplitClassLoader(env, split_name), g_class_loader_load_class_method_id,
j_class_name.obj()));
}
void PrepareClassLoaders(JNIEnv* env) {
if (g_class_loader_load_class_method_id == nullptr) {
GetClassLoader(env);
auto class_loader_clazz = ScopedJavaLocalRef<jclass>::Adopt(
env, env->FindClass("java/lang/ClassLoader"));
CHECK(!ClearException(env));
g_class_loader_load_class_method_id =
env->GetMethodID(class_loader_clazz.obj(), "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
CHECK(!ClearException(env));
}
}
#endif
}
LogFatalCallback g_log_fatal_callback_for_testing = nullptr;
const char kUnableToGetStackTraceMessage[] =
"Unable to retrieve Java caller stack trace as the exception handler is "
"being re-entered";
const char kReetrantOutOfMemoryMessage[] =
"While handling an uncaught Java exception, an OutOfMemoryError "
"occurred.";
const char kReetrantExceptionMessage[] =
"While handling an uncaught Java exception, another exception "
"occurred.";
const char kUncaughtExceptionMessage[] =
"Uncaught Java exception in native code. Please include the Java exception "
"stack from the Android log in your crash report.";
const char kUncaughtExceptionHandlerFailedMessage[] =
"Uncaught Java exception in native code and the Java uncaught exception "
"handler did not terminate the process. Please include the Java exception "
"stack from the Android log in your crash report.";
const char kOomInGetJavaExceptionInfoMessage[] =
"Unable to obtain Java stack trace due to OutOfMemoryError";
void InitVM(JavaVM* vm) {
jni_zero::InitVM(vm);
jni_zero::SetExceptionHandler(CheckException);
JNIEnv* env = jni_zero::AttachCurrentThread();
#if !BUILDFLAG(IS_ROBOLECTRIC)
PrepareClassLoaders(env);
jni_zero::SetClassResolver(GetClassFromSplit);
#endif
g_out_of_memory_error_class = static_cast<jclass>(
env->NewGlobalRef(env->FindClass("java/lang/OutOfMemoryError")));
DCHECK(g_out_of_memory_error_class);
}
void CheckException(JNIEnv* env) {
if (!jni_zero::HasException(env)) {
return;
}
static thread_local bool g_reentering = false;
if (g_reentering) {
env->ExceptionDescribe();
jthrowable raw_throwable = env->ExceptionOccurred();
env->ExceptionClear();
jclass clazz = env->GetObjectClass(raw_throwable);
bool is_oom_error = env->IsSameObject(clazz, g_out_of_memory_error_class);
env->Throw(raw_throwable);
if (is_oom_error) {
base::android::SetJavaException(kReetrantOutOfMemoryMessage);
if (g_log_fatal_callback_for_testing) {
g_log_fatal_callback_for_testing(kReetrantOutOfMemoryMessage);
} else {
LOG(FATAL) << kReetrantOutOfMemoryMessage;
}
} else {
base::android::SetJavaException(kReetrantExceptionMessage);
if (g_log_fatal_callback_for_testing) {
g_log_fatal_callback_for_testing(kReetrantExceptionMessage);
} else {
LOG(FATAL) << kReetrantExceptionMessage;
}
}
return;
}
g_reentering = true;
LOG(ERROR) << "Crashing due to uncaught Java exception";
const bool handle_exception_in_java =
base::FeatureList::IsEnabled(kHandleExceptionsInJava);
if (!handle_exception_in_java) {
env->ExceptionDescribe();
}
jthrowable raw_throwable = env->ExceptionOccurred();
env->ExceptionClear();
auto throwable = ScopedJavaLocalRef<jthrowable>::Adopt(env, raw_throwable);
if (!handle_exception_in_java) {
base::android::SetJavaException(
GetJavaExceptionInfo(env, throwable).c_str());
if (g_log_fatal_callback_for_testing) {
g_log_fatal_callback_for_testing(kUncaughtExceptionMessage);
} else {
LOG(FATAL) << kUncaughtExceptionMessage;
}
g_reentering = false;
return;
}
const std::string native_stack_trace = base::debug::StackTrace().ToString();
LOG(ERROR) << "Native stack trace:" << std::endl << native_stack_trace;
ScopedJavaLocalRef<jthrowable> secondary_exception =
Java_JniAndroid_handleException(env, throwable, native_stack_trace);
base::android::SetJavaException(
GetJavaExceptionInfo(
env, secondary_exception ? secondary_exception : throwable)
.c_str());
if (g_log_fatal_callback_for_testing) {
g_log_fatal_callback_for_testing(kUncaughtExceptionHandlerFailedMessage);
} else {
LOG(FATAL) << kUncaughtExceptionHandlerFailedMessage;
}
g_reentering = false;
}
std::string GetJavaExceptionInfo(JNIEnv* env,
const JavaRef<jthrowable>& throwable) {
std::string sanitized_exception_string =
Java_JniAndroid_sanitizedStacktraceForUnhandledException(env, throwable);
return !sanitized_exception_string.empty()
? sanitized_exception_string
: kOomInGetJavaExceptionInfoMessage;
}
std::string GetJavaStackTraceIfPresent() {
JNIEnv* env = nullptr;
JavaVM* jvm = jni_zero::GetVM();
if (jvm) {
jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2);
}
if (!env) {
return {};
}
if (HasException(env)) {
return kUnableToGetStackTraceMessage;
}
ScopedJavaLocalRef<jthrowable> throwable =
JNI_Throwable::Java_Throwable_Constructor(env);
std::string ret = GetJavaExceptionInfo(env, throwable);
size_t newline_idx = ret.find('\n');
if (newline_idx == std::string::npos) {
return {};
}
return ret.substr(newline_idx + 1);
}
}
}
DEFINE_JNI(JniAndroid)