• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors
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 "base/android/java_exception_reporter.h"
11 #include "base/android/jni_string.h"
12 #include "base/android/jni_utils.h"
13 #include "base/android_runtime_jni_headers/Throwable_jni.h"
14 #include "base/debug/debugging_buildflags.h"
15 #include "base/feature_list.h"
16 #include "base/logging.h"
17 #include "base/strings/string_util.h"
18 #include "build/build_config.h"
19 #include "build/robolectric_buildflags.h"
20 #include "third_party/jni_zero/jni_zero.h"
21 
22 #if BUILDFLAG(IS_ROBOLECTRIC)
23 #include "base/base_robolectric_jni/JniAndroid_jni.h"  // nogncheck
24 #else
25 #include "base/base_jni/JniAndroid_jni.h"
26 #endif
27 
28 namespace base {
29 namespace android {
30 namespace {
31 
32 // If disabled, we LOG(FATAL) immediately in native code when faced with an
33 // uncaught Java exception (historical behavior). If enabled, we give the Java
34 // uncaught exception handler a chance to handle the exception first, so that
35 // the crash is (hopefully) seen as a Java crash, not a native crash.
36 // TODO(crbug.com/40261529): remove this switch once we are confident the
37 // new behavior is fine.
38 BASE_FEATURE(kHandleExceptionsInJava,
39              "HandleJniExceptionsInJava",
40              base::FEATURE_ENABLED_BY_DEFAULT);
41 
42 jclass g_out_of_memory_error_class = nullptr;
43 
44 #if !BUILDFLAG(IS_ROBOLECTRIC)
45 jmethodID g_class_loader_load_class_method_id = nullptr;
46 // ClassLoader.loadClass() accepts either slashes or dots on Android, but JVM
47 // requires dots. We could translate, but there is no need to go through
48 // ClassLoaders in Robolectric anyways.
49 // https://cs.android.com/search?q=symbol:DexFile_defineClassNative
GetClassFromSplit(JNIEnv * env,const char * class_name,const char * split_name)50 jclass GetClassFromSplit(JNIEnv* env,
51                          const char* class_name,
52                          const char* split_name) {
53   DCHECK(IsStringASCII(class_name));
54   ScopedJavaLocalRef<jstring> j_class_name(env, env->NewStringUTF(class_name));
55   return static_cast<jclass>(env->CallObjectMethod(
56       GetSplitClassLoader(env, split_name), g_class_loader_load_class_method_id,
57       j_class_name.obj()));
58 }
59 
60 // Must be called before using GetClassFromSplit - we need to set the global,
61 // and we need to call GetClassLoader at least once to allow the default
62 // resolver (env->FindClass()) to get our main ClassLoader class instance, which
63 // we then cache use for all future calls to GetSplitClassLoader.
PrepareClassLoaders(JNIEnv * env)64 void PrepareClassLoaders(JNIEnv* env) {
65   if (g_class_loader_load_class_method_id == nullptr) {
66     GetClassLoader(env);
67     ScopedJavaLocalRef<jclass> class_loader_clazz = ScopedJavaLocalRef<jclass>(
68         env, env->FindClass("java/lang/ClassLoader"));
69     CHECK(!ClearException(env));
70     g_class_loader_load_class_method_id =
71         env->GetMethodID(class_loader_clazz.obj(), "loadClass",
72                          "(Ljava/lang/String;)Ljava/lang/Class;");
73     CHECK(!ClearException(env));
74   }
75 }
76 #endif  // !BUILDFLAG(IS_ROBOLECTRIC)
77 }  // namespace
78 
79 LogFatalCallback g_log_fatal_callback_for_testing = nullptr;
80 const char kUnableToGetStackTraceMessage[] =
81     "Unable to retrieve Java caller stack trace as the exception handler is "
82     "being re-entered";
83 const char kReetrantOutOfMemoryMessage[] =
84     "While handling an uncaught Java exception, an OutOfMemoryError "
85     "occurred.";
86 const char kReetrantExceptionMessage[] =
87     "While handling an uncaught Java exception, another exception "
88     "occurred.";
89 const char kUncaughtExceptionMessage[] =
90     "Uncaught Java exception in native code. Please include the Java exception "
91     "stack from the Android log in your crash report.";
92 const char kUncaughtExceptionHandlerFailedMessage[] =
93     "Uncaught Java exception in native code and the Java uncaught exception "
94     "handler did not terminate the process. Please include the Java exception "
95     "stack from the Android log in your crash report.";
96 const char kOomInGetJavaExceptionInfoMessage[] =
97     "Unable to obtain Java stack trace due to OutOfMemoryError";
98 
InitVM(JavaVM * vm)99 void InitVM(JavaVM* vm) {
100   jni_zero::InitVM(vm);
101   jni_zero::SetExceptionHandler(CheckException);
102   JNIEnv* env = jni_zero::AttachCurrentThread();
103 #if !BUILDFLAG(IS_ROBOLECTRIC)
104   // Warm-up needed for GetClassFromSplit, must be called before we set the
105   // resolver, since GetClassFromSplit won't work until after
106   // PrepareClassLoaders has happened.
107   PrepareClassLoaders(env);
108   jni_zero::SetClassResolver(GetClassFromSplit);
109 #endif
110   g_out_of_memory_error_class = static_cast<jclass>(
111       env->NewGlobalRef(env->FindClass("java/lang/OutOfMemoryError")));
112   DCHECK(g_out_of_memory_error_class);
113 }
114 
115 
CheckException(JNIEnv * env)116 void CheckException(JNIEnv* env) {
117   if (!jni_zero::HasException(env)) {
118     return;
119   }
120 
121   static thread_local bool g_reentering = false;
122   if (g_reentering) {
123     // We were handling an uncaught Java exception already, but one of the Java
124     // methods we called below threw another exception. (This is unlikely to
125     // happen, as we are careful to never throw from these methods, but we
126     // can't rule it out entirely. E.g. an OutOfMemoryError when constructing
127     // the jstring for the return value of
128     // sanitizedStacktraceForUnhandledException().
129     env->ExceptionDescribe();
130     jthrowable raw_throwable = env->ExceptionOccurred();
131     env->ExceptionClear();
132     jclass clazz = env->GetObjectClass(raw_throwable);
133     bool is_oom_error = env->IsSameObject(clazz, g_out_of_memory_error_class);
134     env->Throw(raw_throwable);  // Ensure we don't re-enter Java.
135 
136     if (is_oom_error) {
137       base::android::SetJavaException(kReetrantOutOfMemoryMessage);
138       // Use different LOG(FATAL) statements to ensure unique stack traces.
139       if (g_log_fatal_callback_for_testing) {
140         g_log_fatal_callback_for_testing(kReetrantOutOfMemoryMessage);
141       } else {
142         LOG(FATAL) << kReetrantOutOfMemoryMessage;
143       }
144     } else {
145       base::android::SetJavaException(kReetrantExceptionMessage);
146       if (g_log_fatal_callback_for_testing) {
147         g_log_fatal_callback_for_testing(kReetrantExceptionMessage);
148       } else {
149         LOG(FATAL) << kReetrantExceptionMessage;
150       }
151     }
152     // Needed for tests, which do not terminate from LOG(FATAL).
153     return;
154   }
155   g_reentering = true;
156 
157   // Log a message to ensure there is something in the log even if the rest of
158   // this function goes horribly wrong, and also to provide a convenient marker
159   // in the log for where Java exception crash information starts.
160   LOG(ERROR) << "Crashing due to uncaught Java exception";
161 
162   const bool handle_exception_in_java =
163       base::FeatureList::IsEnabled(kHandleExceptionsInJava);
164 
165   if (!handle_exception_in_java) {
166     env->ExceptionDescribe();
167   }
168 
169   // We cannot use `ScopedJavaLocalRef` directly because that ends up calling
170   // env->GetObjectRefType() when DCHECK is on, and that call is not allowed
171   // with a pending exception according to the JNI spec.
172   jthrowable raw_throwable = env->ExceptionOccurred();
173   // Now that we saved the reference to the throwable, clear the exception.
174   //
175   // We need to do this as early as possible to remove the risk that code below
176   // might accidentally call back into Java, which is not allowed when `env`
177   // has an exception set, per the JNI spec. (For example, LOG(FATAL) doesn't
178   // work with a JNI exception set, because it calls
179   // GetJavaStackTraceIfPresent()).
180   env->ExceptionClear();
181   // The reference returned by `ExceptionOccurred()` is a local reference.
182   // `ExceptionClear()` merely removes the exception information from `env`;
183   // it doesn't delete the reference, which is why this call is valid.
184   auto throwable = ScopedJavaLocalRef<jthrowable>::Adopt(env, raw_throwable);
185 
186   if (!handle_exception_in_java) {
187     base::android::SetJavaException(
188         GetJavaExceptionInfo(env, throwable).c_str());
189     if (g_log_fatal_callback_for_testing) {
190       g_log_fatal_callback_for_testing(kUncaughtExceptionMessage);
191     } else {
192       LOG(FATAL) << kUncaughtExceptionMessage;
193     }
194     // Needed for tests, which do not terminate from LOG(FATAL).
195     g_reentering = false;
196     return;
197   }
198 
199   // We don't need to call SetJavaException() in this branch because we
200   // expect handleException() to eventually call JavaExceptionReporter through
201   // the global uncaught exception handler.
202 
203   const std::string native_stack_trace = base::debug::StackTrace().ToString();
204   LOG(ERROR) << "Native stack trace:" << std::endl << native_stack_trace;
205 
206   ScopedJavaLocalRef<jthrowable> secondary_exception =
207       Java_JniAndroid_handleException(
208           env, throwable, ConvertUTF8ToJavaString(env, native_stack_trace));
209 
210   // Ideally handleException() should have terminated the process and we should
211   // not get here. This can happen in the case of OutOfMemoryError or if the
212   // app that embedded WebView installed an exception handler that does not
213   // terminate, or itself threw an exception. We cannot be confident that
214   // JavaExceptionReporter ran, so set the java exception explicitly.
215   base::android::SetJavaException(
216       GetJavaExceptionInfo(
217           env, secondary_exception ? secondary_exception : throwable)
218           .c_str());
219   if (g_log_fatal_callback_for_testing) {
220     g_log_fatal_callback_for_testing(kUncaughtExceptionHandlerFailedMessage);
221   } else {
222     LOG(FATAL) << kUncaughtExceptionHandlerFailedMessage;
223   }
224   // Needed for tests, which do not terminate from LOG(FATAL).
225   g_reentering = false;
226 }
227 
GetJavaExceptionInfo(JNIEnv * env,const JavaRef<jthrowable> & throwable)228 std::string GetJavaExceptionInfo(JNIEnv* env,
229                                  const JavaRef<jthrowable>& throwable) {
230   ScopedJavaLocalRef<jstring> sanitized_exception_string =
231       Java_JniAndroid_sanitizedStacktraceForUnhandledException(env, throwable);
232   // Returns null when PiiElider results in an OutOfMemoryError.
233   return sanitized_exception_string
234              ? ConvertJavaStringToUTF8(sanitized_exception_string)
235              : kOomInGetJavaExceptionInfoMessage;
236 }
237 
GetJavaStackTraceIfPresent()238 std::string GetJavaStackTraceIfPresent() {
239   JNIEnv* env = nullptr;
240   JavaVM* jvm = jni_zero::GetVM();
241   if (jvm) {
242     jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2);
243   }
244   if (!env) {
245     // JNI has not been initialized on this thread.
246     return {};
247   }
248 
249   if (HasException(env)) {
250     // This can happen if CheckException() is being re-entered, decided to
251     // LOG(FATAL) immediately, and LOG(FATAL) itself is calling us. In that case
252     // it is imperative that we don't try to call Java again.
253     return kUnableToGetStackTraceMessage;
254   }
255 
256   ScopedJavaLocalRef<jthrowable> throwable =
257       JNI_Throwable::Java_Throwable_Constructor(env);
258   std::string ret = GetJavaExceptionInfo(env, throwable);
259   // Strip the exception message and leave only the "at" lines. Example:
260   // java.lang.Throwable:
261   // {tab}at Clazz.method(Clazz.java:111)
262   // {tab}at ...
263   size_t newline_idx = ret.find('\n');
264   if (newline_idx == std::string::npos) {
265     // There are no java frames.
266     return {};
267   }
268   return ret.substr(newline_idx + 1);
269 }
270 
271 }  // namespace android
272 }  // namespace base
273