Skip to content

Commit a5a8358

Browse files
committed
Initial attempt to port internals to JNI 0.22
1 parent e9cf369 commit a5a8358

File tree

4 files changed

+196
-218
lines changed

4 files changed

+196
-218
lines changed

rustls-platform-verifier/src/android.rs

Lines changed: 79 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
//! ```
2121
2222
use jni::errors::Error as JNIError;
23-
use jni::objects::{GlobalRef, JClass, JObject, JValue};
24-
use jni::{AttachGuard, JNIEnv, JavaVM};
23+
use jni::objects::{Global, JClass, JClassLoader, JObject};
24+
use jni::{Env, JavaVM};
2525
use once_cell::sync::OnceCell;
26+
use std::ffi::CStr;
2627

27-
static GLOBAL: OnceCell<Global> = OnceCell::new();
28+
static GLOBAL: OnceCell<GlobalStorage> = OnceCell::new();
2829

2930
/// A layer to access the Android runtime which is hosting the current
3031
/// application process.
@@ -37,101 +38,83 @@ pub trait Runtime: Send + Sync {
3738
/// Returns a reference to the current app's [Context].
3839
///
3940
/// [Context]: <https://developer.android.com/reference/android/content/Context>
40-
fn context(&self) -> &GlobalRef;
41+
fn context(&self) -> &Global<JObject<'static>>;
4142
/// Returns a reference to the class returned by the current JVM's `getClassLoader` call.
42-
fn class_loader(&self) -> &GlobalRef;
43+
fn class_loader(&self) -> &Global<JClassLoader<'static>>;
4344
}
4445

45-
enum Global {
46+
enum GlobalStorage {
4647
Internal {
4748
java_vm: JavaVM,
48-
context: GlobalRef,
49-
loader: GlobalRef,
49+
context: Global<JObject<'static>>,
50+
loader: Global<JClassLoader<'static>>,
5051
},
5152
External(&'static dyn Runtime),
5253
}
5354

54-
impl Global {
55-
fn env(&self) -> Result<AttachGuard<'_>, Error> {
56-
let vm = match self {
57-
Global::Internal { java_vm, .. } => java_vm,
58-
Global::External(global) => global.java_vm(),
59-
};
60-
Ok(vm.attach_current_thread()?)
55+
impl GlobalStorage {
56+
fn vm(&self) -> &JavaVM {
57+
match self {
58+
GlobalStorage::Internal { java_vm, .. } => java_vm,
59+
GlobalStorage::External(runtime) => runtime.java_vm(),
60+
}
6161
}
6262

63-
fn context(&self) -> Result<(GlobalContext, AttachGuard<'_>), Error> {
64-
let env = self.env()?;
65-
63+
fn context(&self, env: &mut Env) -> Result<GlobalContext, Error> {
6664
let context = match self {
67-
Global::Internal { context, .. } => context,
68-
Global::External(global) => global.context(),
65+
Self::Internal { context, .. } => context,
66+
Self::External(global) => global.context(),
6967
};
7068

7169
let loader = match self {
72-
Global::Internal { loader, .. } => loader,
73-
Global::External(global) => global.class_loader(),
70+
Self::Internal { loader, .. } => loader,
71+
Self::External(global) => global.class_loader(),
7472
};
7573

76-
Ok((
77-
GlobalContext {
78-
context: env.new_global_ref(context)?,
79-
loader: env.new_global_ref(loader)?,
80-
},
81-
env,
82-
))
74+
Ok(GlobalContext {
75+
context: env.new_global_ref(context)?,
76+
loader: env.new_global_ref(loader)?,
77+
})
8378
}
8479
}
8580

86-
struct GlobalContext {
87-
context: GlobalRef,
88-
loader: GlobalRef,
81+
pub(super) struct GlobalContext {
82+
/// The Android application [Context](https://developer.android.com/reference/android/app/Application).
83+
pub(super) context: Global<JObject<'static>>,
84+
loader: Global<JClassLoader<'static>>,
8985
}
9086

91-
fn global() -> &'static Global {
87+
fn global() -> &'static GlobalStorage {
9288
GLOBAL
9389
.get()
9490
.expect("Expect rustls-platform-verifier to be initialized")
9591
}
9692

97-
/// Initialize given a typical Android NDK [`JNIEnv`] and [`JObject`] context.
93+
/// Initialize given a typical Android NDK [`Env`] and [`JObject`] context.
9894
///
9995
/// This method will setup and store an environment locally. This is useful if nothing else in your
10096
/// application needs to access the Android runtime.
101-
pub fn init_with_env(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> {
97+
pub fn init_with_env(env: &mut Env, context: JObject) -> Result<(), JNIError> {
10298
GLOBAL.get_or_try_init(|| -> Result<_, JNIError> {
103-
let loader =
104-
env.call_method(&context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?;
99+
let loader = env.get_object_class(&context)?.get_class_loader(env)?;
105100

106-
Ok(Global::Internal {
107-
java_vm: env.get_java_vm()?,
101+
Ok(GlobalStorage::Internal {
102+
java_vm: env.get_java_vm(),
108103
context: env.new_global_ref(context)?,
109-
loader: env.new_global_ref(JObject::try_from(loader)?)?,
104+
loader: env.new_global_ref(loader)?,
110105
})
111106
})?;
112107
Ok(())
113108
}
114109

115-
/// *Deprecated*: This is the original method name for [`init_with_env`] and is functionally
116-
/// identical.
117-
pub fn init_hosted(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> {
118-
init_with_env(env, context)
119-
}
120-
121110
/// Initialize with a runtime that can dynamically serve references to
122111
/// the JVM, context, and class loader.
123112
///
124113
/// This is the most flexible option, and is useful for advanced use cases.
125114
///
126115
/// This function will never panic.
127116
pub fn init_with_runtime(runtime: &'static dyn Runtime) {
128-
GLOBAL.get_or_init(|| Global::External(runtime));
129-
}
130-
131-
/// *Deprecated*: This is the original method name for [`init_with_runtime`] and is functionally
132-
/// identical.
133-
pub fn init_external(runtime: &'static dyn Runtime) {
134-
init_with_runtime(runtime);
117+
GLOBAL.get_or_init(|| GlobalStorage::External(runtime));
135118
}
136119

137120
/// Initialize with references to the JVM, context, and class loader.
@@ -145,19 +128,25 @@ pub fn init_external(runtime: &'static dyn Runtime) {
145128
///
146129
/// ```
147130
/// pub fn android_init(raw_env: *mut c_void, raw_context: *mut c_void) -> Result<(), jni::errors::Error> {
148-
/// let mut env = unsafe { jni::JNIEnv::from_raw(raw_env as *mut jni::sys::JNIEnv).unwrap() };
131+
/// let mut env = unsafe { jni::EnvUnowned::from_raw(raw_env as *mut jni::sys::JNIEnv).unwrap() };
149132
/// let context = unsafe { JObject::from_raw(raw_context as jni::sys::jobject) };
150-
/// let loader = env.call_method(&context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?;
133+
/// let loader = env.call_method(&context, c"getClassLoader", c"()Ljava/lang/ClassLoader;", &[])?;
151134
///
152-
/// rustls_platform_verifier::android::init_with_refs(
153-
/// env.get_java_vm()?,
154-
/// env.new_global_ref(context)?,
155-
/// env.new_global_ref(JObject::try_from(loader)?)?,
156-
/// );
135+
/// env.with_env(|env| {
136+
/// rustls_platform_verifier::android::init_with_refs(
137+
/// env.get_java_vm(),
138+
/// env.new_global_ref(context)?,
139+
/// env.new_global_ref(JClassLoader::try_from(loader)?)?,
140+
/// );
141+
/// });
157142
/// }
158143
/// ```
159-
pub fn init_with_refs(java_vm: JavaVM, context: GlobalRef, loader: GlobalRef) {
160-
GLOBAL.get_or_init(|| Global::Internal {
144+
pub fn init_with_refs(
145+
java_vm: JavaVM,
146+
context: Global<JObject<'static>>,
147+
loader: Global<JClassLoader<'static>>,
148+
) {
149+
GLOBAL.get_or_init(|| GlobalStorage::Internal {
161150
java_vm,
162151
context,
163152
loader,
@@ -173,45 +162,36 @@ impl From<JNIError> for Error {
173162
#[track_caller]
174163
fn from(cause: JNIError) -> Self {
175164
if let JNIError::JavaException = cause {
176-
if let Ok(env) = global().env() {
177-
// These should not fail if we are already in a throwing state unless
178-
// things are very broken. In that case, there isn't much we can do.
179-
let _ = env.exception_describe();
180-
let _ = env.exception_clear();
181-
}
165+
// SAFETY: We do not retain the `AttachGuard`, do not have access to the previous guard/env from
166+
// whichever JNI call errored before claling this function, and therefore don't unsafely mutate it.
167+
if let Ok(mut env) = unsafe { global().vm().get_env_attachment() } {
168+
let _ = env.with_env_current_frame(|env| {
169+
env.exception_describe();
170+
env.exception_clear();
171+
Ok::<_, Error>(())
172+
});
173+
};
182174
}
183175

184176
Self
185177
}
186178
}
187179

188180
pub(super) struct LocalContext<'a, 'env> {
189-
env: &'a mut JNIEnv<'env>,
190-
context: GlobalRef,
191-
loader: GlobalRef,
181+
pub(super) env: &'a mut Env<'env>,
182+
pub(super) global: GlobalContext,
192183
}
193184

194185
impl<'a, 'env> LocalContext<'a, 'env> {
195186
/// Load a class from the application class loader
196187
///
197188
/// This should be used instead of `JNIEnv::find_class` to ensure all classes
198189
/// in the application can be found.
199-
fn load_class(&mut self, name: &str) -> Result<JClass<'env>, Error> {
200-
let name = self.env.new_string(name)?;
201-
let class = self.env.call_method(
202-
&self.loader,
203-
"loadClass",
204-
"(Ljava/lang/String;)Ljava/lang/Class;",
205-
&[JValue::from(&name)],
206-
)?;
207-
208-
Ok(JObject::try_from(class)?.into())
209-
}
210-
211-
/// Borrow the `applicationContext` from the Android application
212-
/// <https://developer.android.com/reference/android/app/Application>
213-
pub(super) fn application_context(&self) -> &JObject<'_> {
214-
&self.context
190+
fn load_class(&mut self, name: &'static CStr) -> Result<JClass<'env>, Error> {
191+
self.global
192+
.loader
193+
.load_class(name.as_ref(), self.env)
194+
.map_err(Error::from)
215195
}
216196
}
217197

@@ -220,51 +200,42 @@ impl<'a, 'env> LocalContext<'a, 'env> {
220200
/// are cleared.
221201
pub(super) fn with_context<F, T: 'static>(f: F) -> Result<T, Error>
222202
where
223-
F: FnOnce(&mut LocalContext, &mut JNIEnv) -> Result<T, Error>,
203+
F: FnOnce(&mut LocalContext) -> Result<T, Error>,
224204
{
225-
let (global_context, mut binding_env) = global().context()?;
226-
// SAFETY: Any local references created with this env are always destroyed prior to the parent
227-
// frame exiting because we force it to be dropped before the new frame exits and don't allow
228-
// the closure to access the env directly. We don't use it anywhere outside that sub-scope either.
229-
//
230-
// Rust's borrowing rules enforce that any reference that came from this env must be dropped before it is too.
231-
let ctx_env = unsafe { binding_env.unsafe_clone() };
232-
233-
// 16 is the default capacity in the JVM, we can make this configurable if necessary
234-
binding_env.with_local_frame(16, |env| {
235-
let mut ctx_env = ctx_env;
205+
let global = global();
206+
global.vm().attach_current_thread_for_scope(|env| {
207+
let global_context = global.context(env)?;
236208
let mut context = LocalContext {
237-
env: &mut ctx_env,
238-
context: global_context.context,
239-
loader: global_context.loader,
209+
env,
210+
global: global_context,
240211
};
241-
f(&mut context, env)
212+
f(&mut context)
242213
})
243214
}
244215

245216
/// Loads and caches a class on first use
246217
pub(super) struct CachedClass {
247-
name: &'static str,
248-
class: OnceCell<GlobalRef>,
218+
name: &'static CStr,
219+
class: OnceCell<Global<JClass<'static>>>,
249220
}
250221

251222
impl CachedClass {
252223
/// Creates a lazily initialized class reference to the class with `name`.
253-
pub(super) const fn new(name: &'static str) -> Self {
224+
pub(super) const fn new(name: &'static CStr) -> Self {
254225
Self {
255226
name,
256227
class: OnceCell::new(),
257228
}
258229
}
259230

260231
/// Gets the cached class reference, loaded on first use
261-
pub(super) fn get(&self, cx: &mut LocalContext) -> Result<&JClass<'_>, Error> {
232+
pub(super) fn get(&self, cx: &mut LocalContext) -> Result<&JClass<'static>, Error> {
262233
let class = self.class.get_or_try_init(|| -> Result<_, Error> {
263234
let class = cx.load_class(self.name)?;
264235

265236
Ok(cx.env.new_global_ref(class)?)
266237
})?;
267238

268-
Ok(class.as_obj().into())
239+
Ok(class)
269240
}
270241
}

0 commit comments

Comments
 (0)