| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/android/scoped_java_ref.h" |
| |
| #include <iterator> |
| #include <type_traits> |
| |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_string.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #define EXPECT_SAME_OBJECT(a, b) \ |
| EXPECT_TRUE(env->IsSameObject((a).obj(), (b).obj())) |
| |
| namespace base { |
| namespace android { |
| |
| namespace { |
| int g_local_refs = 0; |
| int g_global_refs = 0; |
| |
| const JNINativeInterface* g_previous_functions; |
| |
| jobject NewGlobalRef(JNIEnv* env, jobject obj) { |
| ++g_global_refs; |
| return g_previous_functions->NewGlobalRef(env, obj); |
| } |
| |
| void DeleteGlobalRef(JNIEnv* env, jobject obj) { |
| --g_global_refs; |
| return g_previous_functions->DeleteGlobalRef(env, obj); |
| } |
| |
| jobject NewLocalRef(JNIEnv* env, jobject obj) { |
| ++g_local_refs; |
| return g_previous_functions->NewLocalRef(env, obj); |
| } |
| |
| void DeleteLocalRef(JNIEnv* env, jobject obj) { |
| --g_local_refs; |
| return g_previous_functions->DeleteLocalRef(env, obj); |
| } |
| } // namespace |
| |
| class ScopedJavaRefTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| g_local_refs = 0; |
| g_global_refs = 0; |
| JNIEnv* env = AttachCurrentThread(); |
| g_previous_functions = env->functions; |
| hooked_functions = *g_previous_functions; |
| env->functions = &hooked_functions; |
| // We inject our own functions in JNINativeInterface so we can keep track |
| // of the reference counting ourselves. |
| hooked_functions.NewGlobalRef = &NewGlobalRef; |
| hooked_functions.DeleteGlobalRef = &DeleteGlobalRef; |
| hooked_functions.NewLocalRef = &NewLocalRef; |
| hooked_functions.DeleteLocalRef = &DeleteLocalRef; |
| } |
| |
| void TearDown() override { |
| JNIEnv* env = AttachCurrentThread(); |
| env->functions = g_previous_functions; |
| } |
| // From JellyBean release, the instance of this struct provided in JNIEnv is |
| // read-only, so we deep copy it to allow individual functions to be hooked. |
| JNINativeInterface hooked_functions; |
| }; |
| |
| // The main purpose of this is testing the various conversions compile. |
| TEST_F(ScopedJavaRefTest, Conversions) { |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, "string"); |
| ScopedJavaGlobalRef<jstring> global(str); |
| |
| // Contextual conversions to bool should be allowed. |
| EXPECT_TRUE(str); |
| EXPECT_FALSE(JavaRef<jobject>()); |
| |
| // All the types should convert from nullptr, even JavaRef. |
| { |
| JavaRef<jstring> null_ref(nullptr); |
| EXPECT_FALSE(null_ref); |
| ScopedJavaLocalRef<jobject> null_local(nullptr); |
| EXPECT_FALSE(null_local); |
| ScopedJavaGlobalRef<jarray> null_global(nullptr); |
| EXPECT_FALSE(null_global); |
| } |
| |
| // Local and global refs should {copy,move}-{construct,assign}. |
| // Moves should leave the source as null. |
| { |
| ScopedJavaLocalRef<jstring> str2(str); |
| EXPECT_SAME_OBJECT(str2, str); |
| ScopedJavaLocalRef<jstring> str3(std::move(str2)); |
| EXPECT_SAME_OBJECT(str3, str); |
| EXPECT_FALSE(str2); |
| ScopedJavaLocalRef<jstring> str4; |
| str4 = str; |
| EXPECT_SAME_OBJECT(str4, str); |
| ScopedJavaLocalRef<jstring> str5; |
| str5 = std::move(str4); |
| EXPECT_SAME_OBJECT(str5, str); |
| EXPECT_FALSE(str4); |
| } |
| { |
| ScopedJavaGlobalRef<jstring> str2(global); |
| EXPECT_SAME_OBJECT(str2, str); |
| ScopedJavaGlobalRef<jstring> str3(std::move(str2)); |
| EXPECT_SAME_OBJECT(str3, str); |
| EXPECT_FALSE(str2); |
| ScopedJavaGlobalRef<jstring> str4; |
| str4 = global; |
| EXPECT_SAME_OBJECT(str4, str); |
| ScopedJavaGlobalRef<jstring> str5; |
| str5 = std::move(str4); |
| EXPECT_SAME_OBJECT(str5, str); |
| EXPECT_FALSE(str4); |
| } |
| |
| // As above but going from jstring to jobject. |
| { |
| ScopedJavaLocalRef<jobject> obj2(str); |
| EXPECT_SAME_OBJECT(obj2, str); |
| ScopedJavaLocalRef<jobject> obj3(std::move(obj2)); |
| EXPECT_SAME_OBJECT(obj3, str); |
| EXPECT_FALSE(obj2); |
| ScopedJavaLocalRef<jobject> obj4; |
| obj4 = str; |
| EXPECT_SAME_OBJECT(obj4, str); |
| ScopedJavaLocalRef<jobject> obj5; |
| obj5 = std::move(obj4); |
| EXPECT_SAME_OBJECT(obj5, str); |
| EXPECT_FALSE(obj4); |
| } |
| { |
| ScopedJavaGlobalRef<jobject> obj2(global); |
| EXPECT_SAME_OBJECT(obj2, str); |
| ScopedJavaGlobalRef<jobject> obj3(std::move(obj2)); |
| EXPECT_SAME_OBJECT(obj3, str); |
| EXPECT_FALSE(obj2); |
| ScopedJavaGlobalRef<jobject> obj4; |
| obj4 = global; |
| EXPECT_SAME_OBJECT(obj4, str); |
| ScopedJavaGlobalRef<jobject> obj5; |
| obj5 = std::move(obj4); |
| EXPECT_SAME_OBJECT(obj5, str); |
| EXPECT_FALSE(obj4); |
| } |
| |
| // Explicit copy construction or assignment between global<->local is allowed, |
| // but not implicit conversions. |
| { |
| ScopedJavaLocalRef<jstring> new_local(global); |
| EXPECT_SAME_OBJECT(new_local, str); |
| new_local = global; |
| EXPECT_SAME_OBJECT(new_local, str); |
| ScopedJavaGlobalRef<jstring> new_global(str); |
| EXPECT_SAME_OBJECT(new_global, str); |
| new_global = str; |
| EXPECT_SAME_OBJECT(new_local, str); |
| static_assert(!std::is_convertible<ScopedJavaLocalRef<jobject>, |
| ScopedJavaGlobalRef<jobject>>::value, |
| ""); |
| static_assert(!std::is_convertible<ScopedJavaGlobalRef<jobject>, |
| ScopedJavaLocalRef<jobject>>::value, |
| ""); |
| } |
| |
| // Converting between local/global while also converting to jobject also works |
| // because JavaRef<jobject> is the base class. |
| { |
| ScopedJavaGlobalRef<jobject> global_obj(str); |
| ScopedJavaLocalRef<jobject> local_obj(global); |
| const JavaRef<jobject>& obj_ref1(str); |
| const JavaRef<jobject>& obj_ref2(global); |
| EXPECT_SAME_OBJECT(obj_ref1, obj_ref2); |
| EXPECT_SAME_OBJECT(global_obj, obj_ref2); |
| } |
| global.Reset(str); |
| const JavaRef<jstring>& str_ref = str; |
| EXPECT_EQ("string", ConvertJavaStringToUTF8(str_ref)); |
| str.Reset(); |
| } |
| |
| TEST_F(ScopedJavaRefTest, RefCounts) { |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jstring> str; |
| // The ConvertJavaStringToUTF8 below creates a new string that would normally |
| // return a local ref. We simulate that by starting the g_local_refs count at |
| // 1. |
| g_local_refs = 1; |
| str.Reset(ConvertUTF8ToJavaString(env, "string")); |
| EXPECT_EQ(1, g_local_refs); |
| EXPECT_EQ(0, g_global_refs); |
| { |
| ScopedJavaGlobalRef<jstring> global_str(str); |
| ScopedJavaGlobalRef<jobject> global_obj(global_str); |
| EXPECT_EQ(1, g_local_refs); |
| EXPECT_EQ(2, g_global_refs); |
| |
| auto str2 = ScopedJavaLocalRef<jstring>::Adopt(env, str.Release()); |
| EXPECT_EQ(1, g_local_refs); |
| { |
| ScopedJavaLocalRef<jstring> str3(str2); |
| EXPECT_EQ(2, g_local_refs); |
| } |
| EXPECT_EQ(1, g_local_refs); |
| { |
| ScopedJavaLocalRef<jstring> str4((ScopedJavaLocalRef<jstring>(str2))); |
| EXPECT_EQ(2, g_local_refs); |
| } |
| EXPECT_EQ(1, g_local_refs); |
| { |
| ScopedJavaLocalRef<jstring> str5; |
| str5 = ScopedJavaLocalRef<jstring>(str2); |
| EXPECT_EQ(2, g_local_refs); |
| } |
| EXPECT_EQ(1, g_local_refs); |
| str2.Reset(); |
| EXPECT_EQ(0, g_local_refs); |
| global_str.Reset(); |
| EXPECT_EQ(1, g_global_refs); |
| ScopedJavaGlobalRef<jobject> global_obj2(global_obj); |
| EXPECT_EQ(2, g_global_refs); |
| } |
| |
| EXPECT_EQ(0, g_local_refs); |
| EXPECT_EQ(0, g_global_refs); |
| } |
| |
| class JavaObjectArrayReaderTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| JNIEnv* env = AttachCurrentThread(); |
| int_class_ = GetClass(env, "java/lang/Integer"); |
| int_constructor_ = MethodID::Get<MethodID::TYPE_INSTANCE>( |
| env, int_class_.obj(), "<init>", "(I)V"); |
| array_ = MakeArray(array_len_); |
| |
| // Make array_len_ different Integer objects, keep a reference to each, |
| // and add them to the array. |
| for (jint i = 0; i < array_len_; ++i) { |
| jobject member = env->NewObject(int_class_.obj(), int_constructor_, i); |
| ASSERT_NE(member, nullptr); |
| array_members_[i] = ScopedJavaLocalRef<jobject>::Adopt(env, member); |
| env->SetObjectArrayElement(array_.obj(), i, member); |
| } |
| } |
| |
| // Make an Integer[] with len elements, all initialized to null. |
| ScopedJavaLocalRef<jobjectArray> MakeArray(jsize len) { |
| JNIEnv* env = AttachCurrentThread(); |
| jobjectArray array = env->NewObjectArray(len, int_class_.obj(), nullptr); |
| EXPECT_NE(array, nullptr); |
| return ScopedJavaLocalRef<jobjectArray>::Adopt(env, array); |
| } |
| |
| static constexpr jsize array_len_ = 10; |
| ScopedJavaLocalRef<jclass> int_class_; |
| jmethodID int_constructor_; |
| ScopedJavaLocalRef<jobject> array_members_[array_len_]; |
| ScopedJavaLocalRef<jobjectArray> array_; |
| }; |
| |
| // Must actually define the variable until C++17 :( |
| constexpr jsize JavaObjectArrayReaderTest::array_len_; |
| |
| TEST_F(JavaObjectArrayReaderTest, ZeroLengthArray) { |
| JavaObjectArrayReader<jobject> zero_length(MakeArray(0)); |
| EXPECT_TRUE(zero_length.empty()); |
| EXPECT_EQ(zero_length.size(), 0); |
| EXPECT_EQ(zero_length.begin(), zero_length.end()); |
| } |
| |
| // Verify that we satisfy the C++ "InputIterator" named requirements. |
| TEST_F(JavaObjectArrayReaderTest, InputIteratorRequirements) { |
| typedef JavaObjectArrayReader<jobject>::iterator It; |
| |
| JNIEnv* env = AttachCurrentThread(); |
| JavaObjectArrayReader<jobject> reader(array_); |
| It i = reader.begin(); |
| |
| EXPECT_TRUE(std::is_copy_constructible<It>::value); |
| It copy = i; |
| EXPECT_EQ(copy, i); |
| EXPECT_EQ(It(i), i); |
| |
| EXPECT_TRUE(std::is_copy_assignable<It>::value); |
| It assign = reader.end(); |
| It& assign2 = (assign = i); |
| EXPECT_EQ(assign, i); |
| EXPECT_EQ(assign2, assign); |
| |
| EXPECT_TRUE(std::is_destructible<It>::value); |
| |
| // Swappable |
| It left = reader.begin(), right = reader.end(); |
| std::swap(left, right); |
| EXPECT_EQ(left, reader.end()); |
| EXPECT_EQ(right, reader.begin()); |
| |
| // Basic check that iterator_traits works |
| bool same_type = std::is_same<std::iterator_traits<It>::iterator_category, |
| std::input_iterator_tag>::value; |
| EXPECT_TRUE(same_type); |
| |
| // Comparisons |
| EXPECT_EQ(reader.begin(), reader.begin()); |
| EXPECT_NE(reader.begin(), reader.end()); |
| |
| // Dereferencing |
| ScopedJavaLocalRef<jobject> o = *(reader.begin()); |
| EXPECT_SAME_OBJECT(o, array_members_[0]); |
| EXPECT_TRUE(env->IsSameObject(o.obj(), reader.begin()->obj())); |
| |
| // Incrementing |
| It preinc = ++(reader.begin()); |
| EXPECT_SAME_OBJECT(*preinc, array_members_[1]); |
| It postinc = reader.begin(); |
| EXPECT_SAME_OBJECT(*postinc++, array_members_[0]); |
| EXPECT_SAME_OBJECT(*postinc, array_members_[1]); |
| } |
| |
| // Check that range-based for and the convenience function work as expected. |
| TEST_F(JavaObjectArrayReaderTest, RangeBasedFor) { |
| JNIEnv* env = AttachCurrentThread(); |
| |
| int i = 0; |
| for (ScopedJavaLocalRef<jobject> element : array_.ReadElements<jobject>()) { |
| EXPECT_SAME_OBJECT(element, array_members_[i++]); |
| } |
| EXPECT_EQ(i, array_len_); |
| } |
| |
| } // namespace android |
| } // namespace base |