-
Notifications
You must be signed in to change notification settings - Fork 119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement ObjectArena and ArenaRef to solve JNI Global Reference Issue #1176
Changes from all commits
3acc2af
8975580
d30becc
8ef27ff
ebc739b
ddf359c
ce47da6
476fd44
3ec8673
5db27da
6c3fc50
54c0826
7e14163
84cbd19
b599bfa
e7879c5
84400cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
/* | ||
* Copyright 2023 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
#include "firestore/src/jni/arena_ref.h" | ||
#include "firestore/src/android/firestore_android.h" | ||
#include "firestore/src/common/make_unique.h" | ||
#include "firestore/src/jni/loader.h" | ||
#include "firestore/src/jni/long.h" | ||
|
||
#include "firestore_integration_test.h" | ||
|
||
#include "gtest/gtest.h" | ||
|
||
namespace firebase { | ||
namespace firestore { | ||
namespace jni { | ||
namespace { | ||
|
||
constexpr char kException[] = "java/lang/Exception"; | ||
Method<String> kConstructor("<init>", "(Ljava/lang/String;)V"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Rename |
||
|
||
class ArenaRefTestAndroid : public FirestoreIntegrationTest { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This looks after everything that you are re-implementing in this class:
In fact, I think you should just go back to |
||
public: | ||
ArenaRefTestAndroid() : loader_(app()), env_(make_unique<Env>(GetEnv())) { | ||
loader_.LoadClass(kException); | ||
loader_.Load(kConstructor); | ||
} | ||
|
||
~ArenaRefTestAndroid() override { | ||
// Ensure that after the test is done that any pending exception is cleared | ||
// to prevent spurious errors in the teardown of FirestoreIntegrationTest. | ||
env_->ExceptionClear(); | ||
} | ||
|
||
Env& env() const { return *env_; } | ||
|
||
void throwException() { | ||
Local<Class> clazz = env().FindClass(kException); | ||
jmethodID ctor = | ||
env().GetMethodId(clazz, "<init>", "(Ljava/lang/String;)V"); | ||
|
||
Local<String> message = env().NewStringUtf("Testing throw"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can just get rid of these throwException() and clearExceptionOccurred() helper methods. throwException() can be replaced by env.Throw(CreateException(env, "Test Exception")); and clearExceptionOccurred() can be replaced by env.ExceptionClear(); Admittedly, this leads to a tiny bit of code duplciation between test cases but that's acceptable because, IMO, it improves readabililty of the test cases. See go/tott/598 about "DAMP" tests rather than the "DRY" principle that is common in non-testing code. |
||
Local<Throwable> exception = env().New<Throwable>(clazz, ctor, message); | ||
// After throwing, use EXPECT rather than ASSERT to ensure that the | ||
// exception is cleared. | ||
env().Throw(exception); | ||
EXPECT_FALSE(env().ok()); | ||
} | ||
|
||
void clearExceptionOccurred() { | ||
Local<Throwable> thrown = env().ClearExceptionOccurred(); | ||
EXPECT_EQ(thrown.GetMessage(env()), "Testing throw"); | ||
} | ||
|
||
private: | ||
// Env is declared as having a `noexcept(false)` destructor, which causes the | ||
// compiler to propagate this into `EnvTest`'s destructor, but this conflicts | ||
// with the declaration of the parent class. Holding `Env` with a unique | ||
// pointer sidesteps this restriction. | ||
std::unique_ptr<Env> env_; | ||
Loader loader_; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't hold an Env in an instance variable. Instead, just do |
||
}; | ||
|
||
TEST_F(ArenaRefTestAndroid, DefaultConstructor) { | ||
ArenaRef arena_ref; | ||
EXPECT_EQ(arena_ref.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, ConstructsFromNull) { | ||
Local<String> string; | ||
ArenaRef arena_ref(env(), string); | ||
EXPECT_EQ(arena_ref.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, ConstructsFromValid) { | ||
Local<String> string = env().NewStringUtf("hello world"); | ||
ArenaRef arena_ref(env(), string); | ||
EXPECT_TRUE(env().IsSameObject(arena_ref.get(env()), string)); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, CopyConstructsFromNull) { | ||
ArenaRef arena_ref1; | ||
ArenaRef arena_ref2(arena_ref1); | ||
EXPECT_EQ(arena_ref2.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, CopyConstructsFromValid) { | ||
Local<String> string = env().NewStringUtf("hello world"); | ||
|
||
ArenaRef arena_ref1(env(), string); | ||
ArenaRef arena_ref2(arena_ref1); | ||
EXPECT_TRUE(env().IsSameObject(arena_ref1.get(env()), string)); | ||
EXPECT_TRUE(env().IsSameObject(arena_ref2.get(env()), string)); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, CopyAssignsFromNullToNull) { | ||
ArenaRef arena_ref1, arena_ref2; | ||
arena_ref2 = arena_ref1; | ||
EXPECT_EQ(arena_ref1.get(env()).get(), nullptr); | ||
EXPECT_EQ(arena_ref2.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, CopyAssignsFromNullToValid) { | ||
Local<String> string = env().NewStringUtf("hello world"); | ||
|
||
ArenaRef arena_ref1; | ||
ArenaRef arena_ref2(env(), string); | ||
arena_ref2 = arena_ref1; | ||
EXPECT_EQ(arena_ref1.get(env()).get(), nullptr); | ||
EXPECT_EQ(arena_ref2.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, CopyAssignsFromValidToNull) { | ||
Local<String> string = env().NewStringUtf("hello world"); | ||
|
||
ArenaRef arena_ref1; | ||
ArenaRef arena_ref2(env(), string); | ||
arena_ref1 = arena_ref2; | ||
EXPECT_TRUE(env().IsSameObject(arena_ref1.get(env()), string)); | ||
EXPECT_TRUE(env().IsSameObject(arena_ref2.get(env()), string)); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, CopyAssignsFromValidToValid) { | ||
Local<String> string1 = env().NewStringUtf("hello world"); | ||
Local<String> string2 = env().NewStringUtf("hello earth"); | ||
|
||
ArenaRef arena_ref1(env(), string1); | ||
ArenaRef arena_ref2(env(), string2); | ||
arena_ref1 = arena_ref2; | ||
|
||
EXPECT_TRUE(env().IsSameObject(arena_ref1.get(env()), string2)); | ||
EXPECT_TRUE(env().IsSameObject(arena_ref2.get(env()), string2)); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, CopyAssignsFromNullObjectItself) { | ||
ArenaRef arena_ref1; | ||
arena_ref1 = arena_ref1; | ||
EXPECT_EQ(arena_ref1.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, CopyAssignsFromValidObjectItself) { | ||
Local<String> string1 = env().NewStringUtf("hello world"); | ||
|
||
ArenaRef arena_ref1(env(), string1); | ||
arena_ref1 = arena_ref1; | ||
EXPECT_TRUE(env().IsSameObject(arena_ref1.get(env()), string1)); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, MoveConstructsFromNull) { | ||
ArenaRef arena_ref1; | ||
ArenaRef arena_ref2(std::move(arena_ref1)); | ||
EXPECT_EQ(arena_ref2.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, MoveConstructsFromValid) { | ||
Local<String> string = env().NewStringUtf("hello world"); | ||
|
||
ArenaRef arena_ref2(env(), string); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Rename |
||
ArenaRef arena_ref3(std::move(arena_ref2)); | ||
EXPECT_TRUE(env().IsSameObject(arena_ref3.get(env()), string)); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, MoveAssignsFromNullToNull) { | ||
ArenaRef arena_ref1, arena_ref2; | ||
arena_ref2 = std::move(arena_ref1); | ||
EXPECT_EQ(arena_ref2.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, MoveAssignsFromNullToValid) { | ||
Local<String> string = env().NewStringUtf("hello world"); | ||
|
||
ArenaRef arena_ref1; | ||
ArenaRef arena_ref2(env(), string); | ||
arena_ref2 = std::move(arena_ref1); | ||
EXPECT_EQ(arena_ref2.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, MoveAssignsFromValidToNull) { | ||
Local<String> string = env().NewStringUtf("hello world"); | ||
|
||
ArenaRef arena_ref1; | ||
ArenaRef arena_ref2(env(), string); | ||
arena_ref1 = std::move(arena_ref2); | ||
EXPECT_TRUE(env().IsSameObject(arena_ref1.get(env()), string)); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, MoveAssignsFromValidToValid) { | ||
Local<String> string1 = env().NewStringUtf("hello world"); | ||
Local<String> string2 = env().NewStringUtf("hello earth"); | ||
|
||
ArenaRef arena_ref1(env(), string1); | ||
ArenaRef arena_ref2(env(), string2); | ||
arena_ref1 = std::move(arena_ref2); | ||
EXPECT_TRUE(env().IsSameObject(arena_ref1.get(env()), string2)); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, MoveAssignsFromNullObjectItself) { | ||
ArenaRef arena_ref1; | ||
arena_ref1 = std::move(arena_ref1); | ||
EXPECT_EQ(arena_ref1.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, MoveAssignsFromValidObjectItself) { | ||
Local<String> string1 = env().NewStringUtf("hello world"); | ||
|
||
ArenaRef arena_ref1(env(), string1); | ||
arena_ref1 = std::move(arena_ref1); | ||
EXPECT_TRUE(env().IsSameObject(arena_ref1.get(env()), string1)); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, ThrowBeforeConstruct) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Can you rename the "ThrowBeforeXXX" test names to "XXXWithPendingException" or something like that? I think using the term "with pending exception" more clearly conveys what scenario the test is testing. |
||
Local<String> string = env().NewStringUtf("hello world"); | ||
EXPECT_EQ(string.ToString(env()).size(), 11U); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove these checks for the length of the string returned from |
||
throwException(); | ||
ArenaRef arena_ref(env(), string); | ||
clearExceptionOccurred(); | ||
EXPECT_EQ(arena_ref.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, ThrowBeforeCopyConstruct) { | ||
Local<String> string = env().NewStringUtf("hello world"); | ||
ArenaRef arena_ref1(env(), string); | ||
EXPECT_EQ(arena_ref1.get(env()).ToString(env()).size(), 11U); | ||
throwException(); | ||
ArenaRef arena_ref2(arena_ref1); | ||
clearExceptionOccurred(); | ||
EXPECT_EQ(arena_ref2.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, ThrowBeforeCopyAssignment) { | ||
Local<String> string = env().NewStringUtf("hello world"); | ||
ArenaRef arena_ref1(env(), string); | ||
ArenaRef arena_ref2; | ||
EXPECT_EQ(arena_ref1.get(env()).ToString(env()).size(), 11U); | ||
throwException(); | ||
arena_ref2 = arena_ref1; | ||
clearExceptionOccurred(); | ||
EXPECT_EQ(arena_ref2.get(env()).get(), nullptr); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, ThrowBeforeMoveConstruct) { | ||
Local<String> string = env().NewStringUtf("hello world"); | ||
ArenaRef arena_ref1(env(), string); | ||
EXPECT_EQ(arena_ref1.get(env()).ToString(env()).size(), 11U); | ||
throwException(); | ||
ArenaRef arena_ref2(std::move(arena_ref1)); | ||
clearExceptionOccurred(); | ||
EXPECT_TRUE(env().IsSameObject(arena_ref2.get(env()), string)); | ||
} | ||
|
||
TEST_F(ArenaRefTestAndroid, ThrowBeforeMoveAssignment) { | ||
Local<String> string = env().NewStringUtf("hello world"); | ||
ArenaRef arena_ref1(env(), string); | ||
ArenaRef arena_ref2; | ||
EXPECT_EQ(arena_ref1.get(env()).ToString(env()).size(), 11U); | ||
throwException(); | ||
arena_ref2 = std::move(arena_ref1); | ||
clearExceptionOccurred(); | ||
EXPECT_TRUE(env().IsSameObject(arena_ref2.get(env()), string)); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a test for calling get() with a pending exception. |
||
} // namespace | ||
} // namespace jni | ||
} // namespace firestore | ||
} // namespace firebase |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -128,6 +128,18 @@ TEST_F(EnvTest, CallsVoidMethods) { | |
EXPECT_EQ(length, 42); | ||
} | ||
|
||
TEST_F(EnvTest, CallsValidArenaRef) { | ||
Local<String> str = env().NewStringUtf("Foo"); | ||
ArenaRef arena_ref(env(), str); | ||
|
||
Local<Class> clazz = env().FindClass("java/lang/String"); | ||
jmethodID to_lower_case = | ||
env().GetMethodId(clazz, "toLowerCase", "()Ljava/lang/String;"); | ||
|
||
Local<Object> result = env().Call(arena_ref, Method<Object>(to_lower_case)); | ||
EXPECT_EQ("foo", result.ToString(env())); | ||
} | ||
|
||
TEST_F(EnvTest, GetsStaticFields) { | ||
Local<Class> clazz = env().FindClass("java/lang/String"); | ||
jfieldID comparator = env().GetStaticFieldId(clazz, "CASE_INSENSITIVE_ORDER", | ||
|
@@ -160,6 +172,21 @@ TEST_F(EnvTest, ToString) { | |
EXPECT_EQ("Foo", result); | ||
} | ||
|
||
TEST_F(EnvTest, IsInstanceOfChecksValidArenaRef) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Augment the |
||
Local<Class> clazz = env().FindClass("java/lang/String"); | ||
Local<String> str = env().NewStringUtf("Foo"); | ||
ArenaRef arena_ref(env(), str); | ||
EXPECT_TRUE(env().IsInstanceOf(arena_ref, clazz)); | ||
} | ||
|
||
TEST_F(EnvTest, IsInstanceOfChecksNullArenaRef) { | ||
GTEST_SKIP() | ||
<< "Skip until edge case of IsInstanceOf (b/268420201) is covered."; | ||
Local<Class> clazz = env().FindClass("java/lang/String"); | ||
ArenaRef arena_ref; | ||
EXPECT_FALSE(env().IsInstanceOf(arena_ref, clazz)); | ||
} | ||
|
||
TEST_F(EnvTest, Throw) { | ||
Local<Class> clazz = env().FindClass("java/lang/Exception"); | ||
jmethodID ctor = env().GetMethodId(clazz, "<init>", "(Ljava/lang/String;)V"); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment to
TestArenaRefMinimunLimit
to mention the global refs issue that it's checking and include a link to the issue that it's verifying doesn't regress (i.e. firebase/firebase-unity-sdk#569)