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 <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