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