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