Skip to content

Commit 30f9b9e

Browse files
wilhuffa-maurice
authored andcommitted
Add JNI ownership types
These generate local and global reference subtypes of a given JNI wrapper that make it possible to automatically emit DeleteLocalRef and DeleteGlobalRef calls in the course of regular usage. PiperOrigin-RevId: 321175591
1 parent cb36be5 commit 30f9b9e

File tree

7 files changed

+734
-2
lines changed

7 files changed

+734
-2
lines changed

firestore/src/android/firestore_android.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "firestore/src/android/wrapper.h"
3939
#include "firestore/src/android/write_batch_android.h"
4040
#include "firestore/src/include/firebase/firestore.h"
41+
#include "firestore/src/jni/jni.h"
4142

4243
namespace firebase {
4344
namespace firestore {
@@ -144,6 +145,8 @@ FirestoreInternal::FirestoreInternal(App* app) {
144145
bool FirestoreInternal::Initialize(App* app) {
145146
MutexLock init_lock(init_mutex_);
146147
if (initialize_count_ == 0) {
148+
jni::Initialize(app->java_vm());
149+
147150
JNIEnv* env = app->GetJNIEnv();
148151
jobject activity = app->activity();
149152
if (!(firebase_firestore::CacheMethodIds(env, activity) &&

firestore/src/jni/jni.cc

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#include "firestore/src/jni/jni.h"
2+
3+
#include <pthread.h>
4+
5+
#include "app/src/assert.h"
6+
7+
namespace firebase {
8+
namespace firestore {
9+
namespace jni {
10+
namespace {
11+
12+
pthread_key_t jni_env_key;
13+
14+
JavaVM* g_jvm = nullptr;
15+
16+
/**
17+
* Reinterprets `JNIEnv**` out parameters to `void**` on platforms where that's
18+
* required.
19+
*/
20+
void** EnvOut(JNIEnv** env) { return reinterpret_cast<void**>(env); }
21+
22+
JavaVM* GetGlobalJavaVM() {
23+
// TODO(mcg): Use dlsym to call JNI_GetCreatedJavaVMs.
24+
FIREBASE_ASSERT_MESSAGE(
25+
g_jvm != nullptr,
26+
"Global JVM is unset; missing call to jni::Initialize()");
27+
return g_jvm;
28+
}
29+
30+
/**
31+
* A callback used by `pthread_key_create` to clean up thread-specific storage
32+
* when a thread is destroyed.
33+
*/
34+
void DetachCurrentThread(void* env) {
35+
JavaVM* jvm = g_jvm;
36+
if (jvm == nullptr || env == nullptr) {
37+
return;
38+
}
39+
40+
jint result = jvm->DetachCurrentThread();
41+
if (result != JNI_OK) {
42+
LogWarning("DetachCurrentThread failed to detach (result=%d)", result);
43+
}
44+
}
45+
46+
} // namespace
47+
48+
void Initialize(JavaVM* jvm) {
49+
g_jvm = jvm;
50+
51+
static pthread_once_t initialized = PTHREAD_ONCE_INIT;
52+
pthread_once(&initialized, [] {
53+
int err = pthread_key_create(&jni_env_key, DetachCurrentThread);
54+
FIREBASE_ASSERT_MESSAGE(err == 0, "pthread_key_create failed (errno=%d)",
55+
err);
56+
});
57+
}
58+
59+
JNIEnv* GetEnv() {
60+
JavaVM* jvm = GetGlobalJavaVM();
61+
62+
JNIEnv* env = nullptr;
63+
jint result = jvm->GetEnv(EnvOut(&env), JNI_VERSION_1_6);
64+
if (result == JNI_OK) {
65+
// Called from a JVM-managed thread or from a thread that was previously
66+
// attached. In either case, there's no work to be done.
67+
return env;
68+
}
69+
70+
// The only other documented error is JNI_EVERSION, but all supported Android
71+
// implementations support JNI 1.6 so this shouldn't happen.
72+
FIREBASE_ASSERT_MESSAGE(result == JNI_EDETACHED,
73+
"GetEnv failed with an unexpected error (result=%d)",
74+
result);
75+
76+
// If we've gotten here, the current thread is a native thread that has not
77+
// been attached, so we need to attach it and set up a thread-local
78+
// destructor.
79+
result = jvm->AttachCurrentThread(&env, nullptr);
80+
FIREBASE_ASSERT_MESSAGE(result == JNI_OK,
81+
"JNI AttachCurrentThread failed (result=%d)", result);
82+
83+
result = pthread_setspecific(jni_env_key, env);
84+
FIREBASE_ASSERT_MESSAGE(result == 0,
85+
"JNI pthread_setspecific failed (errno=%d)", result);
86+
return env;
87+
}
88+
89+
} // namespace jni
90+
} // namespace firestore
91+
} // namespace firebase

firestore/src/jni/jni.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#ifndef FIREBASE_FIRESTORE_CLIENT_CPP_SRC_JNI_JNI_H_
2+
#define FIREBASE_FIRESTORE_CLIENT_CPP_SRC_JNI_JNI_H_
3+
4+
#include <jni.h>
5+
6+
namespace firebase {
7+
namespace firestore {
8+
namespace jni {
9+
10+
/**
11+
* Initializes the global `JavaVM` pointer. Should be called once per process
12+
* execution.
13+
*/
14+
void Initialize(JavaVM* vm);
15+
16+
/**
17+
* Returns the `JNIEnv` pointer for the current thread.
18+
*/
19+
JNIEnv* GetEnv();
20+
21+
} // namespace jni
22+
} // namespace firestore
23+
} // namespace firebase
24+
25+
#endif // FIREBASE_FIRESTORE_CLIENT_CPP_SRC_JNI_JNI_H_

firestore/src/jni/jni_fwd.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#ifndef FIREBASE_FIRESTORE_CLIENT_CPP_SRC_JNI_JNI_FWD_H_
2+
#define FIREBASE_FIRESTORE_CLIENT_CPP_SRC_JNI_JNI_FWD_H_
3+
4+
namespace firebase {
5+
namespace firestore {
6+
namespace jni {
7+
8+
/**
9+
* Returns the `JNIEnv` pointer for the current thread.
10+
*/
11+
JNIEnv* GetEnv();
12+
13+
// Reference types
14+
template <typename T>
15+
class Local;
16+
template <typename T>
17+
class Global;
18+
template <typename T>
19+
class NonOwning;
20+
21+
class Object;
22+
23+
} // namespace jni
24+
} // namespace firestore
25+
} // namespace firebase
26+
27+
#endif // FIREBASE_FIRESTORE_CLIENT_CPP_SRC_JNI_JNI_FWD_H_

firestore/src/jni/ownership.h

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#ifndef FIREBASE_FIRESTORE_CLIENT_CPP_SRC_JNI_OWNERSHIP_H_
2+
#define FIREBASE_FIRESTORE_CLIENT_CPP_SRC_JNI_OWNERSHIP_H_
3+
4+
#include <jni.h>
5+
6+
#include <string>
7+
8+
#include "firestore/src/jni/jni_fwd.h"
9+
#include "firestore/src/jni/traits.h"
10+
11+
namespace firebase {
12+
namespace firestore {
13+
namespace jni {
14+
15+
/**
16+
* An RAII wrapper for a local JNI reference that automatically deletes the JNI
17+
* local reference when it goes out of scope. Copies and moves are handled by
18+
* creating additional references as required.
19+
*
20+
* @tparam T A JNI reference wrapper, usually a subclass of `Object`.
21+
*/
22+
template <typename T>
23+
class Local : public T {
24+
public:
25+
using jni_type = JniType<T>;
26+
27+
Local() = default;
28+
29+
/**
30+
* Adopts a local reference that is the result of a JNI invocation.
31+
*/
32+
Local(JNIEnv* env, jni_type value) : T(value), env_(env) {}
33+
34+
Local(const Local& other) = delete;
35+
Local& operator=(const Local&) = delete;
36+
37+
Local(Local&& other) noexcept : T(other.release()), env_(other.env_) {}
38+
39+
Local& operator=(Local&& other) noexcept {
40+
if (T::object_ != other.get()) {
41+
EnsureEnv(other.env());
42+
env_->DeleteLocalRef(T::object_);
43+
T::object_ = other.release();
44+
}
45+
return *this;
46+
}
47+
48+
Local(const Global<T>& other) {
49+
EnsureEnv();
50+
T::object_ = env_->NewLocalRef(other.get());
51+
}
52+
53+
Local& operator=(const Global<T>& other) {
54+
EnsureEnv();
55+
env_->DeleteLocalRef(T::object_);
56+
T::object_ = env_->NewLocalRef(other.get());
57+
return *this;
58+
}
59+
60+
Local(Global<T>&& other) noexcept {
61+
EnsureEnv();
62+
T::object_ = env_->NewLocalRef(other.get());
63+
env_->DeleteGlobalRef(other.release());
64+
}
65+
66+
Local& operator=(Global<T>&& other) noexcept {
67+
EnsureEnv();
68+
env_->DeleteLocalRef(T::object_);
69+
T::object_ = env_->NewLocalRef(other.get());
70+
env_->DeleteGlobalRef(other.release());
71+
return *this;
72+
}
73+
74+
~Local() {
75+
if (env_ && T::object_) {
76+
env_->DeleteLocalRef(T::object_);
77+
}
78+
}
79+
80+
jni_type get() const override { return static_cast<jni_type>(T::object_); }
81+
82+
jni_type release() {
83+
jobject result = T::object_;
84+
T::object_ = nullptr;
85+
return static_cast<jni_type>(result);
86+
}
87+
88+
JNIEnv* env() const { return env_; }
89+
90+
private:
91+
void EnsureEnv(JNIEnv* other = nullptr) {
92+
if (env_ == nullptr) {
93+
if (other != nullptr) {
94+
env_ = other;
95+
} else {
96+
env_ = GetEnv();
97+
}
98+
}
99+
}
100+
101+
JNIEnv* env_ = nullptr;
102+
};
103+
104+
/**
105+
* Global references are almost always created by promoting local references.
106+
* Aside from `NewGlobalRef`, there are no JNI APIs that return global
107+
* references. You can construct a `Global` wrapper with `AdoptExisting::kYes`
108+
* in the rare case that you're interoperating with other APIs that produce
109+
* global JNI references.
110+
*/
111+
enum class AdoptExisting { kYes };
112+
113+
/**
114+
* An RAII wrapper for a global JNI reference, that automatically deletes the
115+
* JNI global reference when it goes out of scope. Copies and moves are handled
116+
* by creating additional references as required.
117+
*
118+
* @tparam T A JNI reference wrapper, usually a subclass of `Object`.
119+
*/
120+
template <typename T>
121+
class Global : public T {
122+
public:
123+
using jni_type = JniType<T>;
124+
125+
Global() = default;
126+
127+
Global(jni_type object, AdoptExisting) : T(object) {}
128+
129+
Global(const Local<T>& other) {
130+
JNIEnv* env = EnsureEnv(other.env());
131+
T::object_ = env->NewGlobalRef(other.get());
132+
}
133+
134+
Global& operator=(const Local<T>& other) {
135+
JNIEnv* env = EnsureEnv(other.env());
136+
env->DeleteGlobalRef(T::object_);
137+
T::object_ = env->NewGlobalRef(other.get());
138+
return *this;
139+
}
140+
141+
Global(const T& other) {
142+
JNIEnv* env = GetEnv();
143+
T::object_ = env->NewGlobalRef(other.get());
144+
}
145+
146+
Global& operator=(const T& other) {
147+
if (T::object_ != other.get()) {
148+
JNIEnv* env = GetEnv();
149+
env->DeleteGlobalRef(T::object_);
150+
T::object_ = env->NewGlobalRef(other.get());
151+
}
152+
return *this;
153+
}
154+
155+
// Explicitly declare a copy constructor and copy assignment operator because
156+
// there's an explicitly declared move constructor for this type.
157+
//
158+
// Without this, the implicitly-defined copy constructor would be deleted, and
159+
// during overload resolution the deleted copy constructor would take priority
160+
// over the looser match above that takes `const T&`.
161+
Global(const Global& other) : Global(static_cast<const T&>(other)) {}
162+
163+
Global& operator=(const Global& other) {
164+
*this = static_cast<const T&>(other);
165+
return *this;
166+
}
167+
168+
Global(Global&& other) noexcept : T(other.release()) {}
169+
170+
Global& operator=(Global&& other) noexcept {
171+
if (T::object_ != other.get()) {
172+
if (T::object_) {
173+
JNIEnv* env = GetEnv();
174+
env->DeleteGlobalRef(T::object_);
175+
}
176+
T::object_ = other.release();
177+
}
178+
return *this;
179+
}
180+
181+
Global(Local<T>&& other) noexcept {
182+
JNIEnv* env = EnsureEnv(other.env());
183+
T::object_ = env->NewGlobalRef(other.get());
184+
env->DeleteLocalRef(other.release());
185+
}
186+
187+
Global& operator=(Local<T>&& other) noexcept {
188+
JNIEnv* env = EnsureEnv(other.env());
189+
env->DeleteGlobalRef(T::object_);
190+
T::object_ = env->NewGlobalRef(other.get());
191+
env->DeleteLocalRef(other.release());
192+
return *this;
193+
}
194+
195+
~Global() {
196+
if (T::object_) {
197+
JNIEnv* env = GetEnv();
198+
env->DeleteGlobalRef(T::object_);
199+
}
200+
}
201+
202+
jni_type get() const override { return static_cast<jni_type>(T::object_); }
203+
204+
jni_type release() {
205+
jobject result = T::object_;
206+
T::object_ = nullptr;
207+
return static_cast<jni_type>(result);
208+
}
209+
210+
private:
211+
JNIEnv* EnsureEnv(JNIEnv* other = nullptr) {
212+
if (other != nullptr) {
213+
return other;
214+
} else {
215+
return GetEnv();
216+
}
217+
}
218+
};
219+
220+
} // namespace jni
221+
} // namespace firestore
222+
} // namespace firebase
223+
224+
#endif // FIREBASE_FIRESTORE_CLIENT_CPP_SRC_JNI_OWNERSHIP_H_

0 commit comments

Comments
 (0)