Conversation
38af1e3 to
c85fd1c
Compare
|
I've pushed up an initial attempt to adopt the new API of |
|
jni 0.22 is out now. |
Not sure what this means, nor am I really motivated to learn... |
The error message suggests how to fix the problem (add an explicit |
Dropped the |
|
Okay, so the test suite doesn't compile yet, and I don't think I have enough context on how this should work to fix that. Also, the Nix stuff fails, probably because the compiler is too old and the |
|
Upgraded the PR to 0.22.2. Still needs someone with more JVM and/or Nix context to move forward. |
|
I'm working on a project involving using |
Looking at the error messages in CI, it seems like there are some complaints about edition2024 not being supported by the current rust compiler version, which the flake is getting from the Cargo.toml file, which was set to 1.77 in this commit. 1.85 is the earliest version that supports edition 2024, so the version bump might need to go there to make this work. |
Indeed, here's a fix commit that fixes the Nix CI jobs on this branch. Do you want to cherry-pick it in here, or should I open a separate PR? (Meta note: I added that flake+CI integration and would be OK with it being removed if i'm the only thing keeping it from annoying other contributors. Fighting Android+native platform APIs doesn't spark joy and unless there's a sponsorship motivation I'd prefer to prioritize volunteering time in other Rustls repos) |
|
I just tried making #224 to see if that version bump might fix the CI problems, but it seems the CI pipeline won't run for me. |
|
This looks good so far and I'll be finishing up my review of the global/env changes specifically Saturday morning 👍 |
a92b18f to
f027fef
Compare
643a8ef to
cc1891c
Compare
|
Alright, looks like this can finally pass CI. There was a problem with the earlier version of |
jplatte
left a comment
There was a problem hiding this comment.
As somebody who just likes their Cargo.lock tidy and has no domain knowledge of JNI, this looks good. Left a few minor comments.
|
Thanks! Would especially appreciate a review from someone with substantial Android/jni expertise. |
Unfortunately the Android API exposes jni publicly.
I can't say I have substantial JNI expertise, but I've been using the API a fair amount in the last few weeks. One suggestion I'd have is to take advantage of the macros like jni_sig!, so this code: Could be more readable: I think that's correct. I didn't try to compile it. I've also found the |
| let verification_result = with_context(|cx| { | ||
| let byte_array_class = BYTE_ARRAY_CLASS.get(cx)?; | ||
| let string_class = STRING_CLASS.get(cx)?; | ||
| let cert_verifier_class = CERT_VERIFIER_CLASS.get(cx)?; |
There was a problem hiding this comment.
Note JString, JByteArray and JObjectArray are all built-in jni crate types that already handle caching their corresponding JClass.
A JObjectArray<E> accepts any Reference type as its element type, so you could do:
let array = JObjectArray::<JByteArray>::new(env, (intermediates.len() + 1).try_into().unwrap())?and then this code wouldn't have to directly deal with looking up + caching the JClass for calling new_object_array
Note: If you need to access the (cached) JClass for a built-in jni type (such as JString) then you could use Reference::lookup_class
For the CertificateVerifier class it could be worth declaring a bind_java_type! binding something like:
bind_java_type! {
CertVerifier => "org.rustls.platformverifier.CertificateVerifier",
fields {
code: jint,
message: JString,
},
methods {
#[cfg(any(test, feature = "ffi-testing"))]
static fn add_mock_root(mock_root: byte[]),
static fn verify_certificate_chain(ctx: android.content.Context, server_name: JString, auth_type: JString, allowed_ekus: JString[], ocsp_resp: byte[], now: jlong, cert_list: byte[][]) -> "org.rustls.platformverifier.VerificationResult",
}
}In addition to getting Rust method bindings like CertViewer::add_mock_root(env, mock_root)? that avoid the need for JValue argument wrappers, this would also handle the JClass caching as well as StaticMethodID and FieldID caching.
| JavaStr::from_env(env, &s.into()).ok().map(String::from) | ||
| env.cast_local::<JString>(s) | ||
| .and_then(|s| s.mutf8_chars(env).map(|s| s.to_str().into_owned())) | ||
| .ok() |
There was a problem hiding this comment.
Ideally you can use a bind_java_type binding for this field so you wouldn't need the explicit JString::cast_local here but it's also worth nothing here that if you want to get a String out of a JString then you can either rely on the standard Display trait implementation (i.e. .to_string()) (with the caveat that it will return "<NULL>" for a null string since it has to be infallible) or you could use try_to_string() without having to explicitly get the mutf8_chars.
Another benefit from using a bind_java_type binding here is that it wouldn't need to do a runtime type check every time it casts the Object reference to a String reference
| let env = guard.borrow_env_mut(); | ||
| env.exception_describe(); | ||
| env.exception_clear(); | ||
| }; |
There was a problem hiding this comment.
This looks OK (safe) but this doesn't need to resort to any unsafe APIs here.
In this context where you're assuming that the current thread must already be attached (it wouldn't make sense to be handling a JavaException error if the thread wasn't attached), then you could use JavaVM::with_top_local_frame to effectively get a temporary &mut Env for the top JNI stack frame. This can't be used to return a local reference to the caller, but that's OK for this use case.
If you needed to create some temporary local references in order to handle the Error mapping then you could alternatively consider using JavaVM::with_local_frame() (which is also exception safe) which would push/pop a temporary stack frame to avoid leaking those references.
| /// <https://developer.android.com/reference/android/app/Application> | ||
| pub(super) fn application_context(&self) -> &JObject<'_> { | ||
| &self.context | ||
| fn load_class(&mut self, name: &'static CStr) -> Result<JClass<'env>, Error> { |
There was a problem hiding this comment.
Instead of needing to assume that names are ascii, it could also be reasonable to accept the name as a &'static JNIStr which is essentially a CStr with the added guarantee that it's MUTF-8 encoded.
If the same were applied to CachedClass::new so it accepted a &'static JNIStr then instead of passing a c".." CStr literal, you could pass a compile-time check literal like:
CachedClass::new(jni_str!("org.rustls.platformverifier.CertificateVerifier"));(and that could then be passed through to here)
| name: &'static str, | ||
| class: OnceCell<GlobalRef>, | ||
| name: &'static CStr, | ||
| class: OnceCell<Global<JClass<'static>>>, |
There was a problem hiding this comment.
It looks like it could be possible to remove this caching utility while it's currently only used for three types, "java.lang.String", "[B" and "org.rustls.platformverifier.CertificateVerifier".
The first two types correspond to built-in jni types that already have a JClass cache that can be accessed via JString/JByteArray::lookup_class (via the Reference trait).
If a bind_java_type! binding were added for the last custom type, as sketched here: https://github.com/rustls/rustls-platform-verifier/pull/193/changes#r3012084014 that would also handle caching of the class reference too (as well as method/field IDs).
It also looks like adding a bind_java_type binding and adapting to use JObjectArray<JByteArray> may additionally avoid the need to even look up the JString / JByteArray classes explicitly since the bindings would handle those look ups internally.
| binding_env.with_local_frame(16, |env| { | ||
| let mut ctx_env = ctx_env; | ||
| let global = global(); | ||
| global.vm().attach_current_thread_for_scope(|env| { |
There was a problem hiding this comment.
Using _for_scope here is OK, but unless there's a clear reason why this needs to avoid attachment side effects, the recommended default choice for ensuring the current thread is attached to the JVM is usually JavaVM::attach_current_thread().
In general, attach_current_thread_for_scope essentially just increases the chance of hitting a pessimal case where you keep repeatedly attaching and detaching the thread - which is non-trivial JVM operation.
If the thread is already attached then both attachment APIs are cheap and will not touch the existing attachment, which means that ::attach_current_thread() will amortised the attachment cost in case the thread uses JNI multiple times before it exits.
Note that threads attached via JavaVM::attach_current_thread will be automatically detached from the JVM when they exit.

To give feedback upstream.