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" |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 14 | #include "base/base_jni/JniAndroid_jni.h" |
Scott Violet | 4416579 | 2018-02-22 02:08:08 | [diff] [blame] | 15 | #include "base/debug/debugging_buildflags.h" |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 16 | #include "base/feature_list.h" |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 17 | #include "base/logging.h" |
Andrew Grieve | 75cd62b | 2022-10-18 20:39:50 | [diff] [blame] | 18 | #include "build/build_config.h" |
Peter Kasting | dad508e4 | 2023-03-13 16:12:55 | [diff] [blame] | 19 | #include "third_party/abseil-cpp/absl/base/attributes.h" |
Sam Maier | e2b94c8 | 2023-12-20 19:36:18 | [diff] [blame] | 20 | #include "third_party/jni_zero/core.h" |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 21 | |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 22 | namespace base { |
| 23 | namespace android { |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 24 | namespace { |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 25 | |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 26 | // If disabled, we LOG(FATAL) immediately in native code when faced with an |
| 27 | // uncaught Java exception (historical behavior). If enabled, we give the Java |
| 28 | // uncaught exception handler a chance to handle the exception first, so that |
| 29 | // the crash is (hopefully) seen as a Java crash, not a native crash. |
| 30 | // TODO(https://crbug.com/1426888): remove this switch once we are confident the |
| 31 | // new behavior is fine. |
| 32 | BASE_FEATURE(kHandleExceptionsInJava, |
| 33 | "HandleJniExceptionsInJava", |
| 34 | base::FEATURE_ENABLED_BY_DEFAULT); |
| 35 | |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 36 | jclass g_out_of_memory_error_class = nullptr; |
| 37 | jmethodID g_class_loader_load_class_method_id = nullptr; |
[email protected] | 96e7ade | 2011-12-05 14:42:08 | [diff] [blame] | 38 | |
Sam Maier | 06caed5 | 2024-01-25 17:10:46 | [diff] [blame^] | 39 | jclass GetClassFromSplit(JNIEnv* env, |
| 40 | const char* class_name, |
| 41 | const char* split_name) { |
| 42 | return static_cast<jclass>(env->CallObjectMethod( |
| 43 | GetSplitClassLoader(env, split_name), g_class_loader_load_class_method_id, |
| 44 | ConvertUTF8ToJavaString(env, class_name).obj())); |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 45 | } |
| 46 | |
Sam Maier | 06caed5 | 2024-01-25 17:10:46 | [diff] [blame^] | 47 | // Must be called before using GetClassFromSplit - we need to set the global, |
| 48 | // and we need to call GetClassLoader at least once to allow the default |
| 49 | // resolver (env->FindClass()) to get our main ClassLoader class instance, which |
| 50 | // we then cache use for all future calls to GetSplitClassLoader. |
| 51 | void PrepareClassLoaders(JNIEnv* env) { |
| 52 | if (g_class_loader_load_class_method_id == nullptr) { |
| 53 | GetClassLoader(env); |
| 54 | ScopedJavaLocalRef<jclass> class_loader_clazz = ScopedJavaLocalRef<jclass>( |
| 55 | env, env->FindClass("java/lang/ClassLoader")); |
| 56 | CHECK(!ClearException(env)); |
| 57 | g_class_loader_load_class_method_id = |
| 58 | env->GetMethodID(class_loader_clazz.obj(), "loadClass", |
| 59 | "(Ljava/lang/String;)Ljava/lang/Class;"); |
| 60 | CHECK(!ClearException(env)); |
| 61 | } |
| 62 | } |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 63 | } // namespace |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 64 | |
Andrew Grieve | 378282f | 2024-01-10 16:18:49 | [diff] [blame] | 65 | LogFatalCallback g_log_fatal_callback_for_testing = nullptr; |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 66 | const char kUnableToGetStackTraceMessage[] = |
| 67 | "Unable to retrieve Java caller stack trace as the exception handler is " |
| 68 | "being re-entered"; |
| 69 | const char kReetrantOutOfMemoryMessage[] = |
| 70 | "While handling an uncaught Java exception, an OutOfMemoryError " |
| 71 | "occurred."; |
| 72 | const char kReetrantExceptionMessage[] = |
| 73 | "While handling an uncaught Java exception, another exception " |
| 74 | "occurred."; |
| 75 | const char kUncaughtExceptionMessage[] = |
| 76 | "Uncaught Java exception in native code. Please include the Java exception " |
| 77 | "stack from the Android log in your crash report."; |
| 78 | const char kUncaughtExceptionHandlerFailedMessage[] = |
| 79 | "Uncaught Java exception in native code and the Java uncaught exception " |
| 80 | "handler did not terminate the process. Please include the Java exception " |
| 81 | "stack from the Android log in your crash report."; |
| 82 | const char kOomInGetJavaExceptionInfoMessage[] = |
| 83 | "Unable to obtain Java stack trace due to OutOfMemoryError"; |
| 84 | |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 85 | void InitVM(JavaVM* vm) { |
Sam Maier | e2b94c8 | 2023-12-20 19:36:18 | [diff] [blame] | 86 | jni_zero::InitVM(vm); |
Sam Maier | 79cf2fb | 2023-12-21 19:04:14 | [diff] [blame] | 87 | jni_zero::SetExceptionHandler(CheckException); |
Sam Maier | cb004e13 | 2023-12-21 21:28:35 | [diff] [blame] | 88 | JNIEnv* env = jni_zero::AttachCurrentThread(); |
Sam Maier | 06caed5 | 2024-01-25 17:10:46 | [diff] [blame^] | 89 | // Warmup needed for GetClassFromSplit, must be called before we set the |
| 90 | // resolver, since GetClassFromSplit won't work until after |
| 91 | // PrepareClassLoaders has happened. |
| 92 | PrepareClassLoaders(env); |
| 93 | jni_zero::SetClassResolver(GetClassFromSplit); |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 94 | g_out_of_memory_error_class = static_cast<jclass>( |
| 95 | env->NewGlobalRef(env->FindClass("java/lang/OutOfMemoryError"))); |
| 96 | DCHECK(g_out_of_memory_error_class); |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 97 | } |
| 98 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 99 | template<MethodID::Type type> |
| 100 | jmethodID MethodID::Get(JNIEnv* env, |
| 101 | jclass clazz, |
| 102 | const char* method_name, |
| 103 | const char* jni_signature) { |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 104 | auto get_method_ptr = type == MethodID::TYPE_STATIC ? |
| 105 | &JNIEnv::GetStaticMethodID : |
| 106 | &JNIEnv::GetMethodID; |
| 107 | jmethodID id = (env->*get_method_ptr)(clazz, method_name, jni_signature); |
yfriedman | e524fe7 | 2016-05-05 19:51:38 | [diff] [blame] | 108 | if (base::android::ClearException(env) || !id) { |
| 109 | LOG(FATAL) << "Failed to find " << |
| 110 | (type == TYPE_STATIC ? "static " : "") << |
| 111 | "method " << method_name << " " << jni_signature; |
| 112 | } |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 113 | return id; |
[email protected] | fae37d6 | 2012-03-08 12:39:13 | [diff] [blame] | 114 | } |
| 115 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 116 | // If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call |
| 117 | // into ::Get() above. If there's a race, it's ok since the values are the same |
| 118 | // (and the duplicated effort will happen only once). |
Benoit Lize | ab965bc | 2021-04-01 15:30:24 | [diff] [blame] | 119 | template <MethodID::Type type> |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 120 | jmethodID MethodID::LazyGet(JNIEnv* env, |
[email protected] | 3764dddb | 2012-10-02 21:13:55 | [diff] [blame] | 121 | jclass clazz, |
| 122 | const char* method_name, |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 123 | const char* jni_signature, |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 124 | std::atomic<jmethodID>* atomic_method_id) { |
Benoit Lize | ab965bc | 2021-04-01 15:30:24 | [diff] [blame] | 125 | const jmethodID value = atomic_method_id->load(std::memory_order_acquire); |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 126 | if (value) |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 127 | return value; |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 128 | jmethodID id = MethodID::Get<type>(env, clazz, method_name, jni_signature); |
Benoit Lize | ab965bc | 2021-04-01 15:30:24 | [diff] [blame] | 129 | atomic_method_id->store(id, std::memory_order_release); |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 130 | return id; |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 131 | } |
| 132 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 133 | // Various template instantiations. |
| 134 | template jmethodID MethodID::Get<MethodID::TYPE_STATIC>( |
| 135 | JNIEnv* env, jclass clazz, const char* method_name, |
| 136 | const char* jni_signature); |
[email protected] | fae37d6 | 2012-03-08 12:39:13 | [diff] [blame] | 137 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 138 | template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>( |
| 139 | JNIEnv* env, jclass clazz, const char* method_name, |
| 140 | const char* jni_signature); |
[email protected] | 3764dddb | 2012-10-02 21:13:55 | [diff] [blame] | 141 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 142 | template jmethodID MethodID::LazyGet<MethodID::TYPE_STATIC>( |
| 143 | JNIEnv* env, jclass clazz, const char* method_name, |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 144 | const char* jni_signature, std::atomic<jmethodID>* atomic_method_id); |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 145 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 146 | template jmethodID MethodID::LazyGet<MethodID::TYPE_INSTANCE>( |
| 147 | JNIEnv* env, jclass clazz, const char* method_name, |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 148 | const char* jni_signature, std::atomic<jmethodID>* atomic_method_id); |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 149 | |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 150 | |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 151 | void CheckException(JNIEnv* env) { |
Sam Maier | 93fb2278 | 2024-01-09 16:39:25 | [diff] [blame] | 152 | if (!jni_zero::HasException(env)) { |
[email protected] | 909193c | 2014-06-28 01:58:04 | [diff] [blame] | 153 | return; |
Sam Maier | 93fb2278 | 2024-01-09 16:39:25 | [diff] [blame] | 154 | } |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 155 | |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 156 | static thread_local bool g_reentering = false; |
| 157 | if (g_reentering) { |
| 158 | // We were handling an uncaught Java exception already, but one of the Java |
| 159 | // methods we called below threw another exception. (This is unlikely to |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 160 | // happen, as we are careful to never throw from these methods, but we |
| 161 | // can't rule it out entirely. E.g. an OutOfMemoryError when constructing |
| 162 | // the jstring for the return value of |
| 163 | // sanitizedStacktraceForUnhandledException(). |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 164 | env->ExceptionDescribe(); |
| 165 | jthrowable raw_throwable = env->ExceptionOccurred(); |
| 166 | env->ExceptionClear(); |
| 167 | jclass clazz = env->GetObjectClass(raw_throwable); |
| 168 | bool is_oom_error = env->IsSameObject(clazz, g_out_of_memory_error_class); |
| 169 | env->Throw(raw_throwable); // Ensure we don't re-enter Java. |
| 170 | |
| 171 | if (is_oom_error) { |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 172 | base::android::SetJavaException(kReetrantOutOfMemoryMessage); |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 173 | // Use different LOG(FATAL) statements to ensure unique stack traces. |
Andrew Grieve | 378282f | 2024-01-10 16:18:49 | [diff] [blame] | 174 | if (g_log_fatal_callback_for_testing) { |
| 175 | g_log_fatal_callback_for_testing(kReetrantOutOfMemoryMessage); |
| 176 | } else { |
| 177 | LOG(FATAL) << kReetrantOutOfMemoryMessage; |
| 178 | } |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 179 | } else { |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 180 | base::android::SetJavaException(kReetrantExceptionMessage); |
Andrew Grieve | 378282f | 2024-01-10 16:18:49 | [diff] [blame] | 181 | if (g_log_fatal_callback_for_testing) { |
| 182 | g_log_fatal_callback_for_testing(kReetrantExceptionMessage); |
| 183 | } else { |
| 184 | LOG(FATAL) << kReetrantExceptionMessage; |
| 185 | } |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 186 | } |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 187 | // Needed for tests, which do not terminate from LOG(FATAL). |
| 188 | return; |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 189 | } |
| 190 | g_reentering = true; |
| 191 | |
| 192 | // Log a message to ensure there is something in the log even if the rest of |
| 193 | // this function goes horribly wrong, and also to provide a convenient marker |
| 194 | // in the log for where Java exception crash information starts. |
| 195 | LOG(ERROR) << "Crashing due to uncaught Java exception"; |
| 196 | |
| 197 | const bool handle_exception_in_java = |
| 198 | base::FeatureList::IsEnabled(kHandleExceptionsInJava); |
| 199 | |
| 200 | if (!handle_exception_in_java) { |
| 201 | env->ExceptionDescribe(); |
| 202 | } |
| 203 | |
Etienne Dechamps | 0ff37d6 | 2023-11-02 17:22:53 | [diff] [blame] | 204 | // We cannot use `ScopedJavaLocalRef` directly because that ends up calling |
| 205 | // env->GetObjectRefType() when DCHECK is on, and that call is not allowed |
| 206 | // with a pending exception according to the JNI spec. |
| 207 | jthrowable raw_throwable = env->ExceptionOccurred(); |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 208 | // Now that we saved the reference to the throwable, clear the exception. |
| 209 | // |
| 210 | // We need to do this as early as possible to remove the risk that code below |
| 211 | // might accidentally call back into Java, which is not allowed when `env` |
| 212 | // has an exception set, per the JNI spec. (For example, LOG(FATAL) doesn't |
| 213 | // work with a JNI exception set, because it calls |
| 214 | // GetJavaStackTraceIfPresent()). |
| 215 | env->ExceptionClear(); |
| 216 | // The reference returned by `ExceptionOccurred()` is a local reference. |
| 217 | // `ExceptionClear()` merely removes the exception information from `env`; |
| 218 | // it doesn't delete the reference, which is why this call is valid. |
| 219 | auto throwable = ScopedJavaLocalRef<jthrowable>::Adopt(env, raw_throwable); |
[email protected] | 909193c | 2014-06-28 01:58:04 | [diff] [blame] | 220 | |
Etienne Dechamps | 4ebe389 | 2023-11-03 18:02:12 | [diff] [blame] | 221 | if (!handle_exception_in_java) { |
| 222 | base::android::SetJavaException( |
| 223 | GetJavaExceptionInfo(env, throwable).c_str()); |
Andrew Grieve | 378282f | 2024-01-10 16:18:49 | [diff] [blame] | 224 | if (g_log_fatal_callback_for_testing) { |
| 225 | g_log_fatal_callback_for_testing(kUncaughtExceptionMessage); |
| 226 | } else { |
| 227 | LOG(FATAL) << kUncaughtExceptionMessage; |
| 228 | } |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 229 | // Needed for tests, which do not terminate from LOG(FATAL). |
| 230 | g_reentering = false; |
| 231 | return; |
Etienne Dechamps | 4ebe389 | 2023-11-03 18:02:12 | [diff] [blame] | 232 | } |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 233 | |
Etienne Dechamps | 4ebe389 | 2023-11-03 18:02:12 | [diff] [blame] | 234 | // We don't need to call SetJavaException() in this branch because we |
| 235 | // expect handleException() to eventually call JavaExceptionReporter through |
| 236 | // the global uncaught exception handler. |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 237 | |
Etienne Dechamps | e4321c2 | 2023-11-03 15:35:54 | [diff] [blame] | 238 | const std::string native_stack_trace = base::debug::StackTrace().ToString(); |
| 239 | LOG(ERROR) << "Native stack trace:" << std::endl << native_stack_trace; |
| 240 | |
Andrew Grieve | 7730935 | 2023-11-27 21:47:51 | [diff] [blame] | 241 | ScopedJavaLocalRef<jthrowable> secondary_exception = |
| 242 | Java_JniAndroid_handleException( |
| 243 | env, throwable, ConvertUTF8ToJavaString(env, native_stack_trace)); |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 244 | |
| 245 | // Ideally handleException() should have terminated the process and we should |
Andrew Grieve | 7730935 | 2023-11-27 21:47:51 | [diff] [blame] | 246 | // not get here. This can happen in the case of OutOfMemoryError or if the |
| 247 | // app that embedded WebView installed an exception handler that does not |
| 248 | // terminate, or itself threw an exception. We cannot be confident that |
| 249 | // JavaExceptionReporter ran, so set the java exception explicitly. |
| 250 | base::android::SetJavaException( |
| 251 | GetJavaExceptionInfo( |
| 252 | env, secondary_exception ? secondary_exception : throwable) |
| 253 | .c_str()); |
Andrew Grieve | 378282f | 2024-01-10 16:18:49 | [diff] [blame] | 254 | if (g_log_fatal_callback_for_testing) { |
| 255 | g_log_fatal_callback_for_testing(kUncaughtExceptionHandlerFailedMessage); |
| 256 | } else { |
| 257 | LOG(FATAL) << kUncaughtExceptionHandlerFailedMessage; |
| 258 | } |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 259 | // Needed for tests, which do not terminate from LOG(FATAL). |
| 260 | g_reentering = false; |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 261 | } |
| 262 | |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 263 | std::string GetJavaExceptionInfo(JNIEnv* env, |
| 264 | const JavaRef<jthrowable>& throwable) { |
Dan Stahr | 017ceaf | 2023-01-12 09:32:17 | [diff] [blame] | 265 | ScopedJavaLocalRef<jstring> sanitized_exception_string = |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 266 | Java_JniAndroid_sanitizedStacktraceForUnhandledException(env, throwable); |
Andrew Grieve | 0e0938a7 | 2023-11-28 14:58:27 | [diff] [blame] | 267 | // Returns null when PiiElider results in an OutOfMemoryError. |
| 268 | return sanitized_exception_string |
| 269 | ? ConvertJavaStringToUTF8(sanitized_exception_string) |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 270 | : kOomInGetJavaExceptionInfoMessage; |
cjhopman | 060e007 | 2015-05-06 21:37:48 | [diff] [blame] | 271 | } |
| 272 | |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 273 | std::string GetJavaStackTraceIfPresent() { |
| 274 | JNIEnv* env = nullptr; |
Sam Maier | cb004e13 | 2023-12-21 21:28:35 | [diff] [blame] | 275 | JavaVM* jvm = jni_zero::GetVM(); |
| 276 | if (jvm) { |
| 277 | jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2); |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 278 | } |
| 279 | if (!env) { |
| 280 | // JNI has not been initialized on this thread. |
| 281 | return {}; |
| 282 | } |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 283 | |
| 284 | if (HasException(env)) { |
| 285 | // This can happen if CheckException() is being re-entered, decided to |
| 286 | // LOG(FATAL) immediately, and LOG(FATAL) itself is calling us. In that case |
| 287 | // it is imperative that we don't try to call Java again. |
Andrew Grieve | 6230a51 | 2023-12-11 16:47:21 | [diff] [blame] | 288 | return kUnableToGetStackTraceMessage; |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 289 | } |
| 290 | |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 291 | ScopedJavaLocalRef<jthrowable> throwable = |
| 292 | JNI_Throwable::Java_Throwable_Constructor(env); |
| 293 | std::string ret = GetJavaExceptionInfo(env, throwable); |
| 294 | // Strip the exception message and leave only the "at" lines. Example: |
| 295 | // java.lang.Throwable: |
| 296 | // {tab}at Clazz.method(Clazz.java:111) |
| 297 | // {tab}at ... |
| 298 | size_t newline_idx = ret.find('\n'); |
| 299 | if (newline_idx == std::string::npos) { |
| 300 | // There are no java frames. |
| 301 | return {}; |
| 302 | } |
| 303 | return ret.substr(newline_idx + 1); |
| 304 | } |
| 305 | |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 306 | } // namespace android |
| 307 | } // namespace base |