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 
IsManualJniRegistrationDisabled()34 bool IsManualJniRegistrationDisabled() {
35   return g_disable_manual_jni_registration;
36 }
37 
DisableManualJniRegistration()38 void DisableManualJniRegistration() {
39   DCHECK(!g_disable_manual_jni_registration);
40   g_disable_manual_jni_registration = true;
41 }
42 
AttachCurrentThread()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 
AttachCurrentThreadWithName(const std::string & thread_name)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 
DetachFromVM()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 
InitVM(JavaVM * vm)70 void InitVM(JavaVM* vm) {
71   DCHECK(!g_jvm || g_jvm == vm);
72   g_jvm = vm;
73 }
74 
IsVMInitialized()75 bool IsVMInitialized() {
76   return g_jvm != NULL;
77 }
78 
InitReplacementClassLoader(JNIEnv * env,const JavaRef<jobject> & class_loader)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 
GetClass(JNIEnv * env,const char * class_name)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 
LazyGetClass(JNIEnv * env,const char * class_name,base::subtle::AtomicWord * atomic_class_id)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>
Get(JNIEnv * env,jclass clazz,const char * method_name,const char * jni_signature)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>
LazyGet(JNIEnv * env,jclass clazz,const char * method_name,const char * jni_signature,base::subtle::AtomicWord * atomic_method_id)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 
HasException(JNIEnv * env)206 bool HasException(JNIEnv* env) {
207   return env->ExceptionCheck() != JNI_FALSE;
208 }
209 
ClearException(JNIEnv * env)210 bool ClearException(JNIEnv* env) {
211   if (!HasException(env))
212     return false;
213   env->ExceptionDescribe();
214   env->ExceptionClear();
215   return true;
216 }
217 
CheckException(JNIEnv * env)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 
GetJavaExceptionInfo(JNIEnv * env,jthrowable java_throwable)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