Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2012 The Chromium Authors |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "base/android/jni_android.h" |
| 6 | |
avi | b30f240 | 2015-12-24 03:43:28 | [diff] [blame] | 7 | #include <stddef.h> |
erikchen | 011ad3f | 2018-01-26 17:54:55 | [diff] [blame] | 8 | #include <sys/prctl.h> |
avi | b30f240 | 2015-12-24 03:43:28 | [diff] [blame] | 9 | |
Joshua Peraza | 7814da2 | 2018-07-10 21:37:50 | [diff] [blame] | 10 | #include "base/android/java_exception_reporter.h" |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 11 | #include "base/android/jni_string.h" |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 12 | #include "base/android/jni_utils.h" |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 13 | #include "base/android_runtime_jni_headers/Throwable_jni.h" |
Scott Violet | 4416579 | 2018-02-22 02:08:08 | [diff] [blame] | 14 | #include "base/debug/debugging_buildflags.h" |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 15 | #include "base/feature_list.h" |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 16 | #include "base/logging.h" |
Andrew Grieve | 4ad46dc | 2024-06-20 19:22:09 | [diff] [blame] | 17 | #include "base/strings/string_util.h" |
Andrew Grieve | 75cd62b | 2022-10-18 20:39:50 | [diff] [blame] | 18 | #include "build/build_config.h" |
Andrew Grieve | 2f99f287 | 2024-02-07 17:41:56 | [diff] [blame] | 19 | #include "build/robolectric_buildflags.h" |
Sam Maier | 8509cc6 | 2024-02-14 21:57:14 | [diff] [blame] | 20 | #include "third_party/jni_zero/jni_zero.h" |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 21 | |
Andrew Grieve | 70a2ba2 | 2025-02-04 15:51:03 | [diff] [blame] | 22 | // Must come after all headers that specialize FromJniType() / ToJniType(). |
Mohannad Farrag | f1d0892 | 2025-04-16 14:52:36 | [diff] [blame] | 23 | #include "base/jni_android_jni/JniAndroid_jni.h" |
Kyle Farnung | 7d4a9995 | 2024-03-26 21:15:16 | [diff] [blame] | 24 | |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 25 | namespace base { |
| 26 | namespace android { |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 27 | namespace { |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 28 | |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 29 | // If disabled, we LOG(FATAL) immediately in native code when faced with an |
| 30 | // uncaught Java exception (historical behavior). If enabled, we give the Java |
| 31 | // uncaught exception handler a chance to handle the exception first, so that |
| 32 | // the crash is (hopefully) seen as a Java crash, not a native crash. |
Alison Gale | 81f4f2c7 | 2024-04-22 19:33:31 | [diff] [blame] | 33 | // TODO(crbug.com/40261529): remove this switch once we are confident the |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 34 | // new behavior is fine. |
| 35 | BASE_FEATURE(kHandleExceptionsInJava, |
| 36 | "HandleJniExceptionsInJava", |
| 37 | base::FEATURE_ENABLED_BY_DEFAULT); |
| 38 | |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 39 | jclass g_out_of_memory_error_class = nullptr; |
[email protected] | 96e7ade | 2011-12-05 14:42:08 | [diff] [blame] | 40 | |
Andrew Grieve | 2f99f287 | 2024-02-07 17:41:56 | [diff] [blame] | 41 | #if !BUILDFLAG(IS_ROBOLECTRIC) |
| 42 | jmethodID g_class_loader_load_class_method_id = nullptr; |
| 43 | // ClassLoader.loadClass() accepts either slashes or dots on Android, but JVM |
| 44 | // requires dots. We could translate, but there is no need to go through |
| 45 | // ClassLoaders in Robolectric anyways. |
| 46 | // https://cs.android.com/search?q=symbol:DexFile_defineClassNative |
Sam Maier | 06caed5 | 2024-01-25 17:10:46 | [diff] [blame] | 47 | jclass GetClassFromSplit(JNIEnv* env, |
| 48 | const char* class_name, |
| 49 | const char* split_name) { |
Andrew Grieve | 4ad46dc | 2024-06-20 19:22:09 | [diff] [blame] | 50 | DCHECK(IsStringASCII(class_name)); |
| 51 | ScopedJavaLocalRef<jstring> j_class_name(env, env->NewStringUTF(class_name)); |
Sam Maier | 06caed5 | 2024-01-25 17:10:46 | [diff] [blame] | 52 | return static_cast<jclass>(env->CallObjectMethod( |
| 53 | GetSplitClassLoader(env, split_name), g_class_loader_load_class_method_id, |
Andrew Grieve | 4ad46dc | 2024-06-20 19:22:09 | [diff] [blame] | 54 | j_class_name.obj())); |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 55 | } |
| 56 | |
Sam Maier | 06caed5 | 2024-01-25 17:10:46 | [diff] [blame] | 57 | // Must be called before using GetClassFromSplit - we need to set the global, |
| 58 | // and we need to call GetClassLoader at least once to allow the default |
| 59 | // resolver (env->FindClass()) to get our main ClassLoader class instance, which |
| 60 | // we then cache use for all future calls to GetSplitClassLoader. |
| 61 | void PrepareClassLoaders(JNIEnv* env) { |
| 62 | if (g_class_loader_load_class_method_id == nullptr) { |
| 63 | GetClassLoader(env); |
| 64 | ScopedJavaLocalRef<jclass> class_loader_clazz = ScopedJavaLocalRef<jclass>( |
| 65 | env, env->FindClass("java/lang/ClassLoader")); |
| 66 | CHECK(!ClearException(env)); |
| 67 | g_class_loader_load_class_method_id = |
| 68 | env->GetMethodID(class_loader_clazz.obj(), "loadClass", |
| 69 | "(Ljava/lang/String;)Ljava/lang/Class;"); |
| 70 | CHECK(!ClearException(env)); |
| 71 | } |
| 72 | } |
Andrew Grieve | 2f99f287 | 2024-02-07 17:41:56 | [diff] [blame] | 73 | #endif // !BUILDFLAG(IS_ROBOLECTRIC) |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 74 | } // namespace |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 75 | |
Andrew Grieve | 378282f | 2024-01-10 16:18:49 | [diff] [blame] | 76 | LogFatalCallback g_log_fatal_callback_for_testing = nullptr; |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 77 | const char kUnableToGetStackTraceMessage[] = |
| 78 | "Unable to retrieve Java caller stack trace as the exception handler is " |
| 79 | "being re-entered"; |
| 80 | const char kReetrantOutOfMemoryMessage[] = |
| 81 | "While handling an uncaught Java exception, an OutOfMemoryError " |
| 82 | "occurred."; |
| 83 | const char kReetrantExceptionMessage[] = |
| 84 | "While handling an uncaught Java exception, another exception " |
| 85 | "occurred."; |
| 86 | const char kUncaughtExceptionMessage[] = |
| 87 | "Uncaught Java exception in native code. Please include the Java exception " |
| 88 | "stack from the Android log in your crash report."; |
| 89 | const char kUncaughtExceptionHandlerFailedMessage[] = |
| 90 | "Uncaught Java exception in native code and the Java uncaught exception " |
| 91 | "handler did not terminate the process. Please include the Java exception " |
| 92 | "stack from the Android log in your crash report."; |
| 93 | const char kOomInGetJavaExceptionInfoMessage[] = |
| 94 | "Unable to obtain Java stack trace due to OutOfMemoryError"; |
| 95 | |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 96 | void InitVM(JavaVM* vm) { |
Sam Maier | e2b94c8 | 2023-12-20 19:36:18 | [diff] [blame] | 97 | jni_zero::InitVM(vm); |
Sam Maier | 79cf2fb | 2023-12-21 19:04:14 | [diff] [blame] | 98 | jni_zero::SetExceptionHandler(CheckException); |
Sam Maier | cb004e13 | 2023-12-21 21:28:35 | [diff] [blame] | 99 | JNIEnv* env = jni_zero::AttachCurrentThread(); |
Andrew Grieve | 2f99f287 | 2024-02-07 17:41:56 | [diff] [blame] | 100 | #if !BUILDFLAG(IS_ROBOLECTRIC) |
| 101 | // Warm-up needed for GetClassFromSplit, must be called before we set the |
Sam Maier | 06caed5 | 2024-01-25 17:10:46 | [diff] [blame] | 102 | // resolver, since GetClassFromSplit won't work until after |
| 103 | // PrepareClassLoaders has happened. |
| 104 | PrepareClassLoaders(env); |
| 105 | jni_zero::SetClassResolver(GetClassFromSplit); |
Andrew Grieve | 2f99f287 | 2024-02-07 17:41:56 | [diff] [blame] | 106 | #endif |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 107 | g_out_of_memory_error_class = static_cast<jclass>( |
| 108 | env->NewGlobalRef(env->FindClass("java/lang/OutOfMemoryError"))); |
| 109 | DCHECK(g_out_of_memory_error_class); |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 110 | } |
| 111 | |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 112 | void CheckException(JNIEnv* env) { |
Sam Maier | 93fb2278 | 2024-01-09 16:39:25 | [diff] [blame] | 113 | if (!jni_zero::HasException(env)) { |
[email protected] | 909193c | 2014-06-28 01:58:04 | [diff] [blame] | 114 | return; |
Sam Maier | 93fb2278 | 2024-01-09 16:39:25 | [diff] [blame] | 115 | } |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 116 | |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 117 | static thread_local bool g_reentering = false; |
| 118 | if (g_reentering) { |
| 119 | // We were handling an uncaught Java exception already, but one of the Java |
| 120 | // methods we called below threw another exception. (This is unlikely to |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 121 | // happen, as we are careful to never throw from these methods, but we |
| 122 | // can't rule it out entirely. E.g. an OutOfMemoryError when constructing |
| 123 | // the jstring for the return value of |
| 124 | // sanitizedStacktraceForUnhandledException(). |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 125 | env->ExceptionDescribe(); |
| 126 | jthrowable raw_throwable = env->ExceptionOccurred(); |
| 127 | env->ExceptionClear(); |
| 128 | jclass clazz = env->GetObjectClass(raw_throwable); |
| 129 | bool is_oom_error = env->IsSameObject(clazz, g_out_of_memory_error_class); |
| 130 | env->Throw(raw_throwable); // Ensure we don't re-enter Java. |
| 131 | |
| 132 | if (is_oom_error) { |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 133 | base::android::SetJavaException(kReetrantOutOfMemoryMessage); |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 134 | // Use different LOG(FATAL) statements to ensure unique stack traces. |
Andrew Grieve | 378282f | 2024-01-10 16:18:49 | [diff] [blame] | 135 | if (g_log_fatal_callback_for_testing) { |
| 136 | g_log_fatal_callback_for_testing(kReetrantOutOfMemoryMessage); |
| 137 | } else { |
| 138 | LOG(FATAL) << kReetrantOutOfMemoryMessage; |
| 139 | } |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 140 | } else { |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 141 | base::android::SetJavaException(kReetrantExceptionMessage); |
Andrew Grieve | 378282f | 2024-01-10 16:18:49 | [diff] [blame] | 142 | if (g_log_fatal_callback_for_testing) { |
| 143 | g_log_fatal_callback_for_testing(kReetrantExceptionMessage); |
| 144 | } else { |
| 145 | LOG(FATAL) << kReetrantExceptionMessage; |
| 146 | } |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 147 | } |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 148 | // Needed for tests, which do not terminate from LOG(FATAL). |
| 149 | return; |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 150 | } |
| 151 | g_reentering = true; |
| 152 | |
| 153 | // Log a message to ensure there is something in the log even if the rest of |
| 154 | // this function goes horribly wrong, and also to provide a convenient marker |
| 155 | // in the log for where Java exception crash information starts. |
| 156 | LOG(ERROR) << "Crashing due to uncaught Java exception"; |
| 157 | |
| 158 | const bool handle_exception_in_java = |
| 159 | base::FeatureList::IsEnabled(kHandleExceptionsInJava); |
| 160 | |
| 161 | if (!handle_exception_in_java) { |
| 162 | env->ExceptionDescribe(); |
| 163 | } |
| 164 | |
Etienne Dechamps | 0ff37d6 | 2023-11-02 17:22:53 | [diff] [blame] | 165 | // We cannot use `ScopedJavaLocalRef` directly because that ends up calling |
| 166 | // env->GetObjectRefType() when DCHECK is on, and that call is not allowed |
| 167 | // with a pending exception according to the JNI spec. |
| 168 | jthrowable raw_throwable = env->ExceptionOccurred(); |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 169 | // Now that we saved the reference to the throwable, clear the exception. |
| 170 | // |
| 171 | // We need to do this as early as possible to remove the risk that code below |
| 172 | // might accidentally call back into Java, which is not allowed when `env` |
| 173 | // has an exception set, per the JNI spec. (For example, LOG(FATAL) doesn't |
| 174 | // work with a JNI exception set, because it calls |
| 175 | // GetJavaStackTraceIfPresent()). |
| 176 | env->ExceptionClear(); |
| 177 | // The reference returned by `ExceptionOccurred()` is a local reference. |
| 178 | // `ExceptionClear()` merely removes the exception information from `env`; |
| 179 | // it doesn't delete the reference, which is why this call is valid. |
| 180 | auto throwable = ScopedJavaLocalRef<jthrowable>::Adopt(env, raw_throwable); |
[email protected] | 909193c | 2014-06-28 01:58:04 | [diff] [blame] | 181 | |
Etienne Dechamps | 4ebe389 | 2023-11-03 18:02:12 | [diff] [blame] | 182 | if (!handle_exception_in_java) { |
| 183 | base::android::SetJavaException( |
| 184 | GetJavaExceptionInfo(env, throwable).c_str()); |
Andrew Grieve | 378282f | 2024-01-10 16:18:49 | [diff] [blame] | 185 | if (g_log_fatal_callback_for_testing) { |
| 186 | g_log_fatal_callback_for_testing(kUncaughtExceptionMessage); |
| 187 | } else { |
| 188 | LOG(FATAL) << kUncaughtExceptionMessage; |
| 189 | } |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 190 | // Needed for tests, which do not terminate from LOG(FATAL). |
| 191 | g_reentering = false; |
| 192 | return; |
Etienne Dechamps | 4ebe389 | 2023-11-03 18:02:12 | [diff] [blame] | 193 | } |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 194 | |
Etienne Dechamps | 4ebe389 | 2023-11-03 18:02:12 | [diff] [blame] | 195 | // We don't need to call SetJavaException() in this branch because we |
| 196 | // expect handleException() to eventually call JavaExceptionReporter through |
| 197 | // the global uncaught exception handler. |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 198 | |
Etienne Dechamps | e4321c2 | 2023-11-03 15:35:54 | [diff] [blame] | 199 | const std::string native_stack_trace = base::debug::StackTrace().ToString(); |
| 200 | LOG(ERROR) << "Native stack trace:" << std::endl << native_stack_trace; |
| 201 | |
Andrew Grieve | 7730935 | 2023-11-27 21:47:51 | [diff] [blame] | 202 | ScopedJavaLocalRef<jthrowable> secondary_exception = |
Glenn Hartmann | df50216 | 2024-12-17 20:13:22 | [diff] [blame] | 203 | Java_JniAndroid_handleException(env, throwable, native_stack_trace); |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 204 | |
| 205 | // Ideally handleException() should have terminated the process and we should |
Andrew Grieve | 7730935 | 2023-11-27 21:47:51 | [diff] [blame] | 206 | // not get here. This can happen in the case of OutOfMemoryError or if the |
| 207 | // app that embedded WebView installed an exception handler that does not |
| 208 | // terminate, or itself threw an exception. We cannot be confident that |
| 209 | // JavaExceptionReporter ran, so set the java exception explicitly. |
| 210 | base::android::SetJavaException( |
| 211 | GetJavaExceptionInfo( |
| 212 | env, secondary_exception ? secondary_exception : throwable) |
| 213 | .c_str()); |
Andrew Grieve | 378282f | 2024-01-10 16:18:49 | [diff] [blame] | 214 | if (g_log_fatal_callback_for_testing) { |
| 215 | g_log_fatal_callback_for_testing(kUncaughtExceptionHandlerFailedMessage); |
| 216 | } else { |
| 217 | LOG(FATAL) << kUncaughtExceptionHandlerFailedMessage; |
| 218 | } |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 219 | // Needed for tests, which do not terminate from LOG(FATAL). |
| 220 | g_reentering = false; |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 221 | } |
| 222 | |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 223 | std::string GetJavaExceptionInfo(JNIEnv* env, |
| 224 | const JavaRef<jthrowable>& throwable) { |
Glenn Hartmann | df50216 | 2024-12-17 20:13:22 | [diff] [blame] | 225 | std::string sanitized_exception_string = |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 226 | Java_JniAndroid_sanitizedStacktraceForUnhandledException(env, throwable); |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 227 | // Returns null when PiiElider results in an OutOfMemoryError. |
Glenn Hartmann | df50216 | 2024-12-17 20:13:22 | [diff] [blame] | 228 | return !sanitized_exception_string.empty() |
| 229 | ? sanitized_exception_string |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 230 | : kOomInGetJavaExceptionInfoMessage; |
cjhopman | 060e007 | 2015-05-06 21:37:48 | [diff] [blame] | 231 | } |
| 232 | |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 233 | std::string GetJavaStackTraceIfPresent() { |
| 234 | JNIEnv* env = nullptr; |
Sam Maier | cb004e13 | 2023-12-21 21:28:35 | [diff] [blame] | 235 | JavaVM* jvm = jni_zero::GetVM(); |
| 236 | if (jvm) { |
| 237 | jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2); |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 238 | } |
| 239 | if (!env) { |
| 240 | // JNI has not been initialized on this thread. |
| 241 | return {}; |
| 242 | } |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 243 | |
| 244 | if (HasException(env)) { |
| 245 | // This can happen if CheckException() is being re-entered, decided to |
| 246 | // LOG(FATAL) immediately, and LOG(FATAL) itself is calling us. In that case |
| 247 | // it is imperative that we don't try to call Java again. |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 248 | return kUnableToGetStackTraceMessage; |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 249 | } |
| 250 | |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 251 | ScopedJavaLocalRef<jthrowable> throwable = |
| 252 | JNI_Throwable::Java_Throwable_Constructor(env); |
| 253 | std::string ret = GetJavaExceptionInfo(env, throwable); |
| 254 | // Strip the exception message and leave only the "at" lines. Example: |
| 255 | // java.lang.Throwable: |
| 256 | // {tab}at Clazz.method(Clazz.java:111) |
| 257 | // {tab}at ... |
| 258 | size_t newline_idx = ret.find('\n'); |
| 259 | if (newline_idx == std::string::npos) { |
| 260 | // There are no java frames. |
| 261 | return {}; |
| 262 | } |
| 263 | return ret.substr(newline_idx + 1); |
| 264 | } |
| 265 | |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 266 | } // namespace android |
| 267 | } // namespace base |
Andrew Grieve | 4857181 | 2025-02-19 18:21:35 | [diff] [blame] | 268 | |
| 269 | DEFINE_JNI_FOR_JniAndroid() |