• 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 <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