[email protected] | 9fc4416 | 2012-01-23 22:56:41 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
[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> |
| 8 | |
[email protected] | 96e7ade | 2011-12-05 14:42:08 | [diff] [blame] | 9 | #include <map> |
| 10 | |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 11 | #include "base/android/build_info.h" |
| 12 | #include "base/android/jni_string.h" |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 13 | #include "base/android/jni_utils.h" |
dskiba | a8c951e | 2016-10-25 23:39:11 | [diff] [blame] | 14 | #include "base/debug/debugging_flags.h" |
[email protected] | 96e7ade | 2011-12-05 14:42:08 | [diff] [blame] | 15 | #include "base/lazy_instance.h" |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 16 | #include "base/logging.h" |
dskiba | a8c951e | 2016-10-25 23:39:11 | [diff] [blame] | 17 | #include "base/threading/thread_local.h" |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 18 | |
| 19 | namespace { |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 20 | using base::android::GetClass; |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 21 | using base::android::MethodID; |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 22 | using base::android::ScopedJavaLocalRef; |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 23 | |
estevenson | 5b04fba | 2017-01-31 20:34:56 | [diff] [blame] | 24 | base::android::JniRegistrationType g_jni_registration_type = |
| 25 | base::android::ALL_JNI_REGISTRATION; |
torne | 103bf19 | 2015-02-20 23:06:33 | [diff] [blame] | 26 | |
[email protected] | 995a3ff | 2012-09-17 06:15:10 | [diff] [blame] | 27 | JavaVM* g_jvm = NULL; |
scottmg | 5e65e3a | 2017-03-08 08:48:46 | [diff] [blame] | 28 | base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject>>::Leaky |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 29 | g_class_loader = LAZY_INSTANCE_INITIALIZER; |
| 30 | jmethodID g_class_loader_load_class_method_id = 0; |
[email protected] | 96e7ade | 2011-12-05 14:42:08 | [diff] [blame] | 31 | |
dskiba | a8c951e | 2016-10-25 23:39:11 | [diff] [blame] | 32 | #if BUILDFLAG(ENABLE_PROFILING) && HAVE_TRACE_STACK_FRAME_POINTERS |
| 33 | base::LazyInstance<base::ThreadLocalPointer<void>>::Leaky |
| 34 | g_stack_frame_pointer = LAZY_INSTANCE_INITIALIZER; |
| 35 | #endif |
| 36 | |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 37 | } // namespace |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 38 | |
| 39 | namespace base { |
| 40 | namespace android { |
| 41 | |
estevenson | 5b04fba | 2017-01-31 20:34:56 | [diff] [blame] | 42 | JniRegistrationType GetJniRegistrationType() { |
| 43 | return g_jni_registration_type; |
torne | 103bf19 | 2015-02-20 23:06:33 | [diff] [blame] | 44 | } |
| 45 | |
estevenson | 5b04fba | 2017-01-31 20:34:56 | [diff] [blame] | 46 | void SetJniRegistrationType(JniRegistrationType jni_registration_type) { |
| 47 | g_jni_registration_type = jni_registration_type; |
torne | 103bf19 | 2015-02-20 23:06:33 | [diff] [blame] | 48 | } |
| 49 | |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 50 | JNIEnv* AttachCurrentThread() { |
[email protected] | 7a3b0e4 | 2012-10-09 19:43:10 | [diff] [blame] | 51 | DCHECK(g_jvm); |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 52 | JNIEnv* env = NULL; |
[email protected] | 691b200 | 2014-01-07 19:51:37 | [diff] [blame] | 53 | jint ret = g_jvm->AttachCurrentThread(&env, NULL); |
[email protected] | 7a3b0e4 | 2012-10-09 19:43:10 | [diff] [blame] | 54 | DCHECK_EQ(JNI_OK, ret); |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 55 | return env; |
| 56 | } |
| 57 | |
[email protected] | f3225bdb | 2014-06-20 00:38:19 | [diff] [blame] | 58 | JNIEnv* AttachCurrentThreadWithName(const std::string& thread_name) { |
| 59 | DCHECK(g_jvm); |
| 60 | JavaVMAttachArgs args; |
| 61 | args.version = JNI_VERSION_1_2; |
| 62 | args.name = thread_name.c_str(); |
| 63 | args.group = NULL; |
| 64 | JNIEnv* env = NULL; |
| 65 | jint ret = g_jvm->AttachCurrentThread(&env, &args); |
| 66 | DCHECK_EQ(JNI_OK, ret); |
| 67 | return env; |
| 68 | } |
| 69 | |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 70 | void DetachFromVM() { |
| 71 | // Ignore the return value, if the thread is not attached, DetachCurrentThread |
| 72 | // 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] | 73 | if (g_jvm) |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 74 | g_jvm->DetachCurrentThread(); |
| 75 | } |
| 76 | |
| 77 | void InitVM(JavaVM* vm) { |
michaelbai | 25ec25c | 2015-10-22 19:40:22 | [diff] [blame] | 78 | DCHECK(!g_jvm || g_jvm == vm); |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 79 | g_jvm = vm; |
| 80 | } |
| 81 | |
[email protected] | 2e94463 | 2013-08-21 17:59:42 | [diff] [blame] | 82 | bool IsVMInitialized() { |
| 83 | return g_jvm != NULL; |
| 84 | } |
| 85 | |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 86 | void InitReplacementClassLoader(JNIEnv* env, |
| 87 | const JavaRef<jobject>& class_loader) { |
| 88 | DCHECK(g_class_loader.Get().is_null()); |
| 89 | DCHECK(!class_loader.is_null()); |
| 90 | |
| 91 | ScopedJavaLocalRef<jclass> class_loader_clazz = |
| 92 | GetClass(env, "java/lang/ClassLoader"); |
| 93 | CHECK(!ClearException(env)); |
| 94 | g_class_loader_load_class_method_id = |
| 95 | env->GetMethodID(class_loader_clazz.obj(), |
| 96 | "loadClass", |
| 97 | "(Ljava/lang/String;)Ljava/lang/Class;"); |
| 98 | CHECK(!ClearException(env)); |
| 99 | |
| 100 | DCHECK(env->IsInstanceOf(class_loader.obj(), class_loader_clazz.obj())); |
| 101 | g_class_loader.Get().Reset(class_loader); |
| 102 | } |
| 103 | |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 104 | ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, const char* class_name) { |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 105 | jclass clazz; |
| 106 | if (!g_class_loader.Get().is_null()) { |
torne | c16354c | 2015-03-16 22:53:33 | [diff] [blame] | 107 | // ClassLoader.loadClass expects a classname with components separated by |
| 108 | // dots instead of the slashes that JNIEnv::FindClass expects. The JNI |
| 109 | // generator generates names with slashes, so we have to replace them here. |
| 110 | // TODO(torne): move to an approach where we always use ClassLoader except |
| 111 | // for the special case of base::android::GetClassLoader(), and change the |
| 112 | // JNI generator to generate dot-separated names. http://crbug.com/461773 |
| 113 | size_t bufsize = strlen(class_name) + 1; |
| 114 | char dotted_name[bufsize]; |
| 115 | memmove(dotted_name, class_name, bufsize); |
| 116 | for (size_t i = 0; i < bufsize; ++i) { |
| 117 | if (dotted_name[i] == '/') { |
| 118 | dotted_name[i] = '.'; |
| 119 | } |
| 120 | } |
| 121 | |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 122 | clazz = static_cast<jclass>( |
| 123 | env->CallObjectMethod(g_class_loader.Get().obj(), |
| 124 | g_class_loader_load_class_method_id, |
torne | c16354c | 2015-03-16 22:53:33 | [diff] [blame] | 125 | ConvertUTF8ToJavaString(env, dotted_name).obj())); |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 126 | } else { |
| 127 | clazz = env->FindClass(class_name); |
| 128 | } |
yfriedman | e524fe7 | 2016-05-05 19:51:38 | [diff] [blame] | 129 | if (ClearException(env) || !clazz) { |
| 130 | LOG(FATAL) << "Failed to find class " << class_name; |
| 131 | } |
[email protected] | c7c2b46 | 2013-04-10 02:44:55 | [diff] [blame] | 132 | return ScopedJavaLocalRef<jclass>(env, clazz); |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 133 | } |
| 134 | |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 135 | jclass LazyGetClass( |
| 136 | JNIEnv* env, |
| 137 | const char* class_name, |
| 138 | base::subtle::AtomicWord* atomic_class_id) { |
avi | 4ec0dff | 2015-11-24 14:26:24 | [diff] [blame] | 139 | static_assert(sizeof(subtle::AtomicWord) >= sizeof(jclass), |
| 140 | "AtomicWord can't be smaller than jclass"); |
[email protected] | d30dd6e | 2014-08-21 16:37:32 | [diff] [blame] | 141 | subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_class_id); |
| 142 | if (value) |
| 143 | return reinterpret_cast<jclass>(value); |
| 144 | ScopedJavaGlobalRef<jclass> clazz; |
| 145 | clazz.Reset(GetClass(env, class_name)); |
| 146 | subtle::AtomicWord null_aw = reinterpret_cast<subtle::AtomicWord>(NULL); |
| 147 | subtle::AtomicWord cas_result = base::subtle::Release_CompareAndSwap( |
| 148 | atomic_class_id, |
| 149 | null_aw, |
| 150 | reinterpret_cast<subtle::AtomicWord>(clazz.obj())); |
| 151 | if (cas_result == null_aw) { |
| 152 | // We intentionally leak the global ref since we now storing it as a raw |
| 153 | // pointer in |atomic_class_id|. |
| 154 | return clazz.Release(); |
| 155 | } else { |
| 156 | return reinterpret_cast<jclass>(cas_result); |
| 157 | } |
| 158 | } |
| 159 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 160 | template<MethodID::Type type> |
| 161 | jmethodID MethodID::Get(JNIEnv* env, |
| 162 | jclass clazz, |
| 163 | const char* method_name, |
| 164 | const char* jni_signature) { |
| 165 | jmethodID id = type == TYPE_STATIC ? |
| 166 | env->GetStaticMethodID(clazz, method_name, jni_signature) : |
| 167 | env->GetMethodID(clazz, method_name, jni_signature); |
yfriedman | e524fe7 | 2016-05-05 19:51:38 | [diff] [blame] | 168 | if (base::android::ClearException(env) || !id) { |
| 169 | LOG(FATAL) << "Failed to find " << |
| 170 | (type == TYPE_STATIC ? "static " : "") << |
| 171 | "method " << method_name << " " << jni_signature; |
| 172 | } |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 173 | return id; |
[email protected] | fae37d6 | 2012-03-08 12:39:13 | [diff] [blame] | 174 | } |
| 175 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 176 | // If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call |
| 177 | // into ::Get() above. If there's a race, it's ok since the values are the same |
| 178 | // (and the duplicated effort will happen only once). |
| 179 | template<MethodID::Type type> |
| 180 | jmethodID MethodID::LazyGet(JNIEnv* env, |
[email protected] | 3764dddb | 2012-10-02 21:13:55 | [diff] [blame] | 181 | jclass clazz, |
| 182 | const char* method_name, |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 183 | const char* jni_signature, |
| 184 | base::subtle::AtomicWord* atomic_method_id) { |
avi | 4ec0dff | 2015-11-24 14:26:24 | [diff] [blame] | 185 | static_assert(sizeof(subtle::AtomicWord) >= sizeof(jmethodID), |
| 186 | "AtomicWord can't be smaller than jMethodID"); |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 187 | subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_method_id); |
| 188 | if (value) |
| 189 | return reinterpret_cast<jmethodID>(value); |
| 190 | jmethodID id = MethodID::Get<type>(env, clazz, method_name, jni_signature); |
| 191 | base::subtle::Release_Store( |
| 192 | atomic_method_id, reinterpret_cast<subtle::AtomicWord>(id)); |
| 193 | return id; |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 194 | } |
| 195 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 196 | // Various template instantiations. |
| 197 | template jmethodID MethodID::Get<MethodID::TYPE_STATIC>( |
| 198 | JNIEnv* env, jclass clazz, const char* method_name, |
| 199 | const char* jni_signature); |
[email protected] | fae37d6 | 2012-03-08 12:39:13 | [diff] [blame] | 200 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 201 | template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>( |
| 202 | JNIEnv* env, jclass clazz, const char* method_name, |
| 203 | const char* jni_signature); |
[email protected] | 3764dddb | 2012-10-02 21:13:55 | [diff] [blame] | 204 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 205 | template jmethodID MethodID::LazyGet<MethodID::TYPE_STATIC>( |
| 206 | JNIEnv* env, jclass clazz, const char* method_name, |
| 207 | const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 208 | |
[email protected] | c410a2cb | 2012-10-16 18:35:10 | [diff] [blame] | 209 | template jmethodID MethodID::LazyGet<MethodID::TYPE_INSTANCE>( |
| 210 | JNIEnv* env, jclass clazz, const char* method_name, |
| 211 | const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 212 | |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 213 | bool HasException(JNIEnv* env) { |
| 214 | return env->ExceptionCheck() != JNI_FALSE; |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 215 | } |
| 216 | |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 217 | bool ClearException(JNIEnv* env) { |
| 218 | if (!HasException(env)) |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 219 | return false; |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 220 | env->ExceptionDescribe(); |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 221 | env->ExceptionClear(); |
| 222 | return true; |
| 223 | } |
| 224 | |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 225 | void CheckException(JNIEnv* env) { |
[email protected] | 909193c | 2014-06-28 01:58:04 | [diff] [blame] | 226 | if (!HasException(env)) |
| 227 | return; |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 228 | |
[email protected] | 995a3ff | 2012-09-17 06:15:10 | [diff] [blame] | 229 | // Exception has been found, might as well tell breakpad about it. |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 230 | jthrowable java_throwable = env->ExceptionOccurred(); |
[email protected] | 909193c | 2014-06-28 01:58:04 | [diff] [blame] | 231 | if (java_throwable) { |
| 232 | // Clear the pending exception, since a local reference is now held. |
| 233 | env->ExceptionDescribe(); |
| 234 | env->ExceptionClear(); |
| 235 | |
| 236 | // Set the exception_string in BuildInfo so that breakpad can read it. |
| 237 | // RVO should avoid any extra copies of the exception string. |
cjhopman | 060e007 | 2015-05-06 21:37:48 | [diff] [blame] | 238 | base::android::BuildInfo::GetInstance()->SetJavaExceptionInfo( |
[email protected] | 909193c | 2014-06-28 01:58:04 | [diff] [blame] | 239 | GetJavaExceptionInfo(env, java_throwable)); |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 240 | } |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 241 | |
[email protected] | 1b46a53 | 2012-05-23 05:59:49 | [diff] [blame] | 242 | // Now, feel good about it and die. |
yfriedman | e524fe7 | 2016-05-05 19:51:38 | [diff] [blame] | 243 | LOG(FATAL) << "Please include Java exception stack in crash report"; |
[email protected] | fe0f1ab | 2012-02-09 21:02:27 | [diff] [blame] | 244 | } |
| 245 | |
cjhopman | 060e007 | 2015-05-06 21:37:48 | [diff] [blame] | 246 | std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { |
| 247 | ScopedJavaLocalRef<jclass> throwable_clazz = |
| 248 | GetClass(env, "java/lang/Throwable"); |
| 249 | jmethodID throwable_printstacktrace = |
| 250 | MethodID::Get<MethodID::TYPE_INSTANCE>( |
| 251 | env, throwable_clazz.obj(), "printStackTrace", |
| 252 | "(Ljava/io/PrintStream;)V"); |
| 253 | |
| 254 | // Create an instance of ByteArrayOutputStream. |
| 255 | ScopedJavaLocalRef<jclass> bytearray_output_stream_clazz = |
| 256 | GetClass(env, "java/io/ByteArrayOutputStream"); |
| 257 | jmethodID bytearray_output_stream_constructor = |
| 258 | MethodID::Get<MethodID::TYPE_INSTANCE>( |
| 259 | env, bytearray_output_stream_clazz.obj(), "<init>", "()V"); |
| 260 | jmethodID bytearray_output_stream_tostring = |
| 261 | MethodID::Get<MethodID::TYPE_INSTANCE>( |
| 262 | env, bytearray_output_stream_clazz.obj(), "toString", |
| 263 | "()Ljava/lang/String;"); |
| 264 | ScopedJavaLocalRef<jobject> bytearray_output_stream(env, |
| 265 | env->NewObject(bytearray_output_stream_clazz.obj(), |
| 266 | bytearray_output_stream_constructor)); |
| 267 | |
| 268 | // Create an instance of PrintStream. |
| 269 | ScopedJavaLocalRef<jclass> printstream_clazz = |
| 270 | GetClass(env, "java/io/PrintStream"); |
| 271 | jmethodID printstream_constructor = |
| 272 | MethodID::Get<MethodID::TYPE_INSTANCE>( |
| 273 | env, printstream_clazz.obj(), "<init>", |
| 274 | "(Ljava/io/OutputStream;)V"); |
| 275 | ScopedJavaLocalRef<jobject> printstream(env, |
| 276 | env->NewObject(printstream_clazz.obj(), printstream_constructor, |
| 277 | bytearray_output_stream.obj())); |
| 278 | |
| 279 | // Call Throwable.printStackTrace(PrintStream) |
| 280 | env->CallVoidMethod(java_throwable, throwable_printstacktrace, |
| 281 | printstream.obj()); |
| 282 | |
| 283 | // Call ByteArrayOutputStream.toString() |
| 284 | ScopedJavaLocalRef<jstring> exception_string( |
| 285 | env, static_cast<jstring>( |
| 286 | env->CallObjectMethod(bytearray_output_stream.obj(), |
| 287 | bytearray_output_stream_tostring))); |
| 288 | |
| 289 | return ConvertJavaStringToUTF8(exception_string); |
| 290 | } |
| 291 | |
dskiba | a8c951e | 2016-10-25 23:39:11 | [diff] [blame] | 292 | #if BUILDFLAG(ENABLE_PROFILING) && HAVE_TRACE_STACK_FRAME_POINTERS |
| 293 | |
| 294 | JNIStackFrameSaver::JNIStackFrameSaver(void* current_fp) { |
| 295 | previous_fp_ = g_stack_frame_pointer.Pointer()->Get(); |
| 296 | g_stack_frame_pointer.Pointer()->Set(current_fp); |
| 297 | } |
| 298 | |
| 299 | JNIStackFrameSaver::~JNIStackFrameSaver() { |
| 300 | g_stack_frame_pointer.Pointer()->Set(previous_fp_); |
| 301 | } |
| 302 | |
| 303 | void* JNIStackFrameSaver::SavedFrame() { |
| 304 | return g_stack_frame_pointer.Pointer()->Get(); |
| 305 | } |
| 306 | |
| 307 | #endif // ENABLE_PROFILING && HAVE_TRACE_STACK_FRAME_POINTERS |
cjhopman | 060e007 | 2015-05-06 21:37:48 | [diff] [blame] | 308 | |
[email protected] | 61c86c6 | 2011-08-02 16:11:16 | [diff] [blame] | 309 | } // namespace android |
| 310 | } // namespace base |