jni_android.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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
15namespace {
16using base::android::GetClass;
17using base::android::MethodID;
18using base::android::ScopedJavaLocalRef;
19
20JavaVM* 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.
23base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject> >::Leaky
24    g_application_context = LAZY_INSTANCE_INITIALIZER;
25base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject> >::Leaky
26    g_class_loader = LAZY_INSTANCE_INITIALIZER;
27jmethodID g_class_loader_load_class_method_id = 0;
28
29std::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
77namespace base {
78namespace android {
79
80JNIEnv* 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
88JNIEnv* 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
100void 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
107void InitVM(JavaVM* vm) {
108  DCHECK(!g_jvm);
109  g_jvm = vm;
110}
111
112bool IsVMInitialized() {
113  return g_jvm != NULL;
114}
115
116void 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
125void 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
143const jobject GetApplicationContext() {
144  DCHECK(!g_application_context.Get().is_null());
145  return g_application_context.Get().obj();
146}
147
148ScopedJavaLocalRef<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
162jclass 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
187template<MethodID::Type type>
188jmethodID 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).
205template<MethodID::Type type>
206jmethodID 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.
223template jmethodID MethodID::Get<MethodID::TYPE_STATIC>(
224    JNIEnv* env, jclass clazz, const char* method_name,
225    const char* jni_signature);
226
227template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>(
228    JNIEnv* env, jclass clazz, const char* method_name,
229    const char* jni_signature);
230
231template 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
235template 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
239bool HasException(JNIEnv* env) {
240  return env->ExceptionCheck() != JNI_FALSE;
241}
242
243bool ClearException(JNIEnv* env) {
244  if (!HasException(env))
245    return false;
246  env->ExceptionDescribe();
247  env->ExceptionClear();
248  return true;
249}
250
251void 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