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/debug/debugging_flags.h" 15 #include "base/lazy_instance.h" 16 #include "base/logging.h" 17 #include "base/threading/thread_local.h" 18 19 namespace { 20 using base::android::GetClass; 21 using base::android::MethodID; 22 using base::android::ScopedJavaLocalRef; 23 24 base::android::JniRegistrationType g_jni_registration_type = 25 base::android::ALL_JNI_REGISTRATION; 26 27 JavaVM* g_jvm = NULL; 28 base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject>>::Leaky 29 g_class_loader = LAZY_INSTANCE_INITIALIZER; 30 jmethodID g_class_loader_load_class_method_id = 0; 31 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 37 } // namespace 38 39 namespace base { 40 namespace android { 41 42 JniRegistrationType GetJniRegistrationType() { 43 return g_jni_registration_type; 44 } 45 46 void SetJniRegistrationType(JniRegistrationType jni_registration_type) { 47 g_jni_registration_type = jni_registration_type; 48 } 49 50 JNIEnv* AttachCurrentThread() { 51 DCHECK(g_jvm); 52 JNIEnv* env = NULL; 53 jint ret = g_jvm->AttachCurrentThread(&env, NULL); 54 DCHECK_EQ(JNI_OK, ret); 55 return env; 56 } 57 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 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. 73 if (g_jvm) 74 g_jvm->DetachCurrentThread(); 75 } 76 77 void InitVM(JavaVM* vm) { 78 DCHECK(!g_jvm || g_jvm == vm); 79 g_jvm = vm; 80 } 81 82 bool IsVMInitialized() { 83 return g_jvm != NULL; 84 } 85 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 104 ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, const char* class_name) { 105 jclass clazz; 106 if (!g_class_loader.Get().is_null()) { 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 122 clazz = static_cast<jclass>( 123 env->CallObjectMethod(g_class_loader.Get().obj(), 124 g_class_loader_load_class_method_id, 125 ConvertUTF8ToJavaString(env, dotted_name).obj())); 126 } else { 127 clazz = env->FindClass(class_name); 128 } 129 if (ClearException(env) || !clazz) { 130 LOG(FATAL) << "Failed to find class " << class_name; 131 } 132 return ScopedJavaLocalRef<jclass>(env, clazz); 133 } 134 135 jclass LazyGetClass( 136 JNIEnv* env, 137 const char* class_name, 138 base::subtle::AtomicWord* atomic_class_id) { 139 static_assert(sizeof(subtle::AtomicWord) >= sizeof(jclass), 140 "AtomicWord can't be smaller than jclass"); 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 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); 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 } 173 return id; 174 } 175 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, 181 jclass clazz, 182 const char* method_name, 183 const char* jni_signature, 184 base::subtle::AtomicWord* atomic_method_id) { 185 static_assert(sizeof(subtle::AtomicWord) >= sizeof(jmethodID), 186 "AtomicWord can't be smaller than jMethodID"); 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; 194 } 195 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); 200 201 template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>( 202 JNIEnv* env, jclass clazz, const char* method_name, 203 const char* jni_signature); 204 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); 208 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); 212 213 bool HasException(JNIEnv* env) { 214 return env->ExceptionCheck() != JNI_FALSE; 215 } 216 217 bool ClearException(JNIEnv* env) { 218 if (!HasException(env)) 219 return false; 220 env->ExceptionDescribe(); 221 env->ExceptionClear(); 222 return true; 223 } 224 225 void CheckException(JNIEnv* env) { 226 if (!HasException(env)) 227 return; 228 229 // Exception has been found, might as well tell breakpad about it. 230 jthrowable java_throwable = env->ExceptionOccurred(); 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. 238 base::android::BuildInfo::GetInstance()->SetJavaExceptionInfo( 239 GetJavaExceptionInfo(env, java_throwable)); 240 } 241 242 // Now, feel good about it and die. 243 // TODO(lhchavez): Remove this hack. See b/28814913 for details. 244 // We're using BuildInfo's java_exception_info() instead of storing the 245 // exception info a few lines above to avoid extra copies. It will be 246 // truncated to 1024 bytes anyways. 247 const char* exception_string = 248 base::android::BuildInfo::GetInstance()->java_exception_info(); 249 if (exception_string) 250 LOG(FATAL) << exception_string; 251 else 252 LOG(FATAL) << "Unhandled exception"; 253 } 254 255 std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { 256 ScopedJavaLocalRef<jclass> throwable_clazz = 257 GetClass(env, "java/lang/Throwable"); 258 jmethodID throwable_printstacktrace = 259 MethodID::Get<MethodID::TYPE_INSTANCE>( 260 env, throwable_clazz.obj(), "printStackTrace", 261 "(Ljava/io/PrintStream;)V"); 262 263 // Create an instance of ByteArrayOutputStream. 264 ScopedJavaLocalRef<jclass> bytearray_output_stream_clazz = 265 GetClass(env, "java/io/ByteArrayOutputStream"); 266 jmethodID bytearray_output_stream_constructor = 267 MethodID::Get<MethodID::TYPE_INSTANCE>( 268 env, bytearray_output_stream_clazz.obj(), "<init>", "()V"); 269 jmethodID bytearray_output_stream_tostring = 270 MethodID::Get<MethodID::TYPE_INSTANCE>( 271 env, bytearray_output_stream_clazz.obj(), "toString", 272 "()Ljava/lang/String;"); 273 ScopedJavaLocalRef<jobject> bytearray_output_stream(env, 274 env->NewObject(bytearray_output_stream_clazz.obj(), 275 bytearray_output_stream_constructor)); 276 277 // Create an instance of PrintStream. 278 ScopedJavaLocalRef<jclass> printstream_clazz = 279 GetClass(env, "java/io/PrintStream"); 280 jmethodID printstream_constructor = 281 MethodID::Get<MethodID::TYPE_INSTANCE>( 282 env, printstream_clazz.obj(), "<init>", 283 "(Ljava/io/OutputStream;)V"); 284 ScopedJavaLocalRef<jobject> printstream(env, 285 env->NewObject(printstream_clazz.obj(), printstream_constructor, 286 bytearray_output_stream.obj())); 287 288 // Call Throwable.printStackTrace(PrintStream) 289 env->CallVoidMethod(java_throwable, throwable_printstacktrace, 290 printstream.obj()); 291 292 // Call ByteArrayOutputStream.toString() 293 ScopedJavaLocalRef<jstring> exception_string( 294 env, static_cast<jstring>( 295 env->CallObjectMethod(bytearray_output_stream.obj(), 296 bytearray_output_stream_tostring))); 297 298 return ConvertJavaStringToUTF8(exception_string); 299 } 300 301 #if BUILDFLAG(ENABLE_PROFILING) && HAVE_TRACE_STACK_FRAME_POINTERS 302 303 JNIStackFrameSaver::JNIStackFrameSaver(void* current_fp) { 304 previous_fp_ = g_stack_frame_pointer.Pointer()->Get(); 305 g_stack_frame_pointer.Pointer()->Set(current_fp); 306 } 307 308 JNIStackFrameSaver::~JNIStackFrameSaver() { 309 g_stack_frame_pointer.Pointer()->Set(previous_fp_); 310 } 311 312 void* JNIStackFrameSaver::SavedFrame() { 313 return g_stack_frame_pointer.Pointer()->Get(); 314 } 315 316 #endif // ENABLE_PROFILING && HAVE_TRACE_STACK_FRAME_POINTERS 317 318 } // namespace android 319 } // namespace base 320