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