Home | History | Annotate | Download | only in android
      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))
    253     return;
    254 
    255   // Exception has been found, might as well tell breakpad about it.
    256   jthrowable java_throwable = env->ExceptionOccurred();
    257   if (java_throwable) {
    258     // Clear the pending exception, since a local reference is now held.
    259     env->ExceptionDescribe();
    260     env->ExceptionClear();
    261 
    262     // Set the exception_string in BuildInfo so that breakpad can read it.
    263     // RVO should avoid any extra copies of the exception string.
    264     base::android::BuildInfo::GetInstance()->set_java_exception_info(
    265         GetJavaExceptionInfo(env, java_throwable));
    266   }
    267 
    268   // Now, feel good about it and die.
    269   CHECK(false) << "Please include Java exception stack in crash report";
    270 }
    271 
    272 }  // namespace android
    273 }  // namespace base
    274