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.
5 #include "base/android/jni_android.h"
9 #include "base/android/build_info.h"
10 #include "base/android/jni_string.h"
11 #include "base/lazy_instance.h"
12 #include "base/logging.h"
13 #include "base/threading/platform_thread.h"
16 using base::android::GetClass
;
17 using base::android::MethodID
;
18 using base::android::ScopedJavaLocalRef
;
20 struct MethodIdentifier
{
21 const char* class_name
;
23 const char* jni_signature
;
25 bool operator<(const MethodIdentifier
& other
) const {
26 int r
= strcmp(class_name
, other
.class_name
);
33 r
= strcmp(method
, other
.method
);
40 return strcmp(jni_signature
, other
.jni_signature
) < 0;
44 typedef std::map
<MethodIdentifier
, jmethodID
> MethodIDMap
;
46 const base::subtle::AtomicWord kUnlocked
= 0;
47 const base::subtle::AtomicWord kLocked
= 1;
48 base::subtle::AtomicWord g_method_id_map_lock
= kUnlocked
;
50 // Leak the global app context, as it is used from a non-joinable worker thread
51 // that may still be running at shutdown. There is no harm in doing this.
52 base::LazyInstance
<base::android::ScopedJavaGlobalRef
<jobject
> >::Leaky
53 g_application_context
= LAZY_INSTANCE_INITIALIZER
;
54 base::LazyInstance
<MethodIDMap
> g_method_id_map
= LAZY_INSTANCE_INITIALIZER
;
56 std::string
GetJavaExceptionInfo(JNIEnv
* env
, jthrowable java_throwable
) {
57 ScopedJavaLocalRef
<jclass
> throwable_clazz
=
58 GetClass(env
, "java/lang/Throwable");
59 jmethodID throwable_printstacktrace
=
60 MethodID::Get
<MethodID::TYPE_INSTANCE
>(
61 env
, throwable_clazz
.obj(), "printStackTrace",
62 "(Ljava/io/PrintStream;)V");
64 // Create an instance of ByteArrayOutputStream.
65 ScopedJavaLocalRef
<jclass
> bytearray_output_stream_clazz
=
66 GetClass(env
, "java/io/ByteArrayOutputStream");
67 jmethodID bytearray_output_stream_constructor
=
68 MethodID::Get
<MethodID::TYPE_INSTANCE
>(
69 env
, bytearray_output_stream_clazz
.obj(), "<init>", "()V");
70 jmethodID bytearray_output_stream_tostring
=
71 MethodID::Get
<MethodID::TYPE_INSTANCE
>(
72 env
, bytearray_output_stream_clazz
.obj(), "toString",
73 "()Ljava/lang/String;");
74 ScopedJavaLocalRef
<jobject
> bytearray_output_stream(env
,
75 env
->NewObject(bytearray_output_stream_clazz
.obj(),
76 bytearray_output_stream_constructor
));
78 // Create an instance of PrintStream.
79 ScopedJavaLocalRef
<jclass
> printstream_clazz
=
80 GetClass(env
, "java/io/PrintStream");
81 jmethodID printstream_constructor
=
82 MethodID::Get
<MethodID::TYPE_INSTANCE
>(
83 env
, printstream_clazz
.obj(), "<init>",
84 "(Ljava/io/OutputStream;)V");
85 ScopedJavaLocalRef
<jobject
> printstream(env
,
86 env
->NewObject(printstream_clazz
.obj(), printstream_constructor
,
87 bytearray_output_stream
.obj()));
89 // Call Throwable.printStackTrace(PrintStream)
90 env
->CallVoidMethod(java_throwable
, throwable_printstacktrace
,
93 // Call ByteArrayOutputStream.toString()
94 ScopedJavaLocalRef
<jstring
> exception_string(
95 env
, static_cast<jstring
>(
96 env
->CallObjectMethod(bytearray_output_stream
.obj(),
97 bytearray_output_stream_tostring
)));
99 return ConvertJavaStringToUTF8(exception_string
);
107 JNIEnv
* AttachCurrentThread() {
110 jint ret
= g_jvm
->AttachCurrentThread(&env
, NULL
);
111 DCHECK_EQ(JNI_OK
, ret
);
115 void DetachFromVM() {
116 // Ignore the return value, if the thread is not attached, DetachCurrentThread
117 // will fail. But it is ok as the native thread may never be attached.
119 g_jvm
->DetachCurrentThread();
122 void InitVM(JavaVM
* vm
) {
127 void InitApplicationContext(const JavaRef
<jobject
>& context
) {
128 DCHECK(g_application_context
.Get().is_null());
129 g_application_context
.Get().Reset(context
);
132 const jobject
GetApplicationContext() {
133 DCHECK(!g_application_context
.Get().is_null());
134 return g_application_context
.Get().obj();
137 ScopedJavaLocalRef
<jclass
> GetClass(JNIEnv
* env
, const char* class_name
) {
138 return ScopedJavaLocalRef
<jclass
>(env
, GetUnscopedClass(env
, class_name
));
141 jclass
GetUnscopedClass(JNIEnv
* env
, const char* class_name
) {
142 jclass clazz
= env
->FindClass(class_name
);
143 CHECK(!ClearException(env
) && clazz
) << "Failed to find class " << class_name
;
147 bool HasClass(JNIEnv
* env
, const char* class_name
) {
148 ScopedJavaLocalRef
<jclass
> clazz(env
, env
->FindClass(class_name
));
153 bool error
= ClearException(env
);
158 template<MethodID::Type type
>
159 jmethodID
MethodID::Get(JNIEnv
* env
,
161 const char* method_name
,
162 const char* jni_signature
) {
163 jmethodID id
= type
== TYPE_STATIC
?
164 env
->GetStaticMethodID(clazz
, method_name
, jni_signature
) :
165 env
->GetMethodID(clazz
, method_name
, jni_signature
);
166 CHECK(base::android::ClearException(env
) || id
) <<
168 (type
== TYPE_STATIC
? "static " : "") <<
169 "method " << method_name
<< " " << jni_signature
;
173 // If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call
174 // into ::Get() above. If there's a race, it's ok since the values are the same
175 // (and the duplicated effort will happen only once).
176 template<MethodID::Type type
>
177 jmethodID
MethodID::LazyGet(JNIEnv
* env
,
179 const char* method_name
,
180 const char* jni_signature
,
181 base::subtle::AtomicWord
* atomic_method_id
) {
182 COMPILE_ASSERT(sizeof(subtle::AtomicWord
) >= sizeof(jmethodID
),
183 AtomicWord_SmallerThan_jMethodID
);
184 subtle::AtomicWord value
= base::subtle::Acquire_Load(atomic_method_id
);
186 return reinterpret_cast<jmethodID
>(value
);
187 jmethodID id
= MethodID::Get
<type
>(env
, clazz
, method_name
, jni_signature
);
188 base::subtle::Release_Store(
189 atomic_method_id
, reinterpret_cast<subtle::AtomicWord
>(id
));
193 // Various template instantiations.
194 template jmethodID
MethodID::Get
<MethodID::TYPE_STATIC
>(
195 JNIEnv
* env
, jclass clazz
, const char* method_name
,
196 const char* jni_signature
);
198 template jmethodID
MethodID::Get
<MethodID::TYPE_INSTANCE
>(
199 JNIEnv
* env
, jclass clazz
, const char* method_name
,
200 const char* jni_signature
);
202 template jmethodID
MethodID::LazyGet
<MethodID::TYPE_STATIC
>(
203 JNIEnv
* env
, jclass clazz
, const char* method_name
,
204 const char* jni_signature
, base::subtle::AtomicWord
* atomic_method_id
);
206 template jmethodID
MethodID::LazyGet
<MethodID::TYPE_INSTANCE
>(
207 JNIEnv
* env
, jclass clazz
, const char* method_name
,
208 const char* jni_signature
, base::subtle::AtomicWord
* atomic_method_id
);
210 jfieldID
GetFieldID(JNIEnv
* env
,
211 const JavaRef
<jclass
>& clazz
,
212 const char* field_name
,
213 const char* jni_signature
) {
214 jfieldID field_id
= env
->GetFieldID(clazz
.obj(), field_name
, jni_signature
);
215 CHECK(!ClearException(env
) && field_id
) << "Failed to find field " <<
216 field_name
<< " " << jni_signature
;
220 bool HasField(JNIEnv
* env
,
221 const JavaRef
<jclass
>& clazz
,
222 const char* field_name
,
223 const char* jni_signature
) {
224 jfieldID field_id
= env
->GetFieldID(clazz
.obj(), field_name
, jni_signature
);
229 bool error
= ClearException(env
);
234 jfieldID
GetStaticFieldID(JNIEnv
* env
,
235 const JavaRef
<jclass
>& clazz
,
236 const char* field_name
,
237 const char* jni_signature
) {
239 env
->GetStaticFieldID(clazz
.obj(), field_name
, jni_signature
);
240 CHECK(!ClearException(env
) && field_id
) << "Failed to find static field " <<
241 field_name
<< " " << jni_signature
;
245 jmethodID
GetMethodIDFromClassName(JNIEnv
* env
,
246 const char* class_name
,
248 const char* jni_signature
) {
249 MethodIdentifier key
;
250 key
.class_name
= class_name
;
252 key
.jni_signature
= jni_signature
;
254 MethodIDMap
* map
= g_method_id_map
.Pointer();
257 while (base::subtle::Acquire_CompareAndSwap(&g_method_id_map_lock
,
259 kLocked
) != kUnlocked
) {
260 base::PlatformThread::YieldCurrentThread();
262 MethodIDMap::const_iterator iter
= map
->find(key
);
263 if (iter
!= map
->end()) {
266 base::subtle::Release_Store(&g_method_id_map_lock
, kUnlocked
);
268 // Addition to the map does not invalidate this iterator.
273 ScopedJavaLocalRef
<jclass
> clazz(env
, env
->FindClass(class_name
));
274 jmethodID id
= MethodID::Get
<MethodID::TYPE_INSTANCE
>(
275 env
, clazz
.obj(), method
, jni_signature
);
277 while (base::subtle::Acquire_CompareAndSwap(&g_method_id_map_lock
,
279 kLocked
) != kUnlocked
) {
280 base::PlatformThread::YieldCurrentThread();
282 // Another thread may have populated the map already.
283 std::pair
<MethodIDMap::const_iterator
, bool> result
=
284 map
->insert(std::make_pair(key
, id
));
285 DCHECK_EQ(id
, result
.first
->second
);
286 base::subtle::Release_Store(&g_method_id_map_lock
, kUnlocked
);
291 bool HasException(JNIEnv
* env
) {
292 return env
->ExceptionCheck() != JNI_FALSE
;
295 bool ClearException(JNIEnv
* env
) {
296 if (!HasException(env
))
298 env
->ExceptionDescribe();
299 env
->ExceptionClear();
303 void CheckException(JNIEnv
* env
) {
304 if (!HasException(env
)) return;
306 // Exception has been found, might as well tell breakpad about it.
307 jthrowable java_throwable
= env
->ExceptionOccurred();
308 if (!java_throwable
) {
309 // Do nothing but return false.
313 // Clear the pending exception, since a local reference is now held.
314 env
->ExceptionDescribe();
315 env
->ExceptionClear();
317 // Set the exception_string in BuildInfo so that breakpad can read it.
318 // RVO should avoid any extra copies of the exception string.
319 base::android::BuildInfo::GetInstance()->set_java_exception_info(
320 GetJavaExceptionInfo(env
, java_throwable
));
322 // Now, feel good about it and die.
326 } // namespace android