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