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