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 #include <sys/prctl.h>
9
10 #include <map>
11
12 #include "base/android/java_exception_reporter.h"
13 #include "base/android/jni_string.h"
14 #include "base/debug/debugging_buildflags.h"
15 #include "base/lazy_instance.h"
16 #include "base/logging.h"
17 #include "base/threading/thread_local.h"
18
19 namespace {
20 using base::android::GetClass;
21 using base::android::MethodID;
22 using base::android::ScopedJavaLocalRef;
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 #if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
30 base::LazyInstance<base::ThreadLocalPointer<void>>::Leaky
31 g_stack_frame_pointer = LAZY_INSTANCE_INITIALIZER;
32 #endif
33
34 bool g_fatal_exception_occurred = false;
35
36 } // namespace
37
38 namespace base {
39 namespace android {
40
AttachCurrentThread()41 JNIEnv* AttachCurrentThread() {
42 DCHECK(g_jvm);
43 JNIEnv* env = nullptr;
44 jint ret = g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2);
45 if (ret == JNI_EDETACHED || !env) {
46 JavaVMAttachArgs args;
47 args.version = JNI_VERSION_1_2;
48 args.group = nullptr;
49
50 // 16 is the maximum size for thread names on Android.
51 char thread_name[16];
52 int err = prctl(PR_GET_NAME, thread_name);
53 if (err < 0) {
54 DPLOG(ERROR) << "prctl(PR_GET_NAME)";
55 args.name = nullptr;
56 } else {
57 args.name = thread_name;
58 }
59
60 ret = g_jvm->AttachCurrentThread(&env, &args);
61 DCHECK_EQ(JNI_OK, ret);
62 }
63 return env;
64 }
65
AttachCurrentThreadWithName(const std::string & thread_name)66 JNIEnv* AttachCurrentThreadWithName(const std::string& thread_name) {
67 DCHECK(g_jvm);
68 JavaVMAttachArgs args;
69 args.version = JNI_VERSION_1_2;
70 args.name = thread_name.c_str();
71 args.group = NULL;
72 JNIEnv* env = NULL;
73 jint ret = g_jvm->AttachCurrentThread(&env, &args);
74 DCHECK_EQ(JNI_OK, ret);
75 return env;
76 }
77
DetachFromVM()78 void DetachFromVM() {
79 // Ignore the return value, if the thread is not attached, DetachCurrentThread
80 // will fail. But it is ok as the native thread may never be attached.
81 if (g_jvm)
82 g_jvm->DetachCurrentThread();
83 }
84
InitVM(JavaVM * vm)85 void InitVM(JavaVM* vm) {
86 DCHECK(!g_jvm || g_jvm == vm);
87 g_jvm = vm;
88 }
89
IsVMInitialized()90 bool IsVMInitialized() {
91 return g_jvm != NULL;
92 }
93
InitReplacementClassLoader(JNIEnv * env,const JavaRef<jobject> & class_loader)94 void InitReplacementClassLoader(JNIEnv* env,
95 const JavaRef<jobject>& class_loader) {
96 DCHECK(g_class_loader.Get().is_null());
97 DCHECK(!class_loader.is_null());
98
99 ScopedJavaLocalRef<jclass> class_loader_clazz =
100 GetClass(env, "java/lang/ClassLoader");
101 CHECK(!ClearException(env));
102 g_class_loader_load_class_method_id =
103 env->GetMethodID(class_loader_clazz.obj(),
104 "loadClass",
105 "(Ljava/lang/String;)Ljava/lang/Class;");
106 CHECK(!ClearException(env));
107
108 DCHECK(env->IsInstanceOf(class_loader.obj(), class_loader_clazz.obj()));
109 g_class_loader.Get().Reset(class_loader);
110 }
111
GetClass(JNIEnv * env,const char * class_name)112 ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, const char* class_name) {
113 jclass clazz;
114 if (!g_class_loader.Get().is_null()) {
115 // ClassLoader.loadClass expects a classname with components separated by
116 // dots instead of the slashes that JNIEnv::FindClass expects. The JNI
117 // generator generates names with slashes, so we have to replace them here.
118 // TODO(torne): move to an approach where we always use ClassLoader except
119 // for the special case of base::android::GetClassLoader(), and change the
120 // JNI generator to generate dot-separated names. http://crbug.com/461773
121 size_t bufsize = strlen(class_name) + 1;
122 char dotted_name[bufsize];
123 memmove(dotted_name, class_name, bufsize);
124 for (size_t i = 0; i < bufsize; ++i) {
125 if (dotted_name[i] == '/') {
126 dotted_name[i] = '.';
127 }
128 }
129
130 clazz = static_cast<jclass>(
131 env->CallObjectMethod(g_class_loader.Get().obj(),
132 g_class_loader_load_class_method_id,
133 ConvertUTF8ToJavaString(env, dotted_name).obj()));
134 } else {
135 clazz = env->FindClass(class_name);
136 }
137 if (ClearException(env) || !clazz) {
138 LOG(FATAL) << "Failed to find class " << class_name;
139 }
140 return ScopedJavaLocalRef<jclass>(env, clazz);
141 }
142
LazyGetClass(JNIEnv * env,const char * class_name,base::subtle::AtomicWord * atomic_class_id)143 jclass LazyGetClass(
144 JNIEnv* env,
145 const char* class_name,
146 base::subtle::AtomicWord* atomic_class_id) {
147 static_assert(sizeof(subtle::AtomicWord) >= sizeof(jclass),
148 "AtomicWord can't be smaller than jclass");
149 subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_class_id);
150 if (value)
151 return reinterpret_cast<jclass>(value);
152 ScopedJavaGlobalRef<jclass> clazz;
153 clazz.Reset(GetClass(env, class_name));
154 subtle::AtomicWord null_aw = reinterpret_cast<subtle::AtomicWord>(NULL);
155 subtle::AtomicWord cas_result = base::subtle::Release_CompareAndSwap(
156 atomic_class_id,
157 null_aw,
158 reinterpret_cast<subtle::AtomicWord>(clazz.obj()));
159 if (cas_result == null_aw) {
160 // We intentionally leak the global ref since we now storing it as a raw
161 // pointer in |atomic_class_id|.
162 return clazz.Release();
163 } else {
164 return reinterpret_cast<jclass>(cas_result);
165 }
166 }
167
168 template<MethodID::Type type>
Get(JNIEnv * env,jclass clazz,const char * method_name,const char * jni_signature)169 jmethodID MethodID::Get(JNIEnv* env,
170 jclass clazz,
171 const char* method_name,
172 const char* jni_signature) {
173 jmethodID id = type == TYPE_STATIC ?
174 env->GetStaticMethodID(clazz, method_name, jni_signature) :
175 env->GetMethodID(clazz, method_name, jni_signature);
176 if (base::android::ClearException(env) || !id) {
177 LOG(FATAL) << "Failed to find " <<
178 (type == TYPE_STATIC ? "static " : "") <<
179 "method " << method_name << " " << jni_signature;
180 }
181 return id;
182 }
183
184 // If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call
185 // into ::Get() above. If there's a race, it's ok since the values are the same
186 // (and the duplicated effort will happen only once).
187 template<MethodID::Type type>
LazyGet(JNIEnv * env,jclass clazz,const char * method_name,const char * jni_signature,base::subtle::AtomicWord * atomic_method_id)188 jmethodID MethodID::LazyGet(JNIEnv* env,
189 jclass clazz,
190 const char* method_name,
191 const char* jni_signature,
192 base::subtle::AtomicWord* atomic_method_id) {
193 static_assert(sizeof(subtle::AtomicWord) >= sizeof(jmethodID),
194 "AtomicWord can't be smaller than jMethodID");
195 subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_method_id);
196 if (value)
197 return reinterpret_cast<jmethodID>(value);
198 jmethodID id = MethodID::Get<type>(env, clazz, method_name, jni_signature);
199 base::subtle::Release_Store(
200 atomic_method_id, reinterpret_cast<subtle::AtomicWord>(id));
201 return id;
202 }
203
204 // Various template instantiations.
205 template jmethodID MethodID::Get<MethodID::TYPE_STATIC>(
206 JNIEnv* env, jclass clazz, const char* method_name,
207 const char* jni_signature);
208
209 template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>(
210 JNIEnv* env, jclass clazz, const char* method_name,
211 const char* jni_signature);
212
213 template jmethodID MethodID::LazyGet<MethodID::TYPE_STATIC>(
214 JNIEnv* env, jclass clazz, const char* method_name,
215 const char* jni_signature, base::subtle::AtomicWord* atomic_method_id);
216
217 template jmethodID MethodID::LazyGet<MethodID::TYPE_INSTANCE>(
218 JNIEnv* env, jclass clazz, const char* method_name,
219 const char* jni_signature, base::subtle::AtomicWord* atomic_method_id);
220
HasException(JNIEnv * env)221 bool HasException(JNIEnv* env) {
222 return env->ExceptionCheck() != JNI_FALSE;
223 }
224
ClearException(JNIEnv * env)225 bool ClearException(JNIEnv* env) {
226 if (!HasException(env))
227 return false;
228 env->ExceptionDescribe();
229 env->ExceptionClear();
230 return true;
231 }
232
CheckException(JNIEnv * env)233 void CheckException(JNIEnv* env) {
234 if (!HasException(env))
235 return;
236
237 jthrowable java_throwable = env->ExceptionOccurred();
238 if (java_throwable) {
239 // Clear the pending exception, since a local reference is now held.
240 env->ExceptionDescribe();
241 env->ExceptionClear();
242
243 if (g_fatal_exception_occurred) {
244 // Another exception (probably OOM) occurred during GetJavaExceptionInfo.
245 base::android::SetJavaException(
246 "Java OOM'ed in exception handling, check logcat");
247 } else {
248 g_fatal_exception_occurred = true;
249 // RVO should avoid any extra copies of the exception string.
250 base::android::SetJavaException(
251 GetJavaExceptionInfo(env, java_throwable).c_str());
252 }
253 }
254
255 // Now, feel good about it and die.
256 // TODO(lhchavez): Remove this hack. See b/28814913 for details.
257 if (java_throwable)
258 LOG(FATAL) << GetJavaExceptionInfo(env, java_throwable);
259 else
260 LOG(FATAL) << "Unhandled exception";
261 }
262
GetJavaExceptionInfo(JNIEnv * env,jthrowable java_throwable)263 std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) {
264 ScopedJavaLocalRef<jclass> throwable_clazz =
265 GetClass(env, "java/lang/Throwable");
266 jmethodID throwable_printstacktrace =
267 MethodID::Get<MethodID::TYPE_INSTANCE>(
268 env, throwable_clazz.obj(), "printStackTrace",
269 "(Ljava/io/PrintStream;)V");
270
271 // Create an instance of ByteArrayOutputStream.
272 ScopedJavaLocalRef<jclass> bytearray_output_stream_clazz =
273 GetClass(env, "java/io/ByteArrayOutputStream");
274 jmethodID bytearray_output_stream_constructor =
275 MethodID::Get<MethodID::TYPE_INSTANCE>(
276 env, bytearray_output_stream_clazz.obj(), "<init>", "()V");
277 jmethodID bytearray_output_stream_tostring =
278 MethodID::Get<MethodID::TYPE_INSTANCE>(
279 env, bytearray_output_stream_clazz.obj(), "toString",
280 "()Ljava/lang/String;");
281 ScopedJavaLocalRef<jobject> bytearray_output_stream(env,
282 env->NewObject(bytearray_output_stream_clazz.obj(),
283 bytearray_output_stream_constructor));
284 CheckException(env);
285
286 // Create an instance of PrintStream.
287 ScopedJavaLocalRef<jclass> printstream_clazz =
288 GetClass(env, "java/io/PrintStream");
289 jmethodID printstream_constructor =
290 MethodID::Get<MethodID::TYPE_INSTANCE>(
291 env, printstream_clazz.obj(), "<init>",
292 "(Ljava/io/OutputStream;)V");
293 ScopedJavaLocalRef<jobject> printstream(env,
294 env->NewObject(printstream_clazz.obj(), printstream_constructor,
295 bytearray_output_stream.obj()));
296 CheckException(env);
297
298 // Call Throwable.printStackTrace(PrintStream)
299 env->CallVoidMethod(java_throwable, throwable_printstacktrace,
300 printstream.obj());
301 CheckException(env);
302
303 // Call ByteArrayOutputStream.toString()
304 ScopedJavaLocalRef<jstring> exception_string(
305 env, static_cast<jstring>(
306 env->CallObjectMethod(bytearray_output_stream.obj(),
307 bytearray_output_stream_tostring)));
308 CheckException(env);
309
310 return ConvertJavaStringToUTF8(exception_string);
311 }
312
313 #if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
314
JNIStackFrameSaver(void * current_fp)315 JNIStackFrameSaver::JNIStackFrameSaver(void* current_fp) {
316 previous_fp_ = g_stack_frame_pointer.Pointer()->Get();
317 g_stack_frame_pointer.Pointer()->Set(current_fp);
318 }
319
~JNIStackFrameSaver()320 JNIStackFrameSaver::~JNIStackFrameSaver() {
321 g_stack_frame_pointer.Pointer()->Set(previous_fp_);
322 }
323
SavedFrame()324 void* JNIStackFrameSaver::SavedFrame() {
325 return g_stack_frame_pointer.Pointer()->Get();
326 }
327
328 #endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
329
330 } // namespace android
331 } // namespace base
332