341 lines
11 KiB
C++
341 lines
11 KiB
C++
// 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
|