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 <optional>
8 #include <string>
9
10 #include "base/android/java_exception_reporter.h"
11 #include "base/at_exit.h"
12 #include "base/functional/bind.h"
13 #include "base/logging.h"
14 #include "base/memory/raw_ptr.h"
15 #include "base/test/scoped_feature_list.h"
16 #include "base/threading/thread.h"
17 #include "base/time/time.h"
18 #include "testing/gmock/include/gmock/gmock.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20
21 // Must come after all headers that specialize FromJniType() / ToJniType().
22 #include "base/base_unittest_support_jni/JniAndroidTestUtils_jni.h"
23
24 using ::testing::Eq;
25 using ::testing::Optional;
26 using ::testing::StartsWith;
27
28 namespace base {
29 namespace android {
30
31 namespace {
32
33 class JniAndroidExceptionTestContext final {
34 public:
JniAndroidExceptionTestContext()35 JniAndroidExceptionTestContext() {
36 CHECK(instance == nullptr);
37 instance = this;
38 SetJavaExceptionCallback(CapturingExceptionCallback);
39 g_log_fatal_callback_for_testing = CapturingLogFatalCallback;
40 }
41
~JniAndroidExceptionTestContext()42 ~JniAndroidExceptionTestContext() {
43 g_log_fatal_callback_for_testing = nullptr;
44 SetJavaExceptionCallback(prev_exception_callback_);
45 env->ExceptionClear();
46 Java_JniAndroidTestUtils_restoreGlobalExceptionHandler(env);
47 instance = nullptr;
48 }
49
50 private:
CapturingLogFatalCallback(const char * message)51 static void CapturingLogFatalCallback(const char* message) {
52 auto* self = instance;
53 CHECK(self);
54 // Capture only the first one (can be called multiple times due to
55 // LOG(FATAL) not terminating).
56 if (!self->assertion_message) {
57 self->assertion_message = message;
58 }
59 }
60
CapturingExceptionCallback(const char * message)61 static void CapturingExceptionCallback(const char* message) {
62 auto* self = instance;
63 CHECK(self);
64 if (self->throw_in_exception_callback) {
65 self->throw_in_exception_callback = false;
66 Java_JniAndroidTestUtils_throwRuntimeException(self->env);
67 } else if (self->throw_oom_in_exception_callback) {
68 self->throw_oom_in_exception_callback = false;
69 Java_JniAndroidTestUtils_throwOutOfMemoryError(self->env);
70 } else {
71 self->last_java_exception = message;
72 }
73 }
74
75 static JniAndroidExceptionTestContext* instance;
76 const JavaExceptionCallback prev_exception_callback_ =
77 GetJavaExceptionCallback();
78
79 public:
80 const raw_ptr<JNIEnv> env = base::android::AttachCurrentThread();
81 bool throw_in_exception_callback = false;
82 bool throw_oom_in_exception_callback = false;
83 std::optional<std::string> assertion_message;
84 std::optional<std::string> last_java_exception;
85 };
86
87 JniAndroidExceptionTestContext* JniAndroidExceptionTestContext::instance =
88 nullptr;
89
90 std::atomic<jmethodID> g_atomic_id(nullptr);
LazyMethodIDCall(JNIEnv * env,jclass clazz,int p)91 int LazyMethodIDCall(JNIEnv* env, jclass clazz, int p) {
92 jmethodID id = base::android::MethodID::LazyGet<
93 base::android::MethodID::TYPE_STATIC>(
94 env, clazz,
95 "abs",
96 "(I)I",
97 &g_atomic_id);
98
99 return env->CallStaticIntMethod(clazz, id, p);
100 }
101
MethodIDCall(JNIEnv * env,jclass clazz,jmethodID id,int p)102 int MethodIDCall(JNIEnv* env, jclass clazz, jmethodID id, int p) {
103 return env->CallStaticIntMethod(clazz, id, p);
104 }
105
106 } // namespace
107
TEST(JNIAndroidMicrobenchmark,MethodId)108 TEST(JNIAndroidMicrobenchmark, MethodId) {
109 JNIEnv* env = AttachCurrentThread();
110 ScopedJavaLocalRef<jclass> clazz(GetClass(env, "java/lang/Math"));
111 base::Time start_lazy = base::Time::Now();
112 int o = 0;
113 for (int i = 0; i < 1024; ++i)
114 o += LazyMethodIDCall(env, clazz.obj(), i);
115 base::Time end_lazy = base::Time::Now();
116
117 jmethodID id = g_atomic_id;
118 base::Time start = base::Time::Now();
119 for (int i = 0; i < 1024; ++i)
120 o += MethodIDCall(env, clazz.obj(), id, i);
121 base::Time end = base::Time::Now();
122
123 // On a Galaxy Nexus, results were in the range of:
124 // JNI LazyMethodIDCall (us) 1984
125 // JNI MethodIDCall (us) 1861
126 LOG(ERROR) << "JNI LazyMethodIDCall (us) " <<
127 base::TimeDelta(end_lazy - start_lazy).InMicroseconds();
128 LOG(ERROR) << "JNI MethodIDCall (us) " <<
129 base::TimeDelta(end - start).InMicroseconds();
130 LOG(ERROR) << "JNI " << o;
131 }
132
TEST(JniAndroidTest,GetJavaStackTraceIfPresent_Normal)133 TEST(JniAndroidTest, GetJavaStackTraceIfPresent_Normal) {
134 // The main thread should always have Java frames in it.
135 EXPECT_THAT(GetJavaStackTraceIfPresent(), StartsWith("\tat"));
136 }
137
TEST(JniAndroidTest,GetJavaStackTraceIfPresent_NoEnv)138 TEST(JniAndroidTest, GetJavaStackTraceIfPresent_NoEnv) {
139 class HelperThread : public Thread {
140 public:
141 HelperThread()
142 : Thread("TestThread"), java_stack_1_("X"), java_stack_2_("X") {}
143
144 void Init() override {
145 // Test without a JNIEnv.
146 java_stack_1_ = GetJavaStackTraceIfPresent();
147
148 // Test with a JNIEnv but no Java frames.
149 AttachCurrentThread();
150 java_stack_2_ = GetJavaStackTraceIfPresent();
151 }
152
153 std::string java_stack_1_;
154 std::string java_stack_2_;
155 };
156
157 HelperThread t;
158 t.StartAndWaitForTesting();
159 EXPECT_EQ(t.java_stack_1_, "");
160 EXPECT_EQ(t.java_stack_2_, "");
161 }
162
TEST(JniAndroidTest,GetJavaStackTraceIfPresent_PendingException)163 TEST(JniAndroidTest, GetJavaStackTraceIfPresent_PendingException) {
164 JNIEnv* env = base::android::AttachCurrentThread();
165 Java_JniAndroidTestUtils_throwRuntimeExceptionUnchecked(env);
166 std::string result = GetJavaStackTraceIfPresent();
167 env->ExceptionClear();
168 EXPECT_EQ(result, kUnableToGetStackTraceMessage);
169 }
170
TEST(JniAndroidTest,GetJavaStackTraceIfPresent_OutOfMemoryError)171 TEST(JniAndroidTest, GetJavaStackTraceIfPresent_OutOfMemoryError) {
172 JNIEnv* env = base::android::AttachCurrentThread();
173 Java_JniAndroidTestUtils_setSimulateOomInSanitizedStacktrace(env, true);
174 std::string result = GetJavaStackTraceIfPresent();
175 Java_JniAndroidTestUtils_setSimulateOomInSanitizedStacktrace(env, false);
176 EXPECT_EQ(result, "");
177 }
178
TEST(JniAndroidExceptionTest,HandleExceptionInNative)179 TEST(JniAndroidExceptionTest, HandleExceptionInNative) {
180 JniAndroidExceptionTestContext ctx;
181 test::ScopedFeatureList feature_list;
182 feature_list.InitFromCommandLine("", "HandleJniExceptionsInJava");
183
184 // Do not call setGlobalExceptionHandlerAsNoOp().
185
186 Java_JniAndroidTestUtils_throwRuntimeException(ctx.env);
187 EXPECT_THAT(ctx.last_java_exception,
188 Optional(StartsWith("java.lang.RuntimeException")));
189 EXPECT_THAT(ctx.assertion_message, Optional(Eq(kUncaughtExceptionMessage)));
190 }
191
TEST(JniAndroidExceptionTest,HandleExceptionInJava_NoOpHandler)192 TEST(JniAndroidExceptionTest, HandleExceptionInJava_NoOpHandler) {
193 JniAndroidExceptionTestContext ctx;
194 Java_JniAndroidTestUtils_setGlobalExceptionHandlerAsNoOp(ctx.env);
195 Java_JniAndroidTestUtils_throwRuntimeException(ctx.env);
196
197 EXPECT_THAT(ctx.last_java_exception,
198 Optional(StartsWith("java.lang.RuntimeException")));
199 EXPECT_THAT(ctx.assertion_message,
200 Optional(Eq(kUncaughtExceptionHandlerFailedMessage)));
201 }
202
TEST(JniAndroidExceptionTest,HandleExceptionInJava_ThrowingHandler)203 TEST(JniAndroidExceptionTest, HandleExceptionInJava_ThrowingHandler) {
204 JniAndroidExceptionTestContext ctx;
205 Java_JniAndroidTestUtils_setGlobalExceptionHandlerToThrow(ctx.env);
206 Java_JniAndroidTestUtils_throwRuntimeException(ctx.env);
207
208 EXPECT_THAT(ctx.last_java_exception,
209 Optional(StartsWith("java.lang.IllegalStateException")));
210 EXPECT_THAT(ctx.assertion_message,
211 Optional(Eq(kUncaughtExceptionHandlerFailedMessage)));
212 }
213
TEST(JniAndroidExceptionTest,HandleExceptionInJava_OomThrowingHandler)214 TEST(JniAndroidExceptionTest, HandleExceptionInJava_OomThrowingHandler) {
215 JniAndroidExceptionTestContext ctx;
216 Java_JniAndroidTestUtils_setGlobalExceptionHandlerToThrowOom(ctx.env);
217 Java_JniAndroidTestUtils_throwRuntimeException(ctx.env);
218
219 // Should still report the original exception when the global exception
220 // handler throws an OutOfMemoryError.
221 EXPECT_THAT(ctx.last_java_exception,
222 Optional(StartsWith("java.lang.RuntimeException")));
223 EXPECT_THAT(ctx.assertion_message,
224 Optional(Eq(kUncaughtExceptionHandlerFailedMessage)));
225 }
226
TEST(JniAndroidExceptionTest,HandleExceptionInJava_OomInGetJavaExceptionInfo)227 TEST(JniAndroidExceptionTest, HandleExceptionInJava_OomInGetJavaExceptionInfo) {
228 JniAndroidExceptionTestContext ctx;
229 Java_JniAndroidTestUtils_setGlobalExceptionHandlerToThrowOom(ctx.env);
230 Java_JniAndroidTestUtils_setSimulateOomInSanitizedStacktrace(ctx.env, true);
231 Java_JniAndroidTestUtils_throwRuntimeException(ctx.env);
232 Java_JniAndroidTestUtils_setSimulateOomInSanitizedStacktrace(ctx.env, false);
233
234 EXPECT_THAT(ctx.last_java_exception,
235 Optional(Eq(kOomInGetJavaExceptionInfoMessage)));
236 EXPECT_THAT(ctx.assertion_message,
237 Optional(Eq(kUncaughtExceptionHandlerFailedMessage)));
238 }
239
TEST(JniAndroidExceptionTest,HandleExceptionInJava_Reentrant)240 TEST(JniAndroidExceptionTest, HandleExceptionInJava_Reentrant) {
241 JniAndroidExceptionTestContext ctx;
242 // Use the SetJavaException() callback to trigger re-entrancy.
243 Java_JniAndroidTestUtils_setGlobalExceptionHandlerToThrow(ctx.env);
244 ctx.throw_in_exception_callback = true;
245 Java_JniAndroidTestUtils_throwRuntimeException(ctx.env);
246
247 EXPECT_THAT(ctx.last_java_exception, Optional(Eq(kReetrantExceptionMessage)));
248 EXPECT_THAT(ctx.assertion_message, Optional(Eq(kReetrantExceptionMessage)));
249 }
250
TEST(JniAndroidExceptionTest,HandleExceptionInJava_ReentrantOom)251 TEST(JniAndroidExceptionTest, HandleExceptionInJava_ReentrantOom) {
252 JniAndroidExceptionTestContext ctx;
253 // Use the SetJavaException() callback to trigger re-entrancy.
254 Java_JniAndroidTestUtils_setGlobalExceptionHandlerToThrow(ctx.env);
255 ctx.throw_oom_in_exception_callback = true;
256 Java_JniAndroidTestUtils_throwRuntimeException(ctx.env);
257
258 EXPECT_THAT(ctx.last_java_exception,
259 Optional(Eq(kReetrantOutOfMemoryMessage)));
260 EXPECT_THAT(ctx.assertion_message, Optional(Eq(kReetrantOutOfMemoryMessage)));
261 }
262
263 } // namespace android
264 } // namespace base
265