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