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
17namespace {
18using base::android::GetClass;
19using base::android::MethodID;
20using base::android::ScopedJavaLocalRef;
21
22bool g_disable_manual_jni_registration = false;
23
24JavaVM* g_jvm = NULL;
25base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject> >::Leaky
26    g_class_loader = LAZY_INSTANCE_INITIALIZER;
27jmethodID g_class_loader_load_class_method_id = 0;
28
29}  // namespace
30
31namespace base {
32namespace android {
33
34bool IsManualJniRegistrationDisabled() {
35  return g_disable_manual_jni_registration;
36}
37
38void DisableManualJniRegistration() {
39  DCHECK(!g_disable_manual_jni_registration);
40  g_disable_manual_jni_registration = true;
41}
42
43JNIEnv* 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
51JNIEnv* 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
63void 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
70void InitVM(JavaVM* vm) {
71  DCHECK(!g_jvm || g_jvm == vm);
72  g_jvm = vm;
73}
74
75bool IsVMInitialized() {
76  return g_jvm != NULL;
77}
78
79void 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
97ScopedJavaLocalRef<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
128jclass 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
153template<MethodID::Type type>
154jmethodID 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).
172template<MethodID::Type type>
173jmethodID 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.
190template jmethodID MethodID::Get<MethodID::TYPE_STATIC>(
191    JNIEnv* env, jclass clazz, const char* method_name,
192    const char* jni_signature);
193
194template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>(
195    JNIEnv* env, jclass clazz, const char* method_name,
196    const char* jni_signature);
197
198template 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
202template 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
206bool HasException(JNIEnv* env) {
207  return env->ExceptionCheck() != JNI_FALSE;
208}
209
210bool ClearException(JNIEnv* env) {
211  if (!HasException(env))
212    return false;
213  env->ExceptionDescribe();
214  env->ExceptionClear();
215  return true;
216}
217
218void 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
248std::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