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