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" |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 20 | |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 21 | namespace base { |
| 22 | namespace android { |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 23 | namespace { |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 24 | |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 25 | // If disabled, we LOG(FATAL) immediately in native code when faced with an |
| 26 | // uncaught Java exception (historical behavior). If enabled, we give the Java |
| 27 | // uncaught exception handler a chance to handle the exception first, so that |
| 28 | // the crash is (hopefully) seen as a Java crash, not a native crash. |
| 29 | // TODO(https://crbug.com/1426888): remove this switch once we are confident the |
| 30 | // new behavior is fine. |
| 31 | BASE_FEATURE(kHandleExceptionsInJava, |
| 32 | "HandleJniExceptionsInJava", |
| 33 | base::FEATURE_ENABLED_BY_DEFAULT); |
| 34 | |
Andrew Grieve | 75cd62b | 2022-10-18 20:39:50 | [diff] [blame] | 35 | JavaVM* g_jvm = nullptr; |
Andrew Grieve | 14eee89 | 2022-11-29 18:09:03 | [diff] [blame] | 36 | jobject g_class_loader = nullptr; |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 37 | jmethodID g_class_loader_load_class_method_id = 0; |
[email protected] | 96e7ade | 2011-12-05 14:42:08 | [diff] [blame] | 38 | |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 39 | ScopedJavaLocalRef<jclass> GetClassInternal(JNIEnv* env, |
| 40 | const char* class_name, |
| 41 | jobject class_loader) { |
| 42 | jclass clazz; |
| 43 | if (class_loader != nullptr) { |
| 44 | // ClassLoader.loadClass expects a classname with components separated by |
| 45 | // dots instead of the slashes that JNIEnv::FindClass expects. The JNI |
| 46 | // generator generates names with slashes, so we have to replace them here. |
| 47 | // TODO(torne): move to an approach where we always use ClassLoader except |
| 48 | // for the special case of base::android::GetClassLoader(), and change the |
| 49 | // JNI generator to generate dot-separated names. http://crbug.com/461773 |
| 50 | size_t bufsize = strlen(class_name) + 1; |
| 51 | char dotted_name[bufsize]; |
| 52 | memmove(dotted_name, class_name, bufsize); |
| 53 | for (size_t i = 0; i < bufsize; ++i) { |
| 54 | if (dotted_name[i] == '/') { |
| 55 | dotted_name[i] = '.'; |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | clazz = static_cast<jclass>( |
| 60 | env->CallObjectMethod(class_loader, g_class_loader_load_class_method_id, |
| 61 | ConvertUTF8ToJavaString(env, dotted_name).obj())); |
| 62 | } else { |
| 63 | clazz = env->FindClass(class_name); |
| 64 | } |
| 65 | if (ClearException(env) || !clazz) { |
| 66 | LOG(FATAL) << "Failed to find class " << class_name; |
| 67 | } |
| 68 | return ScopedJavaLocalRef<jclass>(env, clazz); |
| 69 | } |
| 70 | |
| 71 | } // namespace |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 72 | |
| 73 | JNIEnv* AttachCurrentThread() { |
[email protected] | 7a3b0e4 | 2012-10-09 19:43:10 | [diff] [blame] | 74 | DCHECK(g_jvm); |
erikchen | 011ad3f | 2018-01-26 17:54:55 | [diff] [blame] | 75 | JNIEnv* env = nullptr; |
| 76 | jint ret = g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2); |
| 77 | if (ret == JNI_EDETACHED || !env) { |
| 78 | JavaVMAttachArgs args; |
| 79 | args.version = JNI_VERSION_1_2; |
| 80 | args.group = nullptr; |
| 81 | |
| 82 | // 16 is the maximum size for thread names on Android. |
| 83 | char thread_name[16]; |
| 84 | int err = prctl(PR_GET_NAME, thread_name); |
| 85 | if (err < 0) { |
| 86 | DPLOG(ERROR) << "prctl(PR_GET_NAME)"; |
| 87 | args.name = nullptr; |
| 88 | } else { |
| 89 | args.name = thread_name; |
| 90 | } |
| 91 | |
Andrew Grieve | 75cd62b | 2022-10-18 20:39:50 | [diff] [blame] | 92 | #if BUILDFLAG(IS_ANDROID) |
erikchen | 011ad3f | 2018-01-26 17:54:55 | [diff] [blame] | 93 | ret = g_jvm->AttachCurrentThread(&env, &args); |
Andrew Grieve | 75cd62b | 2022-10-18 20:39:50 | [diff] [blame] | 94 | #else |
| 95 | ret = g_jvm->AttachCurrentThread(reinterpret_cast<void**>(&env), &args); |
| 96 | #endif |
agrieve | 3546854 | 2018-11-30 17:59:10 | [diff] [blame] | 97 | CHECK_EQ(JNI_OK, ret); |
erikchen | 011ad3f | 2018-01-26 17:54:55 | [diff] [blame] | 98 | } |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 99 | return env; |
| 100 | } |
| 101 | |
[email protected] | f3225bdb | 2014-06-20 00:38:19 | [diff] [blame] | 102 | JNIEnv* AttachCurrentThreadWithName(const std::string& thread_name) { |
| 103 | DCHECK(g_jvm); |
| 104 | JavaVMAttachArgs args; |
| 105 | args.version = JNI_VERSION_1_2; |
Andrew Grieve | 75cd62b | 2022-10-18 20:39:50 | [diff] [blame] | 106 | args.name = const_cast<char*>(thread_name.c_str()); |
| 107 | args.group = nullptr; |
| 108 | JNIEnv* env = nullptr; |
| 109 | #if BUILDFLAG(IS_ANDROID) |
[email protected] | f3225bdb | 2014-06-20 00:38:19 | [diff] [blame] | 110 | jint ret = g_jvm->AttachCurrentThread(&env, &args); |
Andrew Grieve | 75cd62b | 2022-10-18 20:39:50 | [diff] [blame] | 111 | #else |
| 112 | jint ret = g_jvm->AttachCurrentThread(reinterpret_cast<void**>(&env), &args); |
| 113 | #endif |
agrieve | 3546854 | 2018-11-30 17:59:10 | [diff] [blame] | 114 | CHECK_EQ(JNI_OK, ret); |
[email protected] | f3225bdb | 2014-06-20 00:38:19 | [diff] [blame] | 115 | return env; |
| 116 | } |
| 117 | |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 118 | void DetachFromVM() { |
| 119 | // Ignore the return value, if the thread is not attached, DetachCurrentThread |
| 120 | // will fail. But it is ok as the native thread may never be attached. |
[email protected] | 691b200 | 2014-01-07 19:51:37 | [diff] [blame] | 121 | if (g_jvm) |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 122 | g_jvm->DetachCurrentThread(); |
| 123 | } |
| 124 | |
| 125 | void InitVM(JavaVM* vm) { |
michaelbai | 25ec25c | 2015-10-22 19:40:22 | [diff] [blame] | 126 | DCHECK(!g_jvm || g_jvm == vm); |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 127 | g_jvm = vm; |
| 128 | } |
| 129 | |
[email protected] | 2e94463 | 2013-08-21 17:59:42 | [diff] [blame] | 130 | bool IsVMInitialized() { |
Andrew Grieve | 75cd62b | 2022-10-18 20:39:50 | [diff] [blame] | 131 | return g_jvm != nullptr; |
[email protected] | 2e94463 | 2013-08-21 17:59:42 | [diff] [blame] | 132 | } |
| 133 | |
Alex Cooper | bdd9a76 | 2023-01-24 23:29:54 | [diff] [blame] | 134 | JavaVM* GetVM() { |
| 135 | return g_jvm; |
| 136 | } |
| 137 | |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 138 | void DisableJvmForTesting() { |
| 139 | g_jvm = nullptr; |
| 140 | } |
| 141 | |
Andrew Grieve | 14eee89 | 2022-11-29 18:09:03 | [diff] [blame] | 142 | void InitGlobalClassLoader(JNIEnv* env) { |
| 143 | DCHECK(g_class_loader == nullptr); |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 144 | |
| 145 | ScopedJavaLocalRef<jclass> class_loader_clazz = |
| 146 | GetClass(env, "java/lang/ClassLoader"); |
| 147 | CHECK(!ClearException(env)); |
| 148 | g_class_loader_load_class_method_id = |
| 149 | env->GetMethodID(class_loader_clazz.obj(), |
| 150 | "loadClass", |
| 151 | "(Ljava/lang/String;)Ljava/lang/Class;"); |
| 152 | CHECK(!ClearException(env)); |
| 153 | |
Andrew Grieve | 14eee89 | 2022-11-29 18:09:03 | [diff] [blame] | 154 | // GetClassLoader() caches the reference, so we do not need to wrap it in a |
| 155 | // smart pointer as well. |
| 156 | g_class_loader = GetClassLoader(env); |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 157 | } |
| 158 | |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 159 | ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, |
| 160 | const char* class_name, |
Andrew Grieve | 14eee89 | 2022-11-29 18:09:03 | [diff] [blame] | 161 | const char* split_name) { |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 162 | return GetClassInternal(env, class_name, |
Andrew Grieve | 14eee89 | 2022-11-29 18:09:03 | [diff] [blame] | 163 | GetSplitClassLoader(env, split_name)); |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 164 | } |
| 165 | |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 166 | ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, const char* class_name) { |
Andrew Grieve | 14eee89 | 2022-11-29 18:09:03 | [diff] [blame] | 167 | return GetClassInternal(env, class_name, g_class_loader); |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 168 | } |
| 169 | |
| 170 | // This is duplicated with LazyGetClass below because these are performance |
| 171 | // sensitive. |
| 172 | jclass LazyGetClass(JNIEnv* env, |
| 173 | const char* class_name, |
Andrew Grieve | 14eee89 | 2022-11-29 18:09:03 | [diff] [blame] | 174 | const char* split_name, |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 175 | std::atomic<jclass>* atomic_class_id) { |
Benoit Lize | ab965bc | 2021-04-01 15:30:24 | [diff] [blame] | 176 | const jclass value = atomic_class_id->load(std::memory_order_acquire); |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 177 | if (value) |
| 178 | return value; |
| 179 | ScopedJavaGlobalRef<jclass> clazz; |
| 180 | clazz.Reset(GetClass(env, class_name, split_name)); |
| 181 | jclass cas_result = nullptr; |
Benoit Lize | ab965bc | 2021-04-01 15:30:24 | [diff] [blame] | 182 | if (atomic_class_id->compare_exchange_strong(cas_result, clazz.obj(), |
| 183 | std::memory_order_acq_rel)) { |
Clark DuVall | c131c50 | 2020-11-26 16:23:49 | [diff] [blame] | 184 | // We intentionally leak the global ref since we now storing it as a raw |
| 185 | // pointer in |atomic_class_id|. |
| 186 | return clazz.Release(); |
| 187 | } else { |
| 188 | return cas_result; |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | // This is duplicated with LazyGetClass above because these are performance |
| 193 | // sensitive. |
| 194 | jclass LazyGetClass(JNIEnv* env, |
| 195 | const char* class_name, |
| 196 | std::atomic<jclass>* atomic_class_id) { |
Benoit Lize | ab965bc | 2021-04-01 15:30:24 | [diff] [blame] | 197 | const jclass value = atomic_class_id->load(std::memory_order_acquire); |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 198 | if (value) |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 199 | return value; |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 200 | ScopedJavaGlobalRef<jclass> clazz; |
| 201 | clazz.Reset(GetClass(env, class_name)); |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 202 | jclass cas_result = nullptr; |
Benoit Lize | ab965bc | 2021-04-01 15:30:24 | [diff] [blame] | 203 | if (atomic_class_id->compare_exchange_strong(cas_result, clazz.obj(), |
| 204 | std::memory_order_acq_rel)) { |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 205 | // We intentionally leak the global ref since we now storing it as a raw |
| 206 | // pointer in |atomic_class_id|. |
| 207 | return clazz.Release(); |
| 208 | } else { |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 209 | return cas_result; |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 210 | } |
| 211 | } |
| 212 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 213 | template<MethodID::Type type> |
| 214 | jmethodID MethodID::Get(JNIEnv* env, |
| 215 | jclass clazz, |
| 216 | const char* method_name, |
| 217 | const char* jni_signature) { |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 218 | auto get_method_ptr = type == MethodID::TYPE_STATIC ? |
| 219 | &JNIEnv::GetStaticMethodID : |
| 220 | &JNIEnv::GetMethodID; |
| 221 | jmethodID id = (env->*get_method_ptr)(clazz, method_name, jni_signature); |
yfriedman | e524fe7 | 2016-05-05 19:51:38 | [diff] [blame] | 222 | if (base::android::ClearException(env) || !id) { |
| 223 | LOG(FATAL) << "Failed to find " << |
| 224 | (type == TYPE_STATIC ? "static " : "") << |
| 225 | "method " << method_name << " " << jni_signature; |
| 226 | } |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 227 | return id; |
[email protected] | fae37d6 | 2012-03-08 12:39:13 | [diff] [blame] | 228 | } |
| 229 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 230 | // If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call |
| 231 | // into ::Get() above. If there's a race, it's ok since the values are the same |
| 232 | // (and the duplicated effort will happen only once). |
Benoit Lize | ab965bc | 2021-04-01 15:30:24 | [diff] [blame] | 233 | template <MethodID::Type type> |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 234 | jmethodID MethodID::LazyGet(JNIEnv* env, |
[email protected] | 3764dddb | 2012-10-02 21:13:55 | [diff] [blame] | 235 | jclass clazz, |
| 236 | const char* method_name, |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 237 | const char* jni_signature, |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 238 | std::atomic<jmethodID>* atomic_method_id) { |
Benoit Lize | ab965bc | 2021-04-01 15:30:24 | [diff] [blame] | 239 | const jmethodID value = atomic_method_id->load(std::memory_order_acquire); |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 240 | if (value) |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 241 | return value; |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 242 | jmethodID id = MethodID::Get<type>(env, clazz, method_name, jni_signature); |
Benoit Lize | ab965bc | 2021-04-01 15:30:24 | [diff] [blame] | 243 | atomic_method_id->store(id, std::memory_order_release); |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 244 | return id; |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 245 | } |
| 246 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 247 | // Various template instantiations. |
| 248 | template jmethodID MethodID::Get<MethodID::TYPE_STATIC>( |
| 249 | JNIEnv* env, jclass clazz, const char* method_name, |
| 250 | const char* jni_signature); |
[email protected] | fae37d6 | 2012-03-08 12:39:13 | [diff] [blame] | 251 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 252 | template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>( |
| 253 | JNIEnv* env, jclass clazz, const char* method_name, |
| 254 | const char* jni_signature); |
[email protected] | 3764dddb | 2012-10-02 21:13:55 | [diff] [blame] | 255 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 256 | template jmethodID MethodID::LazyGet<MethodID::TYPE_STATIC>( |
| 257 | JNIEnv* env, jclass clazz, const char* method_name, |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 258 | const char* jni_signature, std::atomic<jmethodID>* atomic_method_id); |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 259 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 260 | template jmethodID MethodID::LazyGet<MethodID::TYPE_INSTANCE>( |
| 261 | JNIEnv* env, jclass clazz, const char* method_name, |
Oleh Prypin | 54f75931 | 2018-08-02 14:33:32 | [diff] [blame] | 262 | const char* jni_signature, std::atomic<jmethodID>* atomic_method_id); |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 263 | |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 264 | bool HasException(JNIEnv* env) { |
| 265 | return env->ExceptionCheck() != JNI_FALSE; |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 266 | } |
| 267 | |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 268 | bool ClearException(JNIEnv* env) { |
| 269 | if (!HasException(env)) |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 270 | return false; |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 271 | env->ExceptionDescribe(); |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 272 | env->ExceptionClear(); |
| 273 | return true; |
| 274 | } |
| 275 | |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 276 | void CheckException(JNIEnv* env) { |
[email protected] | 909193c | 2014-06-28 01:58:04 | [diff] [blame] | 277 | if (!HasException(env)) |
| 278 | return; |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 279 | |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 280 | static thread_local bool g_reentering = false; |
| 281 | if (g_reentering) { |
| 282 | // We were handling an uncaught Java exception already, but one of the Java |
| 283 | // methods we called below threw another exception. (This is unlikely to |
| 284 | // happen as we are careful to never throw from these methods, but we can't |
| 285 | // rule it out entirely as the JVM itself may throw - think |
| 286 | // OutOfMemoryError, for example.) |
| 287 | // |
| 288 | // Note that just because we LOG(FATAL) here does not mean it's over - |
| 289 | // indeed LOG(FATAL) itself may attempt to call Java methods (e.g. through |
| 290 | // GetJavaStackTraceIfPresent()), and we can't let that happen because that |
| 291 | // could lead to infinite recursion. To prevent this, we deliberately |
| 292 | // refrain from calling `env->ExceptionClear()` in this case - this way, the |
| 293 | // JVM will instantly crash if called again from this thread. Such crashes |
| 294 | // are hard to troubleshoot though, so ideally attempts to call Java methods |
| 295 | // from LOG(FATAL) should guard against HasException() to provide a better |
| 296 | // message. |
| 297 | constexpr char kMessage[] = |
| 298 | "While handling an uncaught Java exception, another Java exception was " |
| 299 | "thrown (out of memory?)."; |
| 300 | base::android::SetJavaException(kMessage); |
| 301 | LOG(FATAL) << kMessage; |
| 302 | } |
| 303 | g_reentering = true; |
| 304 | |
| 305 | // Log a message to ensure there is something in the log even if the rest of |
| 306 | // this function goes horribly wrong, and also to provide a convenient marker |
| 307 | // in the log for where Java exception crash information starts. |
| 308 | LOG(ERROR) << "Crashing due to uncaught Java exception"; |
| 309 | |
| 310 | const bool handle_exception_in_java = |
| 311 | base::FeatureList::IsEnabled(kHandleExceptionsInJava); |
| 312 | |
| 313 | if (!handle_exception_in_java) { |
| 314 | env->ExceptionDescribe(); |
| 315 | } |
| 316 | |
Etienne Dechamps | 0ff37d6 | 2023-11-02 17:22:53 | [diff] [blame] | 317 | // We cannot use `ScopedJavaLocalRef` directly because that ends up calling |
| 318 | // env->GetObjectRefType() when DCHECK is on, and that call is not allowed |
| 319 | // with a pending exception according to the JNI spec. |
| 320 | jthrowable raw_throwable = env->ExceptionOccurred(); |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 321 | // Now that we saved the reference to the throwable, clear the exception. |
| 322 | // |
| 323 | // We need to do this as early as possible to remove the risk that code below |
| 324 | // might accidentally call back into Java, which is not allowed when `env` |
| 325 | // has an exception set, per the JNI spec. (For example, LOG(FATAL) doesn't |
| 326 | // work with a JNI exception set, because it calls |
| 327 | // GetJavaStackTraceIfPresent()). |
| 328 | env->ExceptionClear(); |
| 329 | // The reference returned by `ExceptionOccurred()` is a local reference. |
| 330 | // `ExceptionClear()` merely removes the exception information from `env`; |
| 331 | // it doesn't delete the reference, which is why this call is valid. |
| 332 | auto throwable = ScopedJavaLocalRef<jthrowable>::Adopt(env, raw_throwable); |
[email protected] | 909193c | 2014-06-28 01:58:04 | [diff] [blame] | 333 | |
Etienne Dechamps | 4ebe389 | 2023-11-03 18:02:12 | [diff] [blame^] | 334 | if (!handle_exception_in_java) { |
| 335 | base::android::SetJavaException( |
| 336 | GetJavaExceptionInfo(env, throwable).c_str()); |
| 337 | LOG(FATAL) |
| 338 | << "Uncaught Java exception in native code. Please include the Java " |
| 339 | "exception stack from the Android log in your crash report."; |
| 340 | } |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 341 | |
Etienne Dechamps | 4ebe389 | 2023-11-03 18:02:12 | [diff] [blame^] | 342 | // We don't need to call SetJavaException() in this branch because we |
| 343 | // expect handleException() to eventually call JavaExceptionReporter through |
| 344 | // the global uncaught exception handler. |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 345 | |
Etienne Dechamps | e4321c2 | 2023-11-03 15:35:54 | [diff] [blame] | 346 | const std::string native_stack_trace = base::debug::StackTrace().ToString(); |
| 347 | LOG(ERROR) << "Native stack trace:" << std::endl << native_stack_trace; |
| 348 | |
| 349 | Java_JniAndroid_handleException( |
| 350 | env, throwable, ConvertUTF8ToJavaString(env, native_stack_trace)); |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 351 | |
| 352 | // Ideally handleException() should have terminated the process and we should |
| 353 | // not get here. In the unlikely case it didn't, we need to do that ourselves. |
| 354 | LOG(FATAL) |
| 355 | << "Uncaught Java exception in native code, and the Java uncaught " |
| 356 | "exception handler did not terminate the process. Please include the " |
| 357 | "Java exception stack from the Android log in your crash report."; |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 358 | } |
| 359 | |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 360 | std::string GetJavaExceptionInfo(JNIEnv* env, |
| 361 | const JavaRef<jthrowable>& throwable) { |
Dan Stahr | 017ceaf | 2023-01-12 09:32:17 | [diff] [blame] | 362 | ScopedJavaLocalRef<jstring> sanitized_exception_string = |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 363 | Java_JniAndroid_sanitizedStacktraceForUnhandledException(env, throwable); |
Mohamed Heikal | 47fa6871 | 2018-11-14 23:54:44 | [diff] [blame] | 364 | |
| 365 | return ConvertJavaStringToUTF8(sanitized_exception_string); |
cjhopman | 060e007 | 2015-05-06 21:37:48 | [diff] [blame] | 366 | } |
| 367 | |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 368 | std::string GetJavaStackTraceIfPresent() { |
| 369 | JNIEnv* env = nullptr; |
| 370 | if (g_jvm) { |
| 371 | g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2); |
| 372 | } |
| 373 | if (!env) { |
| 374 | // JNI has not been initialized on this thread. |
| 375 | return {}; |
| 376 | } |
Etienne Dechamps | 8af082f8 | 2023-11-03 14:30:52 | [diff] [blame] | 377 | |
| 378 | if (HasException(env)) { |
| 379 | // This can happen if CheckException() is being re-entered, decided to |
| 380 | // LOG(FATAL) immediately, and LOG(FATAL) itself is calling us. In that case |
| 381 | // it is imperative that we don't try to call Java again. |
| 382 | return "Unable to retrieve Java caller stack trace as the exception " |
| 383 | "handler is being re-entered"; |
| 384 | } |
| 385 | |
Andrew Grieve | d03f4ca | 2023-09-20 19:20:54 | [diff] [blame] | 386 | ScopedJavaLocalRef<jthrowable> throwable = |
| 387 | JNI_Throwable::Java_Throwable_Constructor(env); |
| 388 | std::string ret = GetJavaExceptionInfo(env, throwable); |
| 389 | // Strip the exception message and leave only the "at" lines. Example: |
| 390 | // java.lang.Throwable: |
| 391 | // {tab}at Clazz.method(Clazz.java:111) |
| 392 | // {tab}at ... |
| 393 | size_t newline_idx = ret.find('\n'); |
| 394 | if (newline_idx == std::string::npos) { |
| 395 | // There are no java frames. |
| 396 | return {}; |
| 397 | } |
| 398 | return ret.substr(newline_idx + 1); |
| 399 | } |
| 400 | |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 401 | } // namespace android |
| 402 | } // namespace base |