|  | // 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 "base/compiler_specific.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_v<ScopedJavaLocalRef<jobject>, | 
|  | ScopedJavaGlobalRef<jobject>>, | 
|  | ""); | 
|  | static_assert(!std::is_convertible_v<ScopedJavaGlobalRef<jobject>, | 
|  | ScopedJavaLocalRef<jobject>>, | 
|  | ""); | 
|  | } | 
|  |  | 
|  | // 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); | 
|  | UNSAFE_TODO(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_v<It>); | 
|  | It copy = i; | 
|  | EXPECT_EQ(copy, i); | 
|  | EXPECT_EQ(It(i), i); | 
|  |  | 
|  | EXPECT_TRUE(std::is_copy_assignable_v<It>); | 
|  | It assign = reader.end(); | 
|  | It& assign2 = (assign = i); | 
|  | EXPECT_EQ(assign, i); | 
|  | EXPECT_EQ(assign2, assign); | 
|  |  | 
|  | EXPECT_TRUE(std::is_destructible_v<It>); | 
|  |  | 
|  | // 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_v<std::iterator_traits<It>::iterator_category, | 
|  | std::input_iterator_tag>; | 
|  | 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>()) { | 
|  | UNSAFE_TODO(EXPECT_SAME_OBJECT(element, array_members_[i++])); | 
|  | } | 
|  | EXPECT_EQ(i, array_len_); | 
|  | } | 
|  |  | 
|  | }  // namespace android | 
|  | }  // namespace base |