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