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/lazy_instance.h" 12 #include "base/logging.h" 13 #include "base/threading/platform_thread.h" 14 15 namespace { 16 using base::android::GetClass; 17 using base::android::MethodID; 18 using base::android::ScopedJavaLocalRef; 19 20 struct MethodIdentifier { 21 const char* class_name; 22 const char* method; 23 const char* jni_signature; 24 25 bool operator<(const MethodIdentifier& other) const { 26 int r = strcmp(class_name, other.class_name); 27 if (r < 0) { 28 return true; 29 } else if (r > 0) { 30 return false; 31 } 32 33 r = strcmp(method, other.method); 34 if (r < 0) { 35 return true; 36 } else if (r > 0) { 37 return false; 38 } 39 40 return strcmp(jni_signature, other.jni_signature) < 0; 41 } 42 }; 43 44 typedef std::map<MethodIdentifier, jmethodID> MethodIDMap; 45 46 const base::subtle::AtomicWord kUnlocked = 0; 47 const base::subtle::AtomicWord kLocked = 1; 48 base::subtle::AtomicWord g_method_id_map_lock = kUnlocked; 49 JavaVM* g_jvm = NULL; 50 // Leak the global app context, as it is used from a non-joinable worker thread 51 // that may still be running at shutdown. There is no harm in doing this. 52 base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject> >::Leaky 53 g_application_context = LAZY_INSTANCE_INITIALIZER; 54 base::LazyInstance<MethodIDMap> g_method_id_map = LAZY_INSTANCE_INITIALIZER; 55 56 std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { 57 ScopedJavaLocalRef<jclass> throwable_clazz = 58 GetClass(env, "java/lang/Throwable"); 59 jmethodID throwable_printstacktrace = 60 MethodID::Get<MethodID::TYPE_INSTANCE>( 61 env, throwable_clazz.obj(), "printStackTrace", 62 "(Ljava/io/PrintStream;)V"); 63 64 // Create an instance of ByteArrayOutputStream. 65 ScopedJavaLocalRef<jclass> bytearray_output_stream_clazz = 66 GetClass(env, "java/io/ByteArrayOutputStream"); 67 jmethodID bytearray_output_stream_constructor = 68 MethodID::Get<MethodID::TYPE_INSTANCE>( 69 env, bytearray_output_stream_clazz.obj(), "<init>", "()V"); 70 jmethodID bytearray_output_stream_tostring = 71 MethodID::Get<MethodID::TYPE_INSTANCE>( 72 env, bytearray_output_stream_clazz.obj(), "toString", 73 "()Ljava/lang/String;"); 74 ScopedJavaLocalRef<jobject> bytearray_output_stream(env, 75 env->NewObject(bytearray_output_stream_clazz.obj(), 76 bytearray_output_stream_constructor)); 77 78 // Create an instance of PrintStream. 79 ScopedJavaLocalRef<jclass> printstream_clazz = 80 GetClass(env, "java/io/PrintStream"); 81 jmethodID printstream_constructor = 82 MethodID::Get<MethodID::TYPE_INSTANCE>( 83 env, printstream_clazz.obj(), "<init>", 84 "(Ljava/io/OutputStream;)V"); 85 ScopedJavaLocalRef<jobject> printstream(env, 86 env->NewObject(printstream_clazz.obj(), printstream_constructor, 87 bytearray_output_stream.obj())); 88 89 // Call Throwable.printStackTrace(PrintStream) 90 env->CallVoidMethod(java_throwable, throwable_printstacktrace, 91 printstream.obj()); 92 93 // Call ByteArrayOutputStream.toString() 94 ScopedJavaLocalRef<jstring> exception_string( 95 env, static_cast<jstring>( 96 env->CallObjectMethod(bytearray_output_stream.obj(), 97 bytearray_output_stream_tostring))); 98 99 return ConvertJavaStringToUTF8(exception_string); 100 } 101 102 } // namespace 103 104 namespace base { 105 namespace android { 106 107 JNIEnv* AttachCurrentThread() { 108 DCHECK(g_jvm); 109 JNIEnv* env = NULL; 110 jint ret = g_jvm->AttachCurrentThread(&env, NULL); 111 DCHECK_EQ(JNI_OK, ret); 112 return env; 113 } 114 115 void DetachFromVM() { 116 // Ignore the return value, if the thread is not attached, DetachCurrentThread 117 // will fail. But it is ok as the native thread may never be attached. 118 if (g_jvm) 119 g_jvm->DetachCurrentThread(); 120 } 121 122 void InitVM(JavaVM* vm) { 123 DCHECK(!g_jvm); 124 g_jvm = vm; 125 } 126 127 void InitApplicationContext(JNIEnv* env, const JavaRef<jobject>& context) { 128 if (env->IsSameObject(g_application_context.Get().obj(), context.obj())) { 129 // It's safe to set the context more than once if it's the same context. 130 return; 131 } 132 DCHECK(g_application_context.Get().is_null()); 133 g_application_context.Get().Reset(context); 134 } 135 136 const jobject GetApplicationContext() { 137 DCHECK(!g_application_context.Get().is_null()); 138 return g_application_context.Get().obj(); 139 } 140 141 ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, const char* class_name) { 142 jclass clazz = env->FindClass(class_name); 143 CHECK(!ClearException(env) && clazz) << "Failed to find class " << class_name; 144 return ScopedJavaLocalRef<jclass>(env, clazz); 145 } 146 147 bool HasClass(JNIEnv* env, const char* class_name) { 148 ScopedJavaLocalRef<jclass> clazz(env, env->FindClass(class_name)); 149 if (!clazz.obj()) { 150 ClearException(env); 151 return false; 152 } 153 bool error = ClearException(env); 154 DCHECK(!error); 155 return true; 156 } 157 158 template<MethodID::Type type> 159 jmethodID MethodID::Get(JNIEnv* env, 160 jclass clazz, 161 const char* method_name, 162 const char* jni_signature) { 163 jmethodID id = type == TYPE_STATIC ? 164 env->GetStaticMethodID(clazz, method_name, jni_signature) : 165 env->GetMethodID(clazz, method_name, jni_signature); 166 CHECK(base::android::ClearException(env) || id) << 167 "Failed to find " << 168 (type == TYPE_STATIC ? "static " : "") << 169 "method " << method_name << " " << jni_signature; 170 return id; 171 } 172 173 // If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call 174 // into ::Get() above. If there's a race, it's ok since the values are the same 175 // (and the duplicated effort will happen only once). 176 template<MethodID::Type type> 177 jmethodID MethodID::LazyGet(JNIEnv* env, 178 jclass clazz, 179 const char* method_name, 180 const char* jni_signature, 181 base::subtle::AtomicWord* atomic_method_id) { 182 COMPILE_ASSERT(sizeof(subtle::AtomicWord) >= sizeof(jmethodID), 183 AtomicWord_SmallerThan_jMethodID); 184 subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_method_id); 185 if (value) 186 return reinterpret_cast<jmethodID>(value); 187 jmethodID id = MethodID::Get<type>(env, clazz, method_name, jni_signature); 188 base::subtle::Release_Store( 189 atomic_method_id, reinterpret_cast<subtle::AtomicWord>(id)); 190 return id; 191 } 192 193 // Various template instantiations. 194 template jmethodID MethodID::Get<MethodID::TYPE_STATIC>( 195 JNIEnv* env, jclass clazz, const char* method_name, 196 const char* jni_signature); 197 198 template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>( 199 JNIEnv* env, jclass clazz, const char* method_name, 200 const char* jni_signature); 201 202 template jmethodID MethodID::LazyGet<MethodID::TYPE_STATIC>( 203 JNIEnv* env, jclass clazz, const char* method_name, 204 const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); 205 206 template jmethodID MethodID::LazyGet<MethodID::TYPE_INSTANCE>( 207 JNIEnv* env, jclass clazz, const char* method_name, 208 const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); 209 210 jfieldID GetFieldID(JNIEnv* env, 211 const JavaRef<jclass>& clazz, 212 const char* field_name, 213 const char* jni_signature) { 214 jfieldID field_id = env->GetFieldID(clazz.obj(), field_name, jni_signature); 215 CHECK(!ClearException(env) && field_id) << "Failed to find field " << 216 field_name << " " << jni_signature; 217 return field_id; 218 } 219 220 bool HasField(JNIEnv* env, 221 const JavaRef<jclass>& clazz, 222 const char* field_name, 223 const char* jni_signature) { 224 jfieldID field_id = env->GetFieldID(clazz.obj(), field_name, jni_signature); 225 if (!field_id) { 226 ClearException(env); 227 return false; 228 } 229 bool error = ClearException(env); 230 DCHECK(!error); 231 return true; 232 } 233 234 jfieldID GetStaticFieldID(JNIEnv* env, 235 const JavaRef<jclass>& clazz, 236 const char* field_name, 237 const char* jni_signature) { 238 jfieldID field_id = 239 env->GetStaticFieldID(clazz.obj(), field_name, jni_signature); 240 CHECK(!ClearException(env) && field_id) << "Failed to find static field " << 241 field_name << " " << jni_signature; 242 return field_id; 243 } 244 245 jmethodID GetMethodIDFromClassName(JNIEnv* env, 246 const char* class_name, 247 const char* method, 248 const char* jni_signature) { 249 MethodIdentifier key; 250 key.class_name = class_name; 251 key.method = method; 252 key.jni_signature = jni_signature; 253 254 MethodIDMap* map = g_method_id_map.Pointer(); 255 bool found = false; 256 257 while (base::subtle::Acquire_CompareAndSwap(&g_method_id_map_lock, 258 kUnlocked, 259 kLocked) != kUnlocked) { 260 base::PlatformThread::YieldCurrentThread(); 261 } 262 MethodIDMap::const_iterator iter = map->find(key); 263 if (iter != map->end()) { 264 found = true; 265 } 266 base::subtle::Release_Store(&g_method_id_map_lock, kUnlocked); 267 268 // Addition to the map does not invalidate this iterator. 269 if (found) { 270 return iter->second; 271 } 272 273 ScopedJavaLocalRef<jclass> clazz(env, env->FindClass(class_name)); 274 jmethodID id = MethodID::Get<MethodID::TYPE_INSTANCE>( 275 env, clazz.obj(), method, jni_signature); 276 277 while (base::subtle::Acquire_CompareAndSwap(&g_method_id_map_lock, 278 kUnlocked, 279 kLocked) != kUnlocked) { 280 base::PlatformThread::YieldCurrentThread(); 281 } 282 // Another thread may have populated the map already. 283 std::pair<MethodIDMap::const_iterator, bool> result = 284 map->insert(std::make_pair(key, id)); 285 DCHECK_EQ(id, result.first->second); 286 base::subtle::Release_Store(&g_method_id_map_lock, kUnlocked); 287 288 return id; 289 } 290 291 bool HasException(JNIEnv* env) { 292 return env->ExceptionCheck() != JNI_FALSE; 293 } 294 295 bool ClearException(JNIEnv* env) { 296 if (!HasException(env)) 297 return false; 298 env->ExceptionDescribe(); 299 env->ExceptionClear(); 300 return true; 301 } 302 303 void CheckException(JNIEnv* env) { 304 if (!HasException(env)) return; 305 306 // Exception has been found, might as well tell breakpad about it. 307 jthrowable java_throwable = env->ExceptionOccurred(); 308 if (!java_throwable) { 309 // Do nothing but return false. 310 CHECK(false); 311 } 312 313 // Clear the pending exception, since a local reference is now held. 314 env->ExceptionDescribe(); 315 env->ExceptionClear(); 316 317 // Set the exception_string in BuildInfo so that breakpad can read it. 318 // RVO should avoid any extra copies of the exception string. 319 base::android::BuildInfo::GetInstance()->set_java_exception_info( 320 GetJavaExceptionInfo(env, java_throwable)); 321 322 // Now, feel good about it and die. 323 CHECK(false); 324 } 325 326 } // namespace android 327 } // namespace base 328