Skip to content

Commit

Permalink
Fix broken promotion mechanics for richly decorated locals to globals.
Browse files Browse the repository at this point in the history
**THIS IS A BREAKING CHANGE** Locals were being blindly absorbed by globals which is wrong. Clients are unlikely to be broken because this logic would cause crashes on use, however, I can't guarantee there isn't a path that relied on this.

PiperOrigin-RevId: 543592694
  • Loading branch information
jwhpryor authored and copybara-github committed Jun 27, 2023
1 parent 1cf0e8d commit 8c26f2e
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 6 deletions.
29 changes: 27 additions & 2 deletions implementation/global_object_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ using ::jni::Constructor;
using ::jni::CreateCopy;
using ::jni::Field;
using ::jni::GlobalObject;
using ::jni::LocalObject;
using ::jni::Method;
using ::jni::Params;
using ::jni::PromoteToGlobal;
Expand Down Expand Up @@ -75,7 +76,7 @@ TEST_F(JniTest, GlobalObject_DoesNotDeleteAnyLocalsForAdoptedGlobalJobject) {

GlobalObject<kClass> global_object{AdoptGlobal{}, Fake<jobject>()};

EXPECT_NE(jobject{global_object}, nullptr);
EXPECT_EQ(jobject{global_object}, Fake<jobject>());
}

TEST_F(JniTest, GlobalObject_PromotesJobjectsOnConstruction) {
Expand All @@ -85,8 +86,32 @@ TEST_F(JniTest, GlobalObject_PromotesJobjectsOnConstruction) {

static constexpr Class kClass{"kClass"};
GlobalObject<kClass> global_object{PromoteToGlobal{}, Fake<jobject>()};
EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>()));
}

EXPECT_NE(jobject{global_object}, nullptr);
TEST_F(JniTest, GlobalObject_PromotesDecoratedLocals) {
EXPECT_CALL(*env_, NewObjectV).Times(0);
EXPECT_CALL(*env_, DeleteLocalRef).Times(1);
EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>())));

static constexpr Class kClass{"kClass"};
LocalObject<kClass> local_obj{Fake<jobject>()};
// GlobalObject<kClass> global_object{local_obj}; // doesn't compile (good).
GlobalObject<kClass> global_object{std::move(local_obj)};

EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>()));
}

// Identical to above but Local constructed in place.
TEST_F(JniTest, GlobalObject_PromotesDecoratedLocalsFromXValue) {
EXPECT_CALL(*env_, NewObjectV).Times(0);
EXPECT_CALL(*env_, DeleteLocalRef).Times(1);
EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>())));

static constexpr Class kClass{"kClass"};
GlobalObject<kClass> global_object{LocalObject<kClass>{Fake<jobject>()}};

EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>()));
}

TEST_F(JniTest, GlobalObject_CallsOnlyDeleteOnWrapCtor) {
Expand Down
28 changes: 24 additions & 4 deletions implementation/promotion_mechanics.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,17 @@ struct AdoptGlobal {};
// Marks the end of `ScopeEntry` daisy chain.
struct ScopedTerminalTag {};

// Shared implementation common to all `Entry`.
// template <typename Base, LifecycleType lifecycleType, typename JniT>
// Shared implementation common to all *local* `Entry`.
template <typename Base, LifecycleType lifecycleType, typename JniT,
typename ViableSpan>
struct EntryBase : public Base {
using Base::Base;
using Span = typename JniT::SpanType;

// `RefBaseTag` move constructor for object of same span type.
template <typename T, typename = std::enable_if_t<
template <typename T, typename = std::enable_if_t<(
::jni::metaprogramming::DeepEqual_v<EntryBase, T> ||
std::is_base_of_v<RefBaseTag<Span>, T>>>
std::is_base_of_v<RefBaseTag<Span>, T>)>>
EntryBase(T&& rhs) : Base(rhs.Release()) {}

// "Copy" constructor: Additional reference to object will be created.
Expand All @@ -79,6 +78,27 @@ struct Entry
: Base(static_cast<typename JniT::StorageType>(object)) {}
};

// Shared implementation common to all *global* `Entry`.
template <typename Base, typename JniT, typename ViableSpan>
struct EntryBase<Base, LifecycleType::GLOBAL, JniT, ViableSpan> : public Base {
using Base::Base;
using Span = typename JniT::SpanType;

// `RefBaseTag` move constructor for object of same span type.
template <typename T, typename = std::enable_if_t<(
::jni::metaprogramming::DeepEqual_v<EntryBase, T> ||
std::is_base_of_v<RefBaseTag<Span>, T>)>>
EntryBase(T&& rhs)
: Base(LifecycleHelper<typename JniT::StorageType,
LifecycleType::GLOBAL>::Promote(rhs.Release())) {}

// "Copy" constructor: Additional reference to object will be created.
EntryBase(CreateCopy, ViableSpan object)
: Base(static_cast<Span>(
LifecycleHelper<Span, LifecycleType::GLOBAL>::NewReference(
static_cast<Span>(object)))) {}
};

// Global scoped entry augmentation.
template <typename JniT, typename ViableSpan, typename... ViableSpans>
struct Entry<LifecycleType::GLOBAL, JniT, ViableSpan, ViableSpans...>
Expand Down

0 comments on commit 8c26f2e

Please sign in to comment.