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