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