• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
15 namespace {
16 using base::android::GetClass;
17 using base::android::MethodID;
18 using base::android::ScopedJavaLocalRef;
19 
20 JavaVM* 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.
23 base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject> >::Leaky
24     g_application_context = LAZY_INSTANCE_INITIALIZER;
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 
GetJavaExceptionInfo(JNIEnv * env,jthrowable java_throwable)29 std::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 
77 namespace base {
78 namespace android {
79 
AttachCurrentThread()80 JNIEnv* 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 
AttachCurrentThreadWithName(const std::string & thread_name)88 JNIEnv* 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 
DetachFromVM()100 void 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 
InitVM(JavaVM * vm)107 void InitVM(JavaVM* vm) {
108   DCHECK(!g_jvm);
109   g_jvm = vm;
110 }
111 
IsVMInitialized()112 bool IsVMInitialized() {
113   return g_jvm != NULL;
114 }
115 
InitApplicationContext(JNIEnv * env,const JavaRef<jobject> & context)116 void 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 
InitReplacementClassLoader(JNIEnv * env,const JavaRef<jobject> & class_loader)125 void 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 
GetApplicationContext()143 const jobject GetApplicationContext() {
144   DCHECK(!g_application_context.Get().is_null());
145   return g_application_context.Get().obj();
146 }
147 
GetClass(JNIEnv * env,const char * class_name)148 ScopedJavaLocalRef<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 
LazyGetClass(JNIEnv * env,const char * class_name,base::subtle::AtomicWord * atomic_class_id)162 jclass 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 
187 template<MethodID::Type type>
Get(JNIEnv * env,jclass clazz,const char * method_name,const char * jni_signature)188 jmethodID 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).
205 template<MethodID::Type type>
LazyGet(JNIEnv * env,jclass clazz,const char * method_name,const char * jni_signature,base::subtle::AtomicWord * atomic_method_id)206 jmethodID 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.
223 template jmethodID MethodID::Get<MethodID::TYPE_STATIC>(
224     JNIEnv* env, jclass clazz, const char* method_name,
225     const char* jni_signature);
226 
227 template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>(
228     JNIEnv* env, jclass clazz, const char* method_name,
229     const char* jni_signature);
230 
231 template 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 
235 template 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 
HasException(JNIEnv * env)239 bool HasException(JNIEnv* env) {
240   return env->ExceptionCheck() != JNI_FALSE;
241 }
242 
ClearException(JNIEnv * env)243 bool ClearException(JNIEnv* env) {
244   if (!HasException(env))
245     return false;
246   env->ExceptionDescribe();
247   env->ExceptionClear();
248   return true;
249 }
250 
CheckException(JNIEnv * env)251 void CheckException(JNIEnv* env) {
252   if (!HasException(env)) return;
253 
254   // Exception has been found, might as well tell breakpad about it.
255   jthrowable java_throwable = env->ExceptionOccurred();
256   if (!java_throwable) {
257     // Do nothing but return false.
258     CHECK(false);
259   }
260 
261   // Clear the pending exception, since a local reference is now held.
262   env->ExceptionDescribe();
263   env->ExceptionClear();
264 
265   // Set the exception_string in BuildInfo so that breakpad can read it.
266   // RVO should avoid any extra copies of the exception string.
267   base::android::BuildInfo::GetInstance()->set_java_exception_info(
268       GetJavaExceptionInfo(env, java_throwable));
269 
270   // Now, feel good about it and die.
271   CHECK(false);
272 }
273 
274 }  // namespace android
275 }  // namespace base
276