diff options
author | Matthew Maurer <mmaurer@google.com> | 2024-05-22 14:16:45 +0000 |
---|---|---|
committer | Matthew Maurer <mmaurer@google.com> | 2024-05-22 15:52:44 +0000 |
commit | 293fd53532cc59fd55f3947a6ec3a653349ea00f (patch) | |
tree | f184ec389bc5a9b415fa85b8430e8a87e3c6d9e7 | |
parent | d42c2ec45a5bb40774a7decae5b3e16339202742 (diff) | |
download | jni-main.tar.gz |
This project was upgraded with external_updater.
Usage: tools/external_updater/updater.sh update external/rust/crates/jni
For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
Test: TreeHugger
Change-Id: Ie9a5aa910facc39ae45337b846c0c88ded3ddab7
69 files changed, 6020 insertions, 1943 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..d7f36c6 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "09a5911358ffa846b5c85451c050a0e64669e863" + }, + "path_in_vcs": "" +}
\ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7c34d68..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rust-analyzer.cargo.features": [ - "invocation" - ] -}
\ No newline at end of file @@ -44,7 +44,7 @@ rust_test { host_supported: true, crate_name: "jni", cargo_env_compat: true, - cargo_pkg_version: "0.20.0", + cargo_pkg_version: "0.21.1", crate_root: "src/lib.rs", test_suites: ["general-tests"], auto_gen_config: true, @@ -55,6 +55,7 @@ rust_test { features: ["default"], rustlibs: [ "libcesu8", + "libcfg_if", "libcombine", "libjni_sys", "liblazy_static", @@ -68,12 +69,13 @@ rust_library { host_supported: true, crate_name: "jni", cargo_env_compat: true, - cargo_pkg_version: "0.20.0", + cargo_pkg_version: "0.21.1", crate_root: "src/lib.rs", edition: "2018", features: ["default"], rustlibs: [ "libcesu8", + "libcfg_if", "libcombine", "libjni_sys", "liblog_rust", diff --git a/CHANGELOG.md b/CHANGELOG.md index aeb692b..9db31bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,73 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [0.21.1] — 2023-03-08 + +### Fixes +- Compilation is fixed for architectures with a C ABI that has unsigned `char` types. ([#419](https://github.com/jni-rs/jni-rs/pull/419)) + +## [0.21.0] — 2023-02-13 + +This release makes extensive breaking changes in order to improve safety. Most projects that use this library will need to be changed. Please see [the migration guide](docs/0.21-MIGRATION.md). + +### Added +- `JavaStr::into_raw()` which drops the `JavaStr` and releases ownership of the raw string pointer ([#374](https://github.com/jni-rs/jni-rs/pull/374)) +- `JavaStr::from_raw()` which takes ownership of a raw string pointer to create a `JavaStr` ([#374](https://github.com/jni-rs/jni-rs/pull/374)) +- `JNIEnv::get_string_unchecked` is a cheaper, `unsafe` alternative to `get_string` that doesn't check the given object is a `java.lang.String` instance. ([#328](https://github.com/jni-rs/jni-rs/issues/328)) +- `WeakRef` and `JNIEnv#new_weak_ref`. ([#304](https://github.com/jni-rs/jni-rs/pull/304)) +- `define_class_bytearray` method that takes an `AutoElements<jbyte>` rather than a `&[u8]` ([#244](https://github.com/jni-rs/jni-rs/pull/244)) +- `JObject` now has an `as_raw` method that borrows the `JObject` instead of taking ownership like `into_raw`. Needed because `JObject` no longer has the `Copy` trait. ([#392](https://github.com/jni-rs/jni-rs/issues/392)) +- `JavaVM::destroy()` (unsafe) as a way to try and unload a `JavaVM` on supported platforms ([#391](https://github.com/jni-rs/jni-rs/issues/391)) +- `JavaVM::detach_current_thread()` (unsafe) as a way to explicitly detach a thread (normally this is automatic on thread exit). Needed to detach daemon threads manually if using `JavaVM::destroy()` ([#391](https://github.com/jni-rs/jni-rs/issues/391)) +- `JPrimitiveArray<T: TypeArray>` and type-specific aliases like `JByteArray`, `JIntArray` etc now provide safe, reference wrappers for the `sys` types `jarray` and `jbyteArray` etc with a lifetime like `JObject` ([#400](https://github.com/jni-rs/jni-rs/pull/400)) +- `JObjectArray` provides a reference wrapper for a `jobjectArray` with a lifetime like `JObject`. ([#400](https://github.com/jni-rs/jni-rs/pull/400)) +- `AutoElements` and `AutoElementsCritical` (previously `AutoArray`/`AutoPrimitiveArray`) implement `Deref<Target=[T]>` and `DerefMut` so array elements can be accessed via slices without needing additional `unsafe` code. ([#400](https://github.com/jni-rs/jni-rs/pull/400)) +- `AsJArrayRaw` trait which enables `JNIEnv::get_array_length()` to work with `JPrimitiveArray` or `JObjectArray` types ([#400](https://github.com/jni-rs/jni-rs/pull/400)) +- `InitArgsBuilder` now has `try_option` and `option_encoded` methods. ([#414](https://github.com/jni-rs/jni-rs/pull/414)) + +### Changed +- `JNIEnv::get_string` checks that the given object is a `java.lang.String` instance to avoid undefined behaviour from the JNI implementation potentially aborting the program. ([#328](https://github.com/jni-rs/jni-rs/issues/328)) +- `JNIEnv::call_*method_unchecked` was marked `unsafe`, as passing improper argument types, or a bad number of arguments, can cause a JVM crash. ([#385](https://github.com/jni-rs/jni-rs/issues/385)) +- The `JNIEnv::new_object_unchecked` function now takes arguments as `&[jni::sys::jvalue]` to avoid allocating, putting it inline with changes to `JniEnv::call_*_unchecked` from 0.20.0 ([#382](https://github.com/jni-rs/jni-rs/pull/382)) +- The `get_superclass` function now returns an Option instead of a null pointer if the class has no superclass ([#151](https://github.com/jni-rs/jni-rs/issues/151)) +- The `invocation` feature now locates the JVM implementation dynamically at runtime (via the `java-locator` crate by default) instead of linking with the JVM at build time ([#293](https://github.com/jni-rs/jni-rs/pull/293)) +- Most `JNIEnv` methods now require `&mut self`. This improves safety by preventing `JObject`s from getting an invalid lifetime. Most native method implementations (that is, `#[no_mangle] extern "system" fn`s) must now make the `JNIEnv` parameter `mut`. See the example on the crate documentation. ([#392](https://github.com/jni-rs/jni-rs/issues/392)) +- `JByteBuffer`, `JClass`, `JNIEnv`, `JObject`, `JString`, and `JThrowable` no longer have the `Clone` or `Copy` traits. This improves safety by preventing object references from being used after the JVM deletes them. Most functions that take one of these types as a parameter (except `extern fn`s that are directly called by the JVM) should now borrow it instead, e.g. `&JObject` instead of `JObject`. ([#392](https://github.com/jni-rs/jni-rs/issues/392)) +- `AutoLocal` is now generic in the type of object reference (`JString`, etc). ([#392](https://github.com/jni-rs/jni-rs/issues/392)) +- The closure passed to `JNIEnv::with_local_frame` must now take a `&mut JNIEnv` parameter, which has a different lifetime. This improves safety by preventing local references from escaping the closure, which would cause a use-after-free bug. `Executor::with_attached` and `Executor::with_attached_capacity` have been similarly changed. ([#392](https://github.com/jni-rs/jni-rs/issues/392)) +- The closure passed to `JNIEnv::with_local_frame` can now return a generic `Result<T, E>` so long as the error implements `From<jni::errors::Error>` ([#399](https://github.com/jni-rs/jni-rs/issues/399)) +- `JNIEnv::with_local_frame` now returns the same type that the given closure returns ([#399](https://github.com/jni-rs/jni-rs/issues/399)) +- `JNIEnv::with_local_frame` no longer supports returning a local reference directly to the calling scope (see `with_local_frame_returning_local`) ([#399](https://github.com/jni-rs/jni-rs/issues/399)) +- `Executor::with_attached` and `Executor::with_attached_capacity` have been changed in the same way as `JNIEnv::with_local_frame` (they are thin wrappers) ([#399](https://github.com/jni-rs/jni-rs/issues/399)) +- `Desc`, `JNIEnv::pop_local_frame`, and `TypeArray` are now `unsafe`. ([#392](https://github.com/jni-rs/jni-rs/issues/392)) +- The `Desc` trait now has an associated type `Output`. Many implementations now return `AutoLocal`, so if you call `Desc::lookup` yourself and then call `as_raw` on the returned object, make sure the `AutoLocal` isn't dropped too soon (see the `Desc::lookup` documentation for examples). ([#392](https://github.com/jni-rs/jni-rs/issues/392)) +- The `Desc<JClass>` trait is no longer implemented for `JObject` or `&JObject`. The previous implementation that called `.get_object_class()` was surprising and a simpler cast would make it easy to mistakenly pass instances where a class is required. ([#118](https://github.com/jni-rs/jni-rs/issues/118)) +- Named lifetimes in the documentation have more descriptive names (like `'local` instead of `'a`). The new naming convention is explained in the `JNIEnv` documentation. ([#392](https://github.com/jni-rs/jni-rs/issues/392)) +- Object reference types (`JObject`, `JClass`, `AutoLocal`, `GlobalRef`, etc) now implement `AsRef<JObject>` and `Deref<Target = JObject>`. Typed wrappers like `JClass` also implement `Into<JObject>`, but `GlobalRef` does not. ([#392](https://github.com/jni-rs/jni-rs/issues/392)) +- Most `JList` and `JMap` methods now require a `&mut JNIEnv` parameter. `JListIter` and `JMapIter` no longer implement `Iterator`, and instead have a `next` method that requires a `&mut JNIEnv` parameter (use `while let` loops instead of `for`). ([#392](https://github.com/jni-rs/jni-rs/issues/392)) +- `JValue` has been changed in several ways: ([#392](https://github.com/jni-rs/jni-rs/issues/392)) + - It is now a generic type named `JValueGen`. `JValue` is now a type alias for `JValueGen<&JObject>`, that is, it borrows an object reference. `JValueOwned` is a type alias for `JValueGen<JObject>`, that is, it owns an object reference. + - `JValueOwned` does not have the `Copy` trait. + - The `to_jni` method is now named `as_jni`, and it borrows the `JValueGen` instead of taking ownership. + - `JObject` can no longer be converted directly to `JValue`, which was commonly done when calling Java methods or constructors. Instead of `obj.into()`, use `(&obj).into()`. +- All `JNIEnv` array APIs now work in terms of `JPrimitiveArray` and `JObjectArray` (reference wrappers with a lifetime) instead of `sys` types like `jarray` and `jbyteArray` ([#400](https://github.com/jni-rs/jni-rs/pull/400)) +- `AutoArray` and `AutoPrimitiveArray` have been renamed `AutoElements` and `AutoElementsCritical` to show their connection and differentiate from new `JPrimitiveArray` API ([#400](https://github.com/jni-rs/jni-rs/pull/400)) +- `get_primitive_array_critical` is now `unsafe` and has been renamed to `get_array_elements_critical` (consistent with the rename of `AutoPrimitiveArray`) with more detailed safety documentation ([#400](https://github.com/jni-rs/jni-rs/pull/400)) +- `get_array_elements` is now also `unsafe` (for many of the same reasons as `get_array_elements_critical`) and has detailed safety documentation ([#400](https://github.com/jni-rs/jni-rs/pull/400)) +- `AutoArray/AutoArrayCritical::size()` has been replaced with `.len()` which can't fail and returns a `usize` ([#400](https://github.com/jni-rs/jni-rs/pull/400)) +- The `TypeArray` trait is now a private / sealed trait, that is considered to be an implementation detail for the `AutoArray` API. +- `JvmError` has several more variants and is now `non_exhaustive`. ([#414](https://github.com/jni-rs/jni-rs/pull/414)) +- `InitArgsBuilder::option` raises an error on Windows if the string is too long. The limit is currently 1048576 bytes. ([#414](https://github.com/jni-rs/jni-rs/pull/414)) + +### Fixed +- Trying to use an object reference after it has been deleted now causes a compile error instead of undefined behavior. As a result, it is now safe to use `AutoLocal`, `JNIEnv::delete_local_ref`, and `JNIEnv::with_local_frame`. (Most of the limitations added in #392, listed above, were needed to make this work.) ([#381](https://github.com/jni-rs/jni-rs/issues/381), [#392](https://github.com/jni-rs/jni-rs/issues/392)) +- Class lookups via the `Desc` trait now return `AutoLocal`s, which prevents them from leaking. ([#109](https://github.com/jni-rs/jni-rs/issues/109), [#392](https://github.com/jni-rs/jni-rs/issues/392)) +- `InitArgsBuilder::option` properly encodes non-ASCII characters on Windows. ([#414](https://github.com/jni-rs/jni-rs/pull/414)) + +### Removed +- `get_string_utf_chars` and `release_string_utf_chars` from `JNIEnv` (See `JavaStr::into_raw()` and `JavaStr::from_raw()` instead) ([#372](https://github.com/jni-rs/jni-rs/pull/372)) +- All `JNIEnv::get_<type>_array_elements()` methods have been removed as redundant since they would all be equivalent to `get_array_elements()` with the introduction of `JPrimitiveArray` ([#400](https://github.com/jni-rs/jni-rs/pull/400)) + ## [0.20.0] — 2022-10-17 ### Added @@ -245,7 +312,9 @@ to call if there is a pending exception (#124): ## [0.10.1] - No changes has been made to the Changelog until this release. -[Unreleased]: https://github.com/jni-rs/jni-rs/compare/v0.20.0...HEAD +[Unreleased]: https://github.com/jni-rs/jni-rs/compare/v0.21.1...HEAD +[0.21.1]: https://github.com/jni-rs/jni-rs/compare/v0.21.0...v0.21.1 +[0.21.0]: https://github.com/jni-rs/jni-rs/compare/v0.20.0...v0.21.0 [0.20.0]: https://github.com/jni-rs/jni-rs/compare/v0.19.0...v0.20.0 [0.19.0]: https://github.com/jni-rs/jni-rs/compare/v0.18.0...v0.19.0 [0.18.0]: https://github.com/jni-rs/jni-rs/compare/v0.17.0...v0.18.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eea3908..d928871 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,7 +111,7 @@ They might help you to see the performance differences between two [API flavours][checked-unchecked]: checked and unchecked, and pick the right one for your application. -[checked-unchecked]: https://docs.rs/jni/0.20.0/jni/struct.JNIEnv.html#checked-and-unchecked-methods +[checked-unchecked]: https://docs.rs/jni/0.21.1/jni/struct.JNIEnv.html#checked-and-unchecked-methods ## The Code Style @@ -12,7 +12,7 @@ [package] edition = "2018" name = "jni" -version = "0.20.0" +version = "0.21.1" authors = ["Josh Chase <josh@prevoty.com>"] description = "Rust bindings to the JNI" documentation = "https://docs.rs/jni" @@ -32,24 +32,51 @@ features = ["invocation"] [dependencies.cesu8] version = "1.1.0" +[dependencies.cfg-if] +version = "1.0.0" + [dependencies.combine] version = "4.1.0" +[dependencies.java-locator] +version = "0.1" +optional = true + [dependencies.jni-sys] version = "0.3.0" +[dependencies.libloading] +version = "0.7" +optional = true + [dependencies.log] version = "0.4.4" [dependencies.thiserror] version = "1.0.20" +[dev-dependencies.assert_matches] +version = "1.5.0" + [dev-dependencies.lazy_static] version = "1" +[dev-dependencies.rusty-fork] +version = "0.3.0" + [build-dependencies.walkdir] version = "2" [features] default = [] -invocation = [] +invocation = [ + "java-locator", + "libloading", +] + +[target."cfg(windows)".dependencies.windows-sys] +version = "0.45.0" +features = ["Win32_Globalization"] + +[target."cfg(windows)".dev-dependencies.bytemuck] +version = "1.13.0" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 913294d..5662d7a 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -13,13 +13,16 @@ license = "MIT/Apache-2.0" name = "jni" repository = "https://github.com/jni-rs/jni-rs" # ¡When bumping version please also update it in examples and documentation! -version = "0.20.0" +version = "0.21.1" edition = "2018" [dependencies] +cfg-if = "1.0.0" cesu8 = "1.1.0" combine = "4.1.0" +java-locator = { version = "0.1", optional = true } jni-sys = "0.3.0" +libloading = { version = "0.7", optional = true } log = "0.4.4" thiserror = "1.0.20" @@ -27,11 +30,18 @@ thiserror = "1.0.20" walkdir = "2" [dev-dependencies] +assert_matches = "1.5.0" lazy_static = "1" +rusty-fork = "0.3.0" +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.45.0", features = ["Win32_Globalization"] } + +[target.'cfg(windows)'.dev-dependencies] +bytemuck = "1.13.0" [features] -invocation = [] +invocation = ["java-locator", "libloading"] default = [] [package.metadata.docs.rs] @@ -1,23 +1,20 @@ # This project was upgraded with external_updater. -# Usage: tools/external_updater/updater.sh update rust/crates/jni -# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md +# Usage: tools/external_updater/updater.sh update external/rust/crates/jni +# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md name: "jni" description: "Rust bindings to the JNI" third_party { - url { - type: HOMEPAGE - value: "https://crates.io/crates/jni" - } - url { - type: ARCHIVE - value: "https://static.crates.io/crates/jni/jni-0.20.0.crate" - } - version: "0.20.0" license_type: NOTICE last_upgrade_date { - year: 2023 - month: 1 - day: 19 + year: 2024 + month: 5 + day: 22 + } + homepage: "https://crates.io/crates/jni" + identifier { + type: "Archive" + value: "https://static.crates.io/crates/jni/jni-0.21.1.crate" + version: "0.21.1" } } diff --git a/benches/api_calls.rs b/benches/api_calls.rs index 0ee0351..be5cdbb 100644 --- a/benches/api_calls.rs +++ b/benches/api_calls.rs @@ -26,12 +26,15 @@ static SIG_MATH_ABS: &str = "(I)I"; static SIG_OBJECT_HASH_CODE: &str = "()I"; static SIG_LOCAL_DATE_TIME_OF: &str = "(IIIIIII)Ljava/time/LocalDateTime;"; +// 32 characters +static TEST_STRING_UNICODE: &str = "_㳧~δ᪘ġ˥쩽|ņ/ٶԦ萴퀉֒ٞHy%ӓ娎ăꊦȮ"; + #[inline(never)] fn native_abs(x: i32) -> i32 { x.abs() } -fn jni_abs_safe(env: &JNIEnv, x: jint) -> jint { +fn jni_abs_safe(env: &mut JNIEnv, x: jint) -> jint { let x = JValue::from(x); let v = env .call_static_method(CLASS_MATH, METHOD_MATH_ABS, SIG_MATH_ABS, &[x]) @@ -39,15 +42,15 @@ fn jni_abs_safe(env: &JNIEnv, x: jint) -> jint { v.i().unwrap() } -fn jni_hash_safe(env: &JNIEnv, obj: JObject) -> jint { +fn jni_hash_safe(env: &mut JNIEnv, obj: &JObject) -> jint { let v = env .call_method(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE, &[]) .unwrap(); v.i().unwrap() } -fn jni_local_date_time_of_safe<'e>( - env: &JNIEnv<'e>, +fn jni_local_date_time_of_safe<'local>( + env: &mut JNIEnv<'local>, year: jint, month: jint, day_of_month: jint, @@ -55,7 +58,7 @@ fn jni_local_date_time_of_safe<'e>( minute: jint, second: jint, nanosecond: jint, -) -> JObject<'e> { +) -> JObject<'local> { let v = env .call_static_method( CLASS_LOCAL_DATE_TIME, @@ -75,43 +78,47 @@ fn jni_local_date_time_of_safe<'e>( v.l().unwrap() } -fn jni_int_call_static_unchecked<'c, C>( - env: &JNIEnv<'c>, +fn jni_int_call_static_unchecked<'local, C>( + env: &mut JNIEnv<'local>, class: C, method_id: JStaticMethodID, x: jint, ) -> jint where - C: Desc<'c, JClass<'c>>, + C: Desc<'local, JClass<'local>>, { let x = JValue::from(x); let ret = ReturnType::Primitive(Primitive::Int); - let v = env - .call_static_method_unchecked(class, method_id, ret, &[x.into()]) - .unwrap(); + let v = + unsafe { env.call_static_method_unchecked(class, method_id, ret, &[x.as_jni()]) }.unwrap(); v.i().unwrap() } -fn jni_int_call_unchecked<'m, M>(env: &JNIEnv<'m>, obj: JObject<'m>, method_id: M) -> jint +fn jni_int_call_unchecked<'local, M>( + env: &mut JNIEnv<'local>, + obj: &JObject<'local>, + method_id: M, +) -> jint where - M: Desc<'m, JMethodID>, + M: Desc<'local, JMethodID>, { let ret = ReturnType::Primitive(Primitive::Int); - let v = env.call_method_unchecked(obj, method_id, ret, &[]).unwrap(); + // SAFETY: Caller retrieved method ID + class specifically for this use: Object.hashCode()I + let v = unsafe { env.call_method_unchecked(obj, method_id, ret, &[]) }.unwrap(); v.i().unwrap() } -fn jni_object_call_static_unchecked<'c, C>( - env: &JNIEnv<'c>, +fn jni_object_call_static_unchecked<'local, C>( + env: &mut JNIEnv<'local>, class: C, method_id: JStaticMethodID, args: &[jvalue], -) -> JObject<'c> +) -> JObject<'local> where - C: Desc<'c, JClass<'c>>, + C: Desc<'local, JClass<'local>>, { - let v = env - .call_static_method_unchecked(class, method_id, ReturnType::Object, args) + // SAFETY: Caller retrieved method ID and constructed arguments + let v = unsafe { env.call_static_method_unchecked(class, method_id, ReturnType::Object, args) } .unwrap(); v.l().unwrap() } @@ -119,6 +126,7 @@ where #[cfg(test)] mod tests { use super::*; + use jni::objects::GlobalRef; use std::rc::Rc; use std::sync::Arc; use test::{black_box, Bencher}; @@ -142,63 +150,63 @@ mod tests { #[bench] fn jni_call_static_abs_method_safe(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); + let mut env = VM.attach_current_thread().unwrap(); - b.iter(|| jni_abs_safe(&env, -3)); + b.iter(|| jni_abs_safe(&mut env, -3)); } #[bench] fn jni_call_static_abs_method_unchecked_str(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); + let mut env = VM.attach_current_thread().unwrap(); let class = CLASS_MATH; let method_id = env .get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS) .unwrap(); - b.iter(|| jni_int_call_static_unchecked(&env, class, method_id, -3)); + b.iter(|| jni_int_call_static_unchecked(&mut env, class, method_id, -3)); } #[bench] fn jni_call_static_abs_method_unchecked_jclass(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); - let class: JClass = CLASS_MATH.lookup(&env).unwrap(); + let mut env = VM.attach_current_thread().unwrap(); + let class = Desc::<JClass>::lookup(CLASS_MATH, &mut env).unwrap(); let method_id = env - .get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS) + .get_static_method_id(&class, METHOD_MATH_ABS, SIG_MATH_ABS) .unwrap(); - b.iter(|| jni_int_call_static_unchecked(&env, class, method_id, -3)); + b.iter(|| jni_int_call_static_unchecked(&mut env, &class, method_id, -3)); } #[bench] fn jni_call_static_date_time_method_safe(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); + let mut env = VM.attach_current_thread().unwrap(); b.iter(|| { - let obj = jni_local_date_time_of_safe(&env, 1, 1, 1, 1, 1, 1, 1); + let obj = jni_local_date_time_of_safe(&mut env, 1, 1, 1, 1, 1, 1, 1); env.delete_local_ref(obj).unwrap(); }); } #[bench] fn jni_call_static_date_time_method_unchecked_jclass(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); - let class: JClass = CLASS_LOCAL_DATE_TIME.lookup(&env).unwrap(); + let mut env = VM.attach_current_thread().unwrap(); + let class = Desc::<JClass>::lookup(CLASS_LOCAL_DATE_TIME, &mut env).unwrap(); let method_id = env - .get_static_method_id(class, METHOD_LOCAL_DATE_TIME_OF, SIG_LOCAL_DATE_TIME_OF) + .get_static_method_id(&class, METHOD_LOCAL_DATE_TIME_OF, SIG_LOCAL_DATE_TIME_OF) .unwrap(); b.iter(|| { let obj = jni_object_call_static_unchecked( - &env, - class, + &mut env, + &class, method_id, &[ - JValue::Int(1).into(), - JValue::Int(1).into(), - JValue::Int(1).into(), - JValue::Int(1).into(), - JValue::Int(1).into(), - JValue::Int(1).into(), - JValue::Int(1).into(), + JValue::Int(1).as_jni(), + JValue::Int(1).as_jni(), + JValue::Int(1).as_jni(), + JValue::Int(1).as_jni(), + JValue::Int(1).as_jni(), + JValue::Int(1).as_jni(), + JValue::Int(1).as_jni(), ], ); env.delete_local_ref(obj).unwrap(); @@ -207,28 +215,29 @@ mod tests { #[bench] fn jni_call_object_hash_method_safe(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); + let mut env = VM.attach_current_thread().unwrap(); let s = env.new_string("").unwrap(); let obj = black_box(JObject::from(s)); - b.iter(|| jni_hash_safe(&env, obj)); + b.iter(|| jni_hash_safe(&mut env, &obj)); } #[bench] fn jni_call_object_hash_method_unchecked(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); + let mut env = VM.attach_current_thread().unwrap(); let s = env.new_string("").unwrap(); let obj = black_box(JObject::from(s)); + let obj_class = env.get_object_class(&obj).unwrap(); let method_id = env - .get_method_id(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE) + .get_method_id(&obj_class, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE) .unwrap(); - b.iter(|| jni_int_call_unchecked(&env, obj, method_id)); + b.iter(|| jni_int_call_unchecked(&mut env, &obj, method_id)); } #[bench] fn jni_new_object_str(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); + let mut env = VM.attach_current_thread().unwrap(); let class = CLASS_OBJECT; b.iter(|| { @@ -239,49 +248,49 @@ mod tests { #[bench] fn jni_new_object_by_id_str(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); + let mut env = VM.attach_current_thread().unwrap(); let class = CLASS_OBJECT; let ctor_id = env .get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR) .unwrap(); b.iter(|| { - let obj = env.new_object_unchecked(class, ctor_id, &[]).unwrap(); + let obj = unsafe { env.new_object_unchecked(class, ctor_id, &[]) }.unwrap(); env.delete_local_ref(obj).unwrap(); }); } #[bench] fn jni_new_object_jclass(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); - let class: JClass = CLASS_OBJECT.lookup(&env).unwrap(); + let mut env = VM.attach_current_thread().unwrap(); + let class = Desc::<JClass>::lookup(CLASS_OBJECT, &mut env).unwrap(); b.iter(|| { - let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap(); + let obj = env.new_object(&class, SIG_OBJECT_CTOR, &[]).unwrap(); env.delete_local_ref(obj).unwrap(); }); } #[bench] fn jni_new_object_by_id_jclass(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); - let class: JClass = CLASS_OBJECT.lookup(&env).unwrap(); + let mut env = VM.attach_current_thread().unwrap(); + let class = Desc::<JClass>::lookup(CLASS_OBJECT, &mut env).unwrap(); let ctor_id = env - .get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR) + .get_method_id(&class, METHOD_CTOR, SIG_OBJECT_CTOR) .unwrap(); b.iter(|| { - let obj = env.new_object_unchecked(class, ctor_id, &[]).unwrap(); + let obj = unsafe { env.new_object_unchecked(&class, ctor_id, &[]) }.unwrap(); env.delete_local_ref(obj).unwrap(); }); } #[bench] fn jni_new_global_ref(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); + let mut env = VM.attach_current_thread().unwrap(); let class = CLASS_OBJECT; let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap(); - let global_ref = env.new_global_ref(obj).unwrap(); + let global_ref = env.new_global_ref(&obj).unwrap(); env.delete_local_ref(obj).unwrap(); b.iter(|| env.new_global_ref(&global_ref).unwrap()); @@ -309,6 +318,28 @@ mod tests { }); } + #[bench] + fn jni_get_string(b: &mut Bencher) { + let mut env = VM.attach_current_thread().unwrap(); + let string = env.new_string(TEST_STRING_UNICODE).unwrap(); + + b.iter(|| { + let s: String = env.get_string(&string).unwrap().into(); + assert_eq!(s, TEST_STRING_UNICODE); + }); + } + + #[bench] + fn jni_get_string_unchecked(b: &mut Bencher) { + let env = VM.attach_current_thread().unwrap(); + let string = env.new_string(TEST_STRING_UNICODE).unwrap(); + + b.iter(|| { + let s: String = unsafe { env.get_string_unchecked(&string) }.unwrap().into(); + assert_eq!(s, TEST_STRING_UNICODE); + }); + } + /// A benchmark measuring Push/PopLocalFrame overhead. /// /// Such operations are *required* if one attaches a long-running @@ -319,10 +350,49 @@ mod tests { fn jni_noop_with_local_frame(b: &mut Bencher) { // Local frame size actually doesn't matter since JVM does not preallocate anything. const LOCAL_FRAME_SIZE: i32 = 32; - let env = VM.attach_current_thread().unwrap(); + let mut env = VM.attach_current_thread().unwrap(); + b.iter(|| { + env.with_local_frame(LOCAL_FRAME_SIZE, |_| -> Result<_, jni::errors::Error> { + Ok(()) + }) + .unwrap() + }); + } + + /// A benchmark measuring Push/PopLocalFrame overhead while retuning a local reference + #[bench] + fn jni_with_local_frame_returning_local(b: &mut Bencher) { + // Local frame size actually doesn't matter since JVM does not preallocate anything. + const LOCAL_FRAME_SIZE: i32 = 32; + let mut env = VM.attach_current_thread().unwrap(); + + let class = env.find_class(CLASS_OBJECT).unwrap(); b.iter(|| { - env.with_local_frame(LOCAL_FRAME_SIZE, || Ok(JObject::null())) - .unwrap() + env.with_local_frame_returning_local(LOCAL_FRAME_SIZE, |env| { + env.new_object(&class, SIG_OBJECT_CTOR, &[]) + }) + }); + } + + /// A benchmark measuring Push/PopLocalFrame overhead while retuning a global + /// object reference that then gets converted into a local reference before + /// dropping the global + #[bench] + fn jni_with_local_frame_returning_global_to_local(b: &mut Bencher) { + // Local frame size actually doesn't matter since JVM does not preallocate anything. + const LOCAL_FRAME_SIZE: i32 = 32; + let mut env = VM.attach_current_thread().unwrap(); + + let class = env.find_class(CLASS_OBJECT).unwrap(); + b.iter(|| { + let global = env + .with_local_frame::<_, GlobalRef, jni::errors::Error>(LOCAL_FRAME_SIZE, |env| { + let local = env.new_object(&class, SIG_OBJECT_CTOR, &[])?; + let global = env.new_global_ref(local)?; + Ok(global) + }) + .unwrap(); + let _local = env.new_local_ref(global).unwrap(); }); } @@ -341,10 +411,10 @@ mod tests { #[bench] fn native_arc(b: &mut Bencher) { - let env = VM.attach_current_thread().unwrap(); + let mut env = VM.attach_current_thread().unwrap(); let class = CLASS_OBJECT; let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap(); - let global_ref = env.new_global_ref(obj).unwrap(); + let global_ref = env.new_global_ref(&obj).unwrap(); env.delete_local_ref(obj).unwrap(); let arc = Arc::new(global_ref); diff --git a/build.rs b/build.rs deleted file mode 100644 index 05419f1..0000000 --- a/build.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! This build script is used to link with `jvm` dynamic library when -//! `invocation` feature is enabled. -//! -//! To do so, we look for `JAVA_HOME` environment variable. -//! * If it exists, we recursively search for `jvm` library file inside `JAVA_HOME` directory. -//! * If it is not set, we use the following commmand to find actual JVM home directory: -//! ```bash -//! java -XshowSettings:properties -version | grep 'java.home' -//! ``` -//! Then, we search for `jvm` as we have `JAVA_HOME`. -//! -//! On Windows, we also need to find `jvm.lib` file which is used while linking -//! at build time. This file is typically placed in `$JAVA_HOME/lib` directory. - -use std::{ - env, - path::{Path, PathBuf}, - process::Command, -}; - -#[cfg(target_os = "windows")] -const EXPECTED_JVM_FILENAME: &str = "jvm.dll"; -#[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd" -))] -const EXPECTED_JVM_FILENAME: &str = "libjvm.so"; -#[cfg(target_os = "macos")] -const EXPECTED_JVM_FILENAME: &str = "libjli.dylib"; - -fn main() { - if cfg!(feature = "invocation") { - let java_home = match env::var("JAVA_HOME") { - Ok(java_home) => PathBuf::from(java_home), - Err(_) => find_java_home().expect( - "Failed to find Java home directory. \ - Try setting JAVA_HOME", - ), - }; - - let libjvm_path = - find_libjvm(&java_home).expect("Failed to find libjvm.so. Check JAVA_HOME"); - - println!("cargo:rustc-link-search=native={}", libjvm_path.display()); - - // On Windows, we need additional file called `jvm.lib` - // and placed inside `JAVA_HOME\lib` directory. - if cfg!(windows) { - let lib_path = java_home.join("lib"); - println!("cargo:rustc-link-search={}", lib_path.display()); - } - - println!("cargo:rerun-if-env-changed=JAVA_HOME"); - - // On MacOS, we need to link to libjli instead of libjvm as a workaround - // to a Java8 bug. See here for more information: - // https://bugs.openjdk.java.net/browse/JDK-7131356 - if env::var("CARGO_CFG_TARGET_OS").unwrap() == "macos" { - println!("cargo:rustc-link-lib=dylib=jli"); - } else { - println!("cargo:rustc-link-lib=dylib=jvm"); - } - } -} - -/// To find Java home directory, we call -/// `java -XshowSettings:properties -version` command and parse its output to -/// find the line `java.home=<some path>`. -fn find_java_home() -> Option<PathBuf> { - Command::new("java") - .arg("-XshowSettings:properties") - .arg("-version") - .output() - .ok() - .and_then(|output| { - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - for line in stdout.lines().chain(stderr.lines()) { - if line.contains("java.home") { - let pos = line.find('=').unwrap() + 1; - let path = line[pos..].trim(); - return Some(PathBuf::from(path)); - } - } - None - }) -} - -fn find_libjvm<S: AsRef<Path>>(path: S) -> Option<PathBuf> { - let walker = walkdir::WalkDir::new(path).follow_links(true); - - for entry in walker { - let entry = match entry { - Ok(entry) => entry, - Err(_e) => continue, - }; - - let file_name = entry.file_name().to_str().unwrap_or(""); - - if file_name == EXPECTED_JVM_FILENAME { - return entry.path().parent().map(Into::into); - } - } - - None -} diff --git a/docs/0.21-MIGRATION.md b/docs/0.21-MIGRATION.md new file mode 100644 index 0000000..63df2e7 --- /dev/null +++ b/docs/0.21-MIGRATION.md @@ -0,0 +1,326 @@ +# Migrating to 0.21 + +Version 0.21 makes extensive breaking changes in order to improve safety. Most projects that use this library will need to be changed accordingly. + +This is a guide for migrating to 0.21. For a full list of changes in this release, please see the [changelog](../CHANGELOG.md). + +Most of these changes are needed to ensure that all local references (`JObject` and the like) have the correct lifetime and can't be used after they're deleted, which would cause undefined behavior. See [issue #392](https://github.com/jni-rs/jni-rs/issues/392) for a discussion of the problem these changes solve. + + +## `JNIEnv` parameter of `extern fn`s should now be `mut` + +In `extern fn`s that are directly called by the JVM, the `JNIEnv` parameter will usually need to be `mut`. + +```rust +#[no_mangle] +pub extern "system" fn Java_HelloWorld_hello<'local>(mut env: JNIEnv<'local>, + class: JClass<'local>, + input: JString<'local>) + -> jstring { + … +} +``` + +This is needed because most `JNIEnv` methods now take a `&mut self` parameter. + + +## `JNIEnv`, `JObject`, etc are now `!Copy` and should be borrowed + +Functions that are *not* directly called by the JVM should, in most cases, borrow local references (`JObject`, `JString`, and so on) and mutably borrow `JNIEnv`s. + +```rust +pub fn print_string(env: &mut JNIEnv, + string: &JString) + -> Result<()> { + println!("{}", env.get_string(string)?.to_string_lossy()); + Ok(()) +} +``` + +This is needed because these types no longer have the `Copy` or `Clone` traits. + + +## `JNIEnv::with_local_frame` closure now takes a `&mut JNIEnv` parameter + +When using `JNIEnv::with_local_frame`, `Executor::with_attached`, or `Executor::with_attached_capacity`, the closure must now take a parameter of type `&mut JNIEnv`. + +```rust +env.with_local_frame(16, |env| { + … +}) +``` + +The closure must only use the `JNIEnv` passed to it in that parameter, and not the `JNIEnv` that `with_local_frame` was called on. + + +## `JNIEnv::with_local_frame` closure can return a generic `Result` + +_Note: This also applies to `Executor::with_attached` and `Executor::with_attached_capacity` which are thin wrappers over `with_local_frame`_ + +The closure passed to `with_local_frame` is now free to return a generic `Result` as long as the error type implements `From<jni::errors::Error>`. + +This can be particularly beneficial when running large amounts of code within a local frame in a crate that defines its own `Result` and `Error` types which need to be propagated to the caller. + +There are a few trade offs with this change though: +1. Sometimes the compiler won't be able to infer the generic error type (E.g. for code that simply returns `Ok(())`) and so it has to be explicitly specified +2. Since it's no longer assumed that code always wants to return a single local reference this special case has to be handled differently + +Two options for clarifying the error type for the compiler if it's ambiguous would be: + +1. Specify the type of the return value as part of an assignment: +```rust +let result: MyResult<()> = env.with_local_frame(10, |env| { Ok(()) }); +``` + +2. Specify the generic `E` error parameter: +```rust +env.with_local_frame::<_, _, MyError>(10, |env| { Ok(()) })?; +``` + +Code that returns a local reference to the calling frame can either use `JNIEnv::with_local_frame_returning_local` (which is marginally optimized for that special case) or else return a `GlobalRef` instead. (This approach works reasonably well in Rust, compared to C, because a global reference will be automatically deleted once it is dropped so it doesn't introduce a memory leak hazard like it would in C) + + +## Passing object reference parameters to Java methods + +When passing an object reference as a parameter to a Java method or constructor, it needs to be explicitly borrowed, as in `(&obj).into()`, instead of simply `obj.into()`. + +```rust +env.call_static_method( + "com/example/SomeClass", + "someStaticMethod", + "(Ljava/lang/Object;)V", + &[ + (&obj).into(), + ], +) +``` + + +## `JList` and `JMap` methods all take a `&mut JNIEnv` parameter + +All methods of `JList` and `JMap` now require a parameter of type `&mut JNIEnv`. They no longer store the `JNIEnv` that was used to construct them. + +This is needed because most `JNIEnv` methods now take a `&mut self` parameter, and if the `JList` or `JMap` did store a `&mut JNIEnv`, then the caller would be unable to use the `JNIEnv` for anything else without first dropping the `JList` or `JMap`. + + +## `JList` and `JMap` iterators no longer implement `Iterator` + +Just like the methods of `JList` and `JMap`, their iterator now also requires a `&mut JNIEnv` in order to get the next element. + +For this reason, the iterator returned by `JList::iter` and `JMap::iter` no longer implements `std::iter::Iterator` and can no longer be used with a `for` loop. Instead, it has a `next` method that takes a `&mut JNIEnv` parameter, and can be used with a `while let` loop. + +```rust +let mut iterator = list.iter(env)?; + +while let Some(obj) = iterator.next(env)? { + let obj: AutoLocal<JObject> = env.auto_local(obj); + // Do something with `obj` here. +} +``` + + +## Local references no longer leak + +`JNIEnv` methods that look up Java classes no longer leak local references ([#109](https://github.com/jni-rs/jni-rs/issues/109)), so it is no longer necessary to wrap calls to these methods inside `JNIEnv::with_local_frame`. + + +## `cannot borrow as mutable` + +If your project has a function that takes two or more parameters, one of them is `JNIEnv`, and another is something returned by a `JNIEnv` method (like `JObject`), then calls to that function may not compile. + +```rust +fn example_function( + env: &mut JNIEnv, + obj: &JObject, +) { + // … +} + +example_function( + env, + // ERROR: cannot borrow `*env` as mutable more than once at a time + &env.new_object( + "com/example/SomeClass", + "()V", + &[], + )?, +) +``` + +To fix this, the `JNIEnv` parameter needs to come *last*. + +```rust +fn example_function( + obj: &JObject, + env: &mut JNIEnv, +) { + // … +} + +example_function( + &env.new_object( + "com/example/SomeClass", + "()V", + &[], + )?, + env, +); +``` + + +## `invocation` feature now finds and loads the JVM dynamically; `JavaVM::new` returns different error + +The `invocation` feature has been changed to locate and load the Java Virtual Machine at run time, using the [java-locator](https://crates.io/crates/java-locator) and [libloading](https://crates.io/crates/libloading) libraries. + +`JavaVM::new` now returns a different error type, `StartJvmError`, when it fails. This new error type covers failures to locate and load the JVM library as well as failures to initialize the JVM. + +If you need to load a specific JVM library instead of automatically discovering one, use `JavaVM::with_libjvm` instead. + +On non-Windows platforms, it is no longer necessary for the Java runtime's `lib` folder to be on the shared library search path (`LD_LIBRARY_PATH` or equivalent). Unfortunately, this is *not* true on Windows, where the Java runtime's DLLs must still be on the `PATH` (or added to the DLL search path with [`SetDllDirectory`](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw)). + + +## `Desc` has been redesigned + +The `Desc` trait has been redesigned. If your project contains any implementations of `Desc` and/or calls to `Desc::lookup`, they will need to be updated. + +If your project uses `Desc` directly like this, please post a comment on [issue #403](https://github.com/jni-rs/jni-rs/issues/403) explaining your situation. We view `Desc` as an implementation detail, and are considering sealing it and hiding its contents in a future release. + +Changes to `Desc` in this release are as follows: + +- `Desc` is now an `unsafe trait`. The safety requirements haven't actually changed, but they were undocumented until now. + +- `Desc` now has an associated type, `Output`, which is now the type returned by the `lookup` method. + +Please see the documentation for the `Desc` trait for more information. + + +## `JNIEnv::call_*method_unchecked` is now `unsafe` + +The `JNIEnv::call_*method_unchecked` methods are now `unsafe`. Their safety requirements haven't changed, but they were undocumented until now. + + +## `AutoLocal` type and lifetime parameters changed + +`AutoLocal` now has one lifetime parameter, representing the local reference frame it belongs to, and one type parameter, representing the type of object reference it contains (`JObject`, `JClass`, and so on). + +This means that the object reference stored in an `AutoLocal` no longer has its type erased. If it was a `JClass` before wrapping it in `AutoLocal`, it will still be a `JClass` after. + +```rust +// `AutoLocal` type parameters now include the type of object reference… +let auto_local: AutoLocal<'local, JClass<'local>>; + +// …so the type of the object reference is now kept instead of being erased. +let local: &JClass<'local> = &*auto_local; +``` + + +## `JObject` no longer implements `From<&AutoLocal> + From<&GlobalRef>` + +It is no longer possible to directly convert an `&AutoLocal` or `&GlobalRef` into a `JObject`. Instead, a `JObject` can be *borrowed* from `AutoLocal` or `GlobalRef` through their implementations of `Deref` and/or `AsRef<JObject>`. + +```rust +let global_ref: GlobalRef; + +// You can get a `JObject` from a `GlobalRef` two ways: +let object_ref: &JObject<'static> = &*global_ref; +let object_ref: &JObject<'static> = global_ref.as_ref(); +``` + + +## `JNIEnv::get_superclass` now returns `Option` + +The `JNIEnv::get_superclass` method previously returned a `JClass`, which would be null if the class in question doesn't have a superclass. It now returns `Option<JClass>` instead, and when it's `Some`, the `JClass` inside is never null. + + +## `JNIEnv::{get,release}_string_utf_chars` removed + +The methods `JNIEnv::get_string_utf_chars` and `JNIEnv::release_string_utf_chars` have been removed. These methods have been replaced with `JavaStr::into_raw` and `JavaStr::from_raw`. To get a `JavaStr`, use `JNIEnv::get_string` or `JNIEnv::get_string_unchecked`. See [issue #372](https://github.com/jni-rs/jni-rs/pull/372) for discussion. + + +## `JPrimitiveArray<T>` and `JObjectArray` provide safe reference wrappers (like `JObject`) for `jarray` + +This affects all `JNIEnv` array APIs including: +- `new_<type>_array` +- `get_array_length` +- `get_object_array_element` +- `set_object_array_element` +- `get_<type>_array_region` +- `set_<type>_array_region` +- `get_array_elements` +- `get_<type>_array_elements` (all removed) +- `get_primitive_array_elements` (also renamed to `get_array_elements_critical`) + +With the exception of `get_array_length` these APIs now take or return a +reference to a `JPrimitiveArray` or a `JObjectArray` instead of a `sys` type +like `jarray` or `jbyteArray`. This improves safety since the sys types can be +copied and easily read as invalid pointers after a reference has been deleted. + +`get_array_length` will accept a reference to a `JPrimitiveArray` or a `JObjectArray` +since these both implement the `AsJArrayRaw` trait. You can also query the `.len()` +of an array via the `AutoElements`/`AutoElementsCritical` APIs if you use those. + +There are the following type-specific aliases for `JPrimitiveArray<T>`: +- `JBooleanArray` +- `JByteArray` +- `JCharArray` +- `JShortArray` +- `JIntArray` +- `JLongArray` +- `JFloatArray` +- `JDoubleArray` + + +## `AutoArray` and `AutoPrimitiveArray` renamed to `AutoElements` and `AutoElementsCritical` respectively + +This rename was done to: +1. Clarify the connection between these APIs (they both provide temporary access to the elements of an array) +2. Clarify their differences (`AutoElementsCritical` is an alternative that defines a restricted "critical" section that helps JNI avoid the need to copy the data for the array) +3. Differentiate these from the new `JPrimitiveArray`/`JObjectArray` types and aliases like `JByteArray` that represent the array itself (not the elements) + + +## `AutoElements<T>` and `AutoElementsCritical<T>` now implement `Deref<Target=[T]>` and `DerefMut` + +Previously accessing array elements required the use of `unsafe` code to access array elements via `AutoArray::as_ptr()` after acquiring an `AutoArray` guard. + +It's now possible to read and write elements via the `Deref` and `DerefMut` traits that will deref into a `[T]` slice like: + +```rust +let byte_array = env.new_byte_array(100)?; + +{ + let mut elements = unsafe { env.get_array_elements(&byte_array, ReleaseMode::CopyBack) }?; + + elements[0] = 0xff; + assert_eq!(elements[0], 0xff); + + // elements released (copied back to Java) here on Drop +} +``` + +If you are accessing a `JPrimitiveArray<jbyte>`/`JByteArray` you may find it helpful +to utilize the [bytemuck](https://crates.io/crates/bytemuck) crate in case you +need to access the elements as `u8` unsigned bytes instead of `i8`. + +Although this hasn't changed, it seems worth highlighting that if you are +accessing a `JPrimitiveArray<jboolean>`/`JBooleanArray` you are responsible for +only storing values of `0` or `1`, since other values could lead to undefined +behaviour for the JVM. + + +## `AutoArray/AutoPrimitiveArray::size()` replace by `AutoElements/AutoElementsCritical::len()` + +Previously `AutoArray::size()` was a wrapper for calling `JNIEnv::get_array_length()` which could +fail, where as `AutoElements` and `AutoElementsCritical` now need to know the array length to +be constructed, and this constant is accessible via the `.len()` method. + +This change was required to support being able to `Deref` to a `[T]` slice. + + +## `get_array_elements[_critical]` are `unsafe` + +Previously `get_array_elements` and `get_primitive_array_critical` could be called by safe code +but there were multiple ways in which these APIs could quite easily lead to undefined behaviour. + +Since it is only possible to acquire an `AutoElements` and `AutoElementsCritical` instance via +`get_array_elements` and `get_primitive_array_critical`, that's where we now document +the safety rules that need to be followed to avoid any undefined behaviour. @@ -85,7 +85,7 @@ //! `mylib` that has everything needed to build an basic crate with `cargo`. We //! need to make a couple of changes to `Cargo.toml` before we do anything else. //! -//! * Under `[dependencies]`, add `jni = "0.20.0"` +//! * Under `[dependencies]`, add `jni = "0.21.1"` //! * Add a new `[lib]` section and under it, `crate_type = ["cdylib"]`. //! //! Now, if you run `cargo build` from inside the crate directory, you should @@ -95,7 +95,7 @@ //! The last thing we need to do is to define our exported method. Add this to //! your crate's `src/lib.rs`: //! -//! ```rust,ignore +//! ```rust,no_run //! // This is the interface to the JVM that we'll call the majority of our //! // methods on. //! use jni::JNIEnv; @@ -113,17 +113,17 @@ //! // This keeps Rust from "mangling" the name and making it unique for this //! // crate. //! #[no_mangle] -//! pub extern "system" fn Java_HelloWorld_hello(env: JNIEnv, +//! pub extern "system" fn Java_HelloWorld_hello<'local>(mut env: JNIEnv<'local>, //! // This is the class that owns our static method. It's not going to be used, //! // but still must be present to match the expected signature of a static //! // native method. -//! class: JClass, -//! input: JString) -//! -> jstring { +//! class: JClass<'local>, +//! input: JString<'local>) +//! -> jstring { //! // First, we have to get the string out of Java. Check out the `strings` //! // module for more info on how this works. //! let input: String = -//! env.get_string(input).expect("Couldn't get java string!").into(); +//! env.get_string(&input).expect("Couldn't get java string!").into(); //! //! // Then we have to create a new Java string to return. Again, more info //! // in the `strings` module. @@ -131,7 +131,7 @@ //! .expect("Couldn't create java string!"); //! //! // Finally, extract the raw pointer to return. -//! output.into_inner() +//! output.into_raw() //! } //! ``` //! diff --git a/src/wrapper/descriptors/class_desc.rs b/src/wrapper/descriptors/class_desc.rs index 9b2e46b..3585ef1 100644 --- a/src/wrapper/descriptors/class_desc.rs +++ b/src/wrapper/descriptors/class_desc.rs @@ -6,34 +6,38 @@ use crate::{ JNIEnv, }; -impl<'a, T> Desc<'a, JClass<'a>> for T +unsafe impl<'local, T> Desc<'local, JClass<'local>> for T where T: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JClass<'a>> { - env.find_class(self) - } -} + type Output = AutoLocal<'local, JClass<'local>>; -impl<'a, 'b> Desc<'a, JClass<'a>> for JObject<'b> { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JClass<'a>> { - env.get_object_class(self) + fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> { + Ok(AutoLocal::new(env.find_class(self)?, env)) } } +// Note: We don't implement `Desc<JClass>` for `&JObject` as a transmute like for `GlobalRef` +// +// Considering that the APIs that return a class return a `JClass` it shouldn't +// usually be necessary unless the `JClass` got type erased (like with GlobalRef) +// +// Implementing `Desc<JClass>` for `&JObject` as a simple cast would also make +// it a lot easier to mistakenly pass an object instance in places where a class +// is required. + /// This conversion assumes that the `GlobalRef` is a pointer to a class object. -impl<'a, 'b> Desc<'a, JClass<'b>> for &'b GlobalRef { - fn lookup(self, _: &JNIEnv<'a>) -> Result<JClass<'b>> { - Ok(self.as_obj().into()) - } -} -/// This conversion assumes that the `AutoLocal` is a pointer to a class object. -impl<'a, 'b, 'c> Desc<'a, JClass<'b>> for &'b AutoLocal<'c, '_> -where - 'c: 'b, -{ - fn lookup(self, _: &JNIEnv<'a>) -> Result<JClass<'b>> { - Ok(self.as_obj().into()) +// TODO: Generify `GlobalRef` and get rid of this `impl`. The transmute is +// sound-ish at the moment (`JClass` is currently `repr(transparent)` +// around `JObject`), but that may change in the future. Moreover, this +// doesn't check if the global reference actually refers to a +// `java.lang.Class` object. +unsafe impl<'local, 'obj_ref> Desc<'local, JClass<'static>> for &'obj_ref GlobalRef { + type Output = &'obj_ref JClass<'static>; + + fn lookup(self, _: &mut JNIEnv<'local>) -> Result<Self::Output> { + let obj: &JObject<'static> = self.as_ref(); + Ok(unsafe { std::mem::transmute(obj) }) } } diff --git a/src/wrapper/descriptors/desc.rs b/src/wrapper/descriptors/desc.rs index 2d7fd9d..938d1a9 100644 --- a/src/wrapper/descriptors/desc.rs +++ b/src/wrapper/descriptors/desc.rs @@ -1,17 +1,134 @@ -use crate::{errors::*, JNIEnv}; +use crate::{ + errors::*, + objects::{AutoLocal, JObject}, + JNIEnv, +}; + +#[cfg(doc)] +use crate::objects::{JClass, JMethodID}; /// Trait for things that can be looked up through the JNI via a descriptor. /// This will be something like the fully-qualified class name /// `java/lang/String` or a tuple containing a class descriptor, method name, /// and method signature. For convenience, this is also implemented for the /// concrete types themselves in addition to their descriptors. -pub trait Desc<'a, T> { +/// +/// # Safety +/// +/// Implementations of this trait must return the correct value from the +/// `lookup` method. It must not, for example, return a random [`JMethodID`] or +/// the [`JClass`] of a class other than the one requested. Returning such an +/// incorrect value results in undefined behavior. This requirement also +/// applies to the returned value's implementation of `AsRef<T>`. +pub unsafe trait Desc<'local, T> { + /// The type that this `Desc` returns. + type Output: AsRef<T>; + /// Look up the concrete type from the JVM. - fn lookup(self, _: &JNIEnv<'a>) -> Result<T>; + /// + /// Note that this method does not return exactly `T`. Instead, it returns + /// some type that implements `AsRef<T>`. For this reason, it is often + /// necessary to use turbofish syntax when calling this method: + /// + /// ```rust,no_run + /// # use jni::{descriptors::Desc, errors::Result, JNIEnv, objects::JClass}; + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { + /// // The value returned by `lookup` is not exactly `JClass`. + /// let class/*: impl AsRef<JClass> */ = + /// Desc::<JClass>::lookup("java/lang/Object", env)?; + /// + /// // But `&JClass` can be borrowed from it. + /// let class: &JClass = class.as_ref(); + /// # Ok(()) + /// # } + /// ``` + /// + /// **Warning:** Many built-in implementations of this trait return + /// [`AutoLocal`] from this method. If you then call [`JObject::as_raw`] on + /// the returned object reference, this may result in the reference being + /// [deleted][JNIEnv::delete_local_ref] before it is used, causing + /// undefined behavior. + /// + /// For example, don't do this: + /// + /// ```rust,no_run + /// # use jni::{descriptors::Desc, errors::Result, JNIEnv, objects::JClass}; + /// # + /// # fn some_function<T>(ptr: *mut T) {} + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { + /// // Undefined behavior: the `JClass` is dropped before the raw pointer + /// // is passed to `some_function`! + /// some_function(Desc::<JClass>::lookup("java/lang/Object", env)?.as_raw()); + /// # Ok(()) + /// # } + /// ``` + /// + /// Instead, do this: + /// + /// ```rust,no_run + /// # use jni::{descriptors::Desc, errors::Result, JNIEnv, objects::JClass}; + /// # + /// # fn some_function<T>(ptr: *mut T) {} + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { + /// let class = Desc::<JClass>::lookup("java/lang/Object", env)?; + /// + /// some_function(class.as_raw()); + /// + /// drop(class); + /// # Ok(()) + /// # } + /// ``` + /// + /// This will still work without the call to `drop` at the end, but calling + /// `drop` ensures that the reference is not accidentally dropped earlier + /// than it should be. + fn lookup(self, _: &mut JNIEnv<'local>) -> Result<Self::Output>; +} + +unsafe impl<'local, T> Desc<'local, T> for T +where + T: AsRef<T>, +{ + type Output = Self; + + fn lookup(self, _: &mut JNIEnv<'local>) -> Result<T> { + Ok(self) + } +} + +unsafe impl<'local, 't_ref, T> Desc<'local, T> for &'t_ref T +where + T: AsRef<T>, +{ + type Output = Self; + + fn lookup(self, _: &mut JNIEnv<'local>) -> Result<Self::Output> { + Ok(self) + } } -impl<'a, T> Desc<'a, T> for T { - fn lookup(self, _: &JNIEnv<'a>) -> Result<T> { +unsafe impl<'local, 'other_local, T> Desc<'local, T> for AutoLocal<'other_local, T> +where + T: AsRef<T> + Into<JObject<'other_local>>, +{ + type Output = Self; + + fn lookup(self, _: &mut JNIEnv<'local>) -> Result<Self::Output> { + Ok(self) + } +} + +unsafe impl<'local, 'other_local, 'obj_ref, T> Desc<'local, T> + for &'obj_ref AutoLocal<'other_local, T> +where + T: AsRef<T> + Into<JObject<'other_local>>, +{ + type Output = Self; + + fn lookup(self, _: &mut JNIEnv<'local>) -> Result<Self::Output> { Ok(self) } } diff --git a/src/wrapper/descriptors/exception_desc.rs b/src/wrapper/descriptors/exception_desc.rs index 4911650..3e3c015 100644 --- a/src/wrapper/descriptors/exception_desc.rs +++ b/src/wrapper/descriptors/exception_desc.rs @@ -1,47 +1,57 @@ use crate::{ descriptors::Desc, errors::*, - objects::{JClass, JObject, JThrowable, JValue}, + objects::{AutoLocal, JClass, JObject, JThrowable, JValue}, strings::JNIString, JNIEnv, }; const DEFAULT_EXCEPTION_CLASS: &str = "java/lang/RuntimeException"; -impl<'a, 'c, C, M> Desc<'a, JThrowable<'a>> for (C, M) +unsafe impl<'local, 'other_local, C, M> Desc<'local, JThrowable<'local>> for (C, M) where - C: Desc<'a, JClass<'c>>, + C: Desc<'local, JClass<'other_local>>, M: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JThrowable<'a>> { - let jmsg: JObject = env.new_string(self.1)?.into(); + type Output = AutoLocal<'local, JThrowable<'local>>; + + fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> { + let jmsg: AutoLocal<JObject> = env.auto_local(env.new_string(self.1)?.into()); let obj: JThrowable = env - .new_object(self.0, "(Ljava/lang/String;)V", &[JValue::from(jmsg)])? + .new_object(self.0, "(Ljava/lang/String;)V", &[JValue::from(&jmsg)])? .into(); - Ok(obj) + Ok(env.auto_local(obj)) } } -impl<'a> Desc<'a, JThrowable<'a>> for Exception { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JThrowable<'a>> { - (self.class, self.msg).lookup(env) +unsafe impl<'local> Desc<'local, JThrowable<'local>> for Exception { + type Output = AutoLocal<'local, JThrowable<'local>>; + + fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> { + Desc::<JThrowable>::lookup((self.class, self.msg), env) } } -impl<'a, 'b> Desc<'a, JThrowable<'a>> for &'b str { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JThrowable<'a>> { - (DEFAULT_EXCEPTION_CLASS, self).lookup(env) +unsafe impl<'local, 'str_ref> Desc<'local, JThrowable<'local>> for &'str_ref str { + type Output = AutoLocal<'local, JThrowable<'local>>; + + fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> { + Desc::<JThrowable>::lookup((DEFAULT_EXCEPTION_CLASS, self), env) } } -impl<'a> Desc<'a, JThrowable<'a>> for String { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JThrowable<'a>> { - (DEFAULT_EXCEPTION_CLASS, self).lookup(env) +unsafe impl<'local> Desc<'local, JThrowable<'local>> for String { + type Output = AutoLocal<'local, JThrowable<'local>>; + + fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> { + Desc::<JThrowable>::lookup((DEFAULT_EXCEPTION_CLASS, self), env) } } -impl<'a> Desc<'a, JThrowable<'a>> for JNIString { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JThrowable<'a>> { - (DEFAULT_EXCEPTION_CLASS, self).lookup(env) +unsafe impl<'local> Desc<'local, JThrowable<'local>> for JNIString { + type Output = AutoLocal<'local, JThrowable<'local>>; + + fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> { + Desc::<JThrowable>::lookup((DEFAULT_EXCEPTION_CLASS, self), env) } } diff --git a/src/wrapper/descriptors/field_desc.rs b/src/wrapper/descriptors/field_desc.rs index 405c03b..7b6fb9a 100644 --- a/src/wrapper/descriptors/field_desc.rs +++ b/src/wrapper/descriptors/field_desc.rs @@ -6,24 +6,28 @@ use crate::{ JNIEnv, }; -impl<'a, 'c, T, U, V> Desc<'a, JFieldID> for (T, U, V) +unsafe impl<'local, 'other_local, T, U, V> Desc<'local, JFieldID> for (T, U, V) where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, U: Into<JNIString>, V: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JFieldID> { + type Output = JFieldID; + + fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> { env.get_field_id(self.0, self.1, self.2) } } -impl<'a, 'c, T, U, V> Desc<'a, JStaticFieldID> for (T, U, V) +unsafe impl<'local, 'other_local, T, U, V> Desc<'local, JStaticFieldID> for (T, U, V) where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, U: Into<JNIString>, V: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JStaticFieldID> { + type Output = JStaticFieldID; + + fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> { env.get_static_field_id(self.0, self.1, self.2) } } diff --git a/src/wrapper/descriptors/method_desc.rs b/src/wrapper/descriptors/method_desc.rs index 1b94e04..d849425 100644 --- a/src/wrapper/descriptors/method_desc.rs +++ b/src/wrapper/descriptors/method_desc.rs @@ -6,34 +6,40 @@ use crate::{ JNIEnv, }; -impl<'a, 'c, T, U, V> Desc<'a, JMethodID> for (T, U, V) +unsafe impl<'local, 'other_local, T, U, V> Desc<'local, JMethodID> for (T, U, V) where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, U: Into<JNIString>, V: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JMethodID> { + type Output = JMethodID; + + fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> { env.get_method_id(self.0, self.1, self.2) } } -impl<'a, 'c, T, Signature> Desc<'a, JMethodID> for (T, Signature) +unsafe impl<'local, 'other_local, T, Signature> Desc<'local, JMethodID> for (T, Signature) where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, Signature: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JMethodID> { - (self.0, "<init>", self.1).lookup(env) + type Output = JMethodID; + + fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> { + Desc::<JMethodID>::lookup((self.0, "<init>", self.1), env) } } -impl<'a, 'c, T, U, V> Desc<'a, JStaticMethodID> for (T, U, V) +unsafe impl<'local, 'other_local, T, U, V> Desc<'local, JStaticMethodID> for (T, U, V) where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, U: Into<JNIString>, V: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JStaticMethodID> { + type Output = JStaticMethodID; + + fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> { env.get_static_method_id(self.0, self.1, self.2) } } diff --git a/src/wrapper/errors.rs b/src/wrapper/errors.rs index 19b9d8c..7c6df59 100644 --- a/src/wrapper/errors.rs +++ b/src/wrapper/errors.rs @@ -13,7 +13,7 @@ pub enum Error { WrongJValueType(&'static str, &'static str), #[error("Invalid constructor return type (must be void)")] InvalidCtorReturn, - #[error("Invalid number of arguments passed to java method: {0}")] + #[error("Invalid number or type of arguments passed to java method: {0}")] InvalidArgList(TypeSignature), #[error("Method not found: {name} {sig}")] MethodNotFound { name: String, sig: String }, @@ -87,3 +87,43 @@ pub struct Exception { pub trait ToException { fn to_exception(&self) -> Exception; } + +/// An error that occurred while starting the JVM using the JNI Invocation API. +/// +/// This only exists if the "invocation" feature is enabled. +#[cfg(feature = "invocation")] +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum StartJvmError { + /// An attempt was made to find a JVM using [java-locator], but it failed. + /// + /// If this happens, give an explicit location to [`JavaVM::with_libjvm`] or set the + /// `JAVA_HOME` environment variable. + /// + /// [java-locator]: https://docs.rs/java-locator/ + /// [`JavaVM::with_libjvm`]: crate::JavaVM::with_libjvm + #[error("Couldn't automatically discover the Java VM's location (try setting the JAVA_HOME environment variable): {0}")] + NotFound( + #[from] + #[source] + java_locator::errors::JavaLocatorError, + ), + + /// An error occurred in trying to load the JVM shared library. + /// + /// On Windows, if this happens it may be necessary to add your `$JAVA_HOME/bin` directory + /// to the DLL search path by adding it to the `PATH` environment variable. + #[error("Couldn't load the Java VM shared library ({0}): {1}")] + LoadError(String, #[source] libloading::Error), + + /// The JNI function `JNI_CreateJavaVM` returned an error. + #[error("{0}")] + Create( + #[from] + #[source] + Error, + ), +} + +#[cfg(feature = "invocation")] +pub type StartJvmResult<T> = std::result::Result<T, StartJvmError>; diff --git a/src/wrapper/executor.rs b/src/wrapper/executor.rs index 9ef12db..fefe772 100644 --- a/src/wrapper/executor.rs +++ b/src/wrapper/executor.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::{errors::*, objects::JObject, JNIEnv, JavaVM}; +use crate::{errors::*, JNIEnv, JavaVM}; /// The capacity of local frames, allocated for attached threads by default. Same as the default /// value Hotspot uses when calling native Java methods. @@ -22,9 +22,9 @@ pub const DEFAULT_LOCAL_FRAME_CAPACITY: i32 = 32; /// ```rust /// # use jni::errors; /// # // -/// # fn main() -> errors::Result<()> { /// # // Ignore this test without invocation feature, so that simple `cargo test` works -/// # #[cfg(feature = "invocation")] { +/// # #[cfg(feature = "invocation")] +/// # fn main() -> errors::StartJvmResult<()> { /// # // /// # use jni::{objects::JValue, Executor, InitArgsBuilder, JavaVM, sys::jint}; /// # use std::sync::Arc; @@ -39,15 +39,16 @@ pub const DEFAULT_LOCAL_FRAME_CAPACITY: i32 = 32; /// /// let val: jint = exec.with_attached(|env| { /// let x = JValue::from(-10); -/// let val: jint = env.call_static_method("java/lang/Math", "abs", "(I)I", &[x])? -/// .i()?; -/// Ok(val) +/// env.call_static_method("java/lang/Math", "abs", "(I)I", &[x])?.i() /// })?; /// /// assert_eq!(val, 10); /// -/// # } /// # Ok(()) } +/// # +/// # // This is a stub that gets run instead if the invocation feature is not built +/// # #[cfg(not(feature = "invocation"))] +/// # fn main() {} /// ``` #[derive(Clone)] pub struct Executor { @@ -65,20 +66,15 @@ impl Executor { /// call. /// /// Allocates a local frame with the specified capacity. - pub fn with_attached_capacity<F, R>(&self, capacity: i32, f: F) -> Result<R> + pub fn with_attached_capacity<F, T, E>(&self, capacity: i32, f: F) -> std::result::Result<T, E> where - F: FnOnce(&JNIEnv) -> Result<R>, + F: FnOnce(&mut JNIEnv) -> std::result::Result<T, E>, + E: From<Error>, { assert!(capacity > 0, "capacity should be a positive integer"); - let jni_env = self.vm.attach_current_thread_as_daemon()?; - let mut result = None; - jni_env.with_local_frame(capacity, || { - result = Some(f(&jni_env)); - Ok(JObject::null()) - })?; - - result.expect("The result should be Some or this line shouldn't be reached") + let mut jni_env = self.vm.attach_current_thread_as_daemon()?; + jni_env.with_local_frame(capacity, |jni_env| f(jni_env)) } /// Executes a provided closure, making sure that the current thread @@ -87,9 +83,10 @@ impl Executor { /// /// Allocates a local frame with /// [the default capacity](constant.DEFAULT_LOCAL_FRAME_CAPACITY.html). - pub fn with_attached<F, R>(&self, f: F) -> Result<R> + pub fn with_attached<F, T, E>(&self, f: F) -> std::result::Result<T, E> where - F: FnOnce(&JNIEnv) -> Result<R>, + F: FnOnce(&mut JNIEnv) -> std::result::Result<T, E>, + E: From<Error>, { self.with_attached_capacity(DEFAULT_LOCAL_FRAME_CAPACITY, f) } diff --git a/src/wrapper/java_vm/init_args.rs b/src/wrapper/java_vm/init_args.rs index c1fd6e1..5c6f886 100644 --- a/src/wrapper/java_vm/init_args.rs +++ b/src/wrapper/java_vm/init_args.rs @@ -1,4 +1,4 @@ -use std::{ffi::CString, os::raw::c_void}; +use std::{borrow::Cow, ffi::CStr, io, os::raw::c_void, ptr}; use thiserror::Error; @@ -7,56 +7,274 @@ use crate::{ JNIVersion, }; +use cfg_if::cfg_if; + +mod char_encoding_generic; + +#[cfg(windows)] +mod char_encoding_windows; + /// Errors that can occur when invoking a [`JavaVM`](super::vm::JavaVM) with the /// [Invocation API](https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html). #[derive(Debug, Error)] +#[non_exhaustive] pub enum JvmError { - /// An internal `0` byte was found when constructing a string. + /// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the supplied + /// string contains a U+0000 code point (except at the end). + /// + /// This error is not raised if the string has a single U+0000 code point at the end. + /// + /// [`InitArgsBuilder::option_encoded`] never raises this error. #[error("internal null in option: {0}")] NullOptString(String), + + /// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the option + /// string is too long. + /// + /// Currently, this error only occurs on Windows, where string length is limited to 1MB to + /// avoid overflow in [`WideCharToMultiByte`] (see [discussion]). String length is not + /// currently limited (other than by available memory) on other platforms. + /// + /// [`InitArgsBuilder::option_encoded`] never raises this error, regardless of platform. + /// + /// [discussion]: https://github.com/jni-rs/jni-rs/pull/414 + /// [`WideCharToMultiByte`]: https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte + #[error("option is too long: {opt_string}")] + #[non_exhaustive] + OptStringTooLong { + /// The option string. + opt_string: String, + }, + + /// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the option + /// string is not representable in the platform default character encoding. + /// + /// [`InitArgsBuilder::option_encoded`] never raises this error. + #[error( + "option {opt_string:?} is not representable in the platform default character encoding" + )] + #[non_exhaustive] + OptStringNotRepresentable { + /// The option string. + opt_string: String, + }, + + /// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the platform + /// reported an error converting it to its default character encoding. + /// + /// [`InitArgsBuilder::option_encoded`] never raises this error. + #[error("couldn't convert option {opt_string:?} to the platform default character encoding: {error}")] + #[non_exhaustive] + OptStringTranscodeFailure { + /// The option string. + opt_string: String, + + /// The error reported by the platform's character encoding conversion routine. + #[source] + error: io::Error, + }, } +impl JvmError { + /// Returns the JVM option that caused the error, if it was caused by one. + pub fn opt_string(&self) -> Option<&str> { + match self { + Self::NullOptString(opt_string) => Some(opt_string), + Self::OptStringTooLong { opt_string, .. } => Some(opt_string), + Self::OptStringNotRepresentable { opt_string, .. } => Some(opt_string), + Self::OptStringTranscodeFailure { opt_string, .. } => Some(opt_string), + } + .map(String::as_str) + } + + #[cfg(all(test, windows))] + fn opt_string_mut(&mut self) -> Option<&mut String> { + match self { + Self::NullOptString(opt_string) => Some(opt_string), + Self::OptStringTooLong { opt_string, .. } => Some(opt_string), + Self::OptStringNotRepresentable { opt_string, .. } => Some(opt_string), + Self::OptStringTranscodeFailure { opt_string, .. } => Some(opt_string), + } + } +} + +const SPECIAL_OPTIONS: &[&str] = &["vfprintf", "abort", "exit"]; + +const SPECIAL_OPTIONS_C: &[&CStr] = unsafe { + &[ + CStr::from_bytes_with_nul_unchecked(b"vfprintf\0"), + CStr::from_bytes_with_nul_unchecked(b"abort\0"), + CStr::from_bytes_with_nul_unchecked(b"exit\0"), + ] +}; + /// Builder for JavaVM InitArgs. /// /// *This API requires "invocation" feature to be enabled, /// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).* #[derive(Debug)] -pub struct InitArgsBuilder { - opts: Vec<String>, +pub struct InitArgsBuilder<'a> { + opts: Result<Vec<Cow<'a, CStr>>, JvmError>, ignore_unrecognized: bool, version: JNIVersion, } -impl Default for InitArgsBuilder { +impl<'a> Default for InitArgsBuilder<'a> { fn default() -> Self { InitArgsBuilder { - opts: vec![], + opts: Ok(vec![]), ignore_unrecognized: false, version: JNIVersion::V8, } } } -impl InitArgsBuilder { +impl<'a> InitArgsBuilder<'a> { /// Create a new default InitArgsBuilder pub fn new() -> Self { Default::default() } - /// Add an option to the init args + /// Adds a JVM option, such as `-Djavax.net.debug=all`. /// - /// The `vfprintf`, `abort`, and `exit` options are unsupported at this time. - pub fn option(self, opt_string: &str) -> Self { - let mut s = self; + /// See [the JNI specification][jni-options] for details on which options are accepted. + /// + /// The `vfprintf`, `abort`, and `exit` options are unsupported at this time. Setting one of + /// these options has no effect. + /// + /// The option must not contain any U+0000 code points except one at the end. A U+0000 code + /// point at the end is not required, but on platforms where UTF-8 is the default character + /// encoding, including one U+0000 code point at the end will make this method run slightly + /// faster. + /// + /// # Errors + /// + /// This method can fail if: + /// + /// * `opt_string` contains a U+0000 code point before the end. + /// * `opt_string` cannot be represented in the platform default character encoding. + /// * the platform's character encoding conversion API reports some other error. + /// * `opt_string` is too long. (In the current implementation, the maximum allowed length is + /// 1048576 bytes on Windows. There is currently no limit on other platforms.) + /// + /// Errors raised by this method are deferred. If an error occurs, it is returned from + /// [`InitArgsBuilder::build`] instead. + /// + /// [jni-options]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html#jni_createjavavm + pub fn option(mut self, opt_string: impl AsRef<str> + Into<Cow<'a, str>>) -> Self { + if let Err(error) = self.try_option(opt_string) { + self.opts = Err(error); + } + + self + } - match opt_string { - "vfprintf" | "abort" | "exit" => return s, - _ => {} + /// Adds a JVM option, such as `-Djavax.net.debug=all`. Returns an error immediately upon + /// failure. + /// + /// This is an alternative to [`InitArgsBuilder::option`] that does not defer errors. See + /// below for details. + /// + /// See [the JNI specification][jni-options] for details on which options are accepted. + /// + /// The `vfprintf`, `abort`, and `exit` options are unsupported at this time. Setting one of + /// these options has no effect. + /// + /// The option must not contain any U+0000 code points except one at the end. A U+0000 code + /// point at the end is not required, but on platforms where UTF-8 is the default character + /// encoding, including one U+0000 code point at the end will make this method run slightly + /// faster. + /// + /// # Errors + /// + /// This method can fail if: + /// + /// * `opt_string` contains a U+0000 code point before the end. + /// * `opt_string` cannot be represented in the platform default character encoding. + /// * the platform's character encoding conversion API reports some other error. + /// * `opt_string` is too long. (In the current implementation, the maximum allowed length is + /// 1048576 bytes on Windows. There is currently no limit on other platforms.) + /// + /// Unlike the `option` method, this one does not defer errors. If the `opt_string` cannot be + /// used, then this method returns `Err` and `self` is not changed. If there is already a + /// deferred error, however, then this method does nothing. + /// + /// [jni-options]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html#jni_createjavavm + pub fn try_option(&mut self, opt_string: impl Into<Cow<'a, str>>) -> Result<(), JvmError> { + let opt_string = opt_string.into(); + + // If there is already a deferred error, do nothing. + let opts = match &mut self.opts { + Ok(ok) => ok, + Err(_) => return Ok(()), + }; + + // If the option is the empty string, then skip everything else and pass a constant empty + // C string. This isn't just an optimization; Win32 `WideCharToMultiByte` will **fail** if + // passed an empty string, so we have to do this check first. + if matches!(opt_string.as_ref(), "" | "\0") { + opts.push(Cow::Borrowed(unsafe { + // Safety: This string not only is null-terminated without any interior null bytes, + // it's nothing but a null terminator. + CStr::from_bytes_with_nul_unchecked(b"\0") + })); + return Ok(()); + } + // If this is one of the special options, do nothing. + else if SPECIAL_OPTIONS.contains(&&*opt_string) { + return Ok(()); } - s.opts.push(opt_string.into()); + let encoded: Cow<'a, CStr> = { + cfg_if! { + if #[cfg(windows)] { + char_encoding_windows::str_to_cstr_win32_default_codepage(opt_string)? + } + else { + // Assume UTF-8 on all other platforms. + char_encoding_generic::utf8_to_cstr(opt_string)? + } + } + }; - s + opts.push(encoded); + Ok(()) + } + + /// Adds a JVM option, such as `-Djavax.net.debug=all`. The option must be a `CStr` encoded in + /// the platform default character encoding. + /// + /// This is an alternative to [`InitArgsBuilder::option`] that does not do any encoding. This + /// method is not `unsafe` as it cannot cause undefined behavior, but the option will be + /// garbled (that is, become [mojibake](https://en.wikipedia.org/wiki/Mojibake)) if not + /// encoded correctly. + /// + /// See [the JNI specification][jni-options] for details on which options are accepted. + /// + /// The `vfprintf`, `abort`, and `exit` options are unsupported at this time. Setting one of + /// these options has no effect. + /// + /// This method does not fail, and will neither return nor defer an error. + /// + /// [jni-options]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html#jni_createjavavm + pub fn option_encoded(mut self, opt_string: impl Into<Cow<'a, CStr>>) -> Self { + let opt_string = opt_string.into(); + + // If there is already a deferred error, do nothing. + let opts = match &mut self.opts { + Ok(ok) => ok, + Err(_) => return self, + }; + + // If this is one of the special options, do nothing. + if SPECIAL_OPTIONS_C.contains(&&*opt_string) { + return self; + } + + // Add the option. + opts.push(opt_string); + + self } /// Set JNI version for the init args @@ -83,19 +301,20 @@ impl InitArgsBuilder { /// Build the `InitArgs` /// - /// This will check for internal nulls in the option strings and will return - /// an error if one is found. - pub fn build(self) -> Result<InitArgs, JvmError> { - let mut opts = Vec::with_capacity(self.opts.len()); - for opt in self.opts { - let option_string = - CString::new(opt.as_str()).map_err(|_| JvmError::NullOptString(opt))?; - let jvm_opt = JavaVMOption { - optionString: option_string.into_raw(), - extraInfo: ::std::ptr::null_mut(), - }; - opts.push(jvm_opt); - } + /// # Errors + /// + /// If a call to [`InitArgsBuilder::option`] caused a deferred error, it is returned from this + /// method. + pub fn build(self) -> Result<InitArgs<'a>, JvmError> { + let opt_strings = self.opts?; + + let opts: Vec<JavaVMOption> = opt_strings + .iter() + .map(|opt_string| JavaVMOption { + optionString: opt_string.as_ptr() as _, + extraInfo: ptr::null_mut(), + }) + .collect(); Ok(InitArgs { inner: JavaVMInitArgs { @@ -104,13 +323,17 @@ impl InitArgsBuilder { options: opts.as_ptr() as _, nOptions: opts.len() as _, }, - opts, + _opts: opts, + _opt_strings: opt_strings, }) } - /// Returns collected options - pub fn options(&self) -> Vec<String> { - self.opts.clone() + /// Returns collected options. + /// + /// If a call to [`InitArgsBuilder::option`] caused a deferred error, then this method returns + /// a reference to that error. + pub fn options(&self) -> Result<&[Cow<'a, CStr>], &JvmError> { + self.opts.as_ref().map(Vec::as_slice) } } @@ -118,21 +341,20 @@ impl InitArgsBuilder { /// /// *This API requires "invocation" feature to be enabled, /// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).* -pub struct InitArgs { +pub struct InitArgs<'a> { inner: JavaVMInitArgs, - opts: Vec<JavaVMOption>, + + // `JavaVMOption` structures are stored here. The JVM accesses this `Vec`'s contents through a + // raw pointer. + _opts: Vec<JavaVMOption>, + + // Option strings are stored here. This ensures that any that are owned aren't dropped before + // the JVM is finished with them. + _opt_strings: Vec<Cow<'a, CStr>>, } -impl InitArgs { +impl<'a> InitArgs<'a> { pub(crate) fn inner_ptr(&self) -> *mut c_void { &self.inner as *const _ as _ } } - -impl Drop for InitArgs { - fn drop(&mut self) { - for opt in self.opts.iter() { - unsafe { CString::from_raw(opt.optionString) }; - } - } -} diff --git a/src/wrapper/java_vm/init_args/char_encoding_generic.rs b/src/wrapper/java_vm/init_args/char_encoding_generic.rs new file mode 100644 index 0000000..8193c16 --- /dev/null +++ b/src/wrapper/java_vm/init_args/char_encoding_generic.rs @@ -0,0 +1,117 @@ +use super::JvmError; +use std::{ + borrow::Cow, + ffi::{CStr, CString}, +}; + +/// Converts `s: Cow<[u8]>` into a `Cow<CStr>`, adding a null byte if necessary. +/// +/// `original`, if present, is the original string, which will be moved into a [`JvmError]` +/// in the event of failure. If `original` is absent, then `s` *is* the original +/// string (i.e. is encoded in UTF-8), and is to be moved into the `JvmError` upon failure. +/// +/// # Errors +/// +/// This will fail if `s` contains any null bytes other than a single null byte at the end. +/// +/// # Safety +/// +/// If `original` is `None`, then `s` must contain valid UTF-8. +pub(super) unsafe fn bytes_to_cstr<'a>( + mut s: Cow<'a, [u8]>, + original: Option<Cow<'_, str>>, +) -> Result<Cow<'a, CStr>, JvmError> { + // Check if it has a null byte at the end already. If not, add one. + let mut null_byte_added = false; + + if s.last() != Some(&0) { + s.to_mut().push(0); + null_byte_added = true; + } + + // This function is called if conversion fails because the string has a null byte + // in the middle. + let convert_error = move |s: Cow<'a, [u8]>| -> JvmError { + // We need to get back to a `String` in order to insert it into the error. How + // to do that depends on whether we were given a separate original or not. + let s: String = { + if let Some(original) = original { + // Yes, there is a separate original. Use that. + original.into_owned() + } else { + // No, `s` *is* the original. Strip off the null byte if we + // added one, then assume the rest is valid UTF-8. + let mut s: Vec<u8> = s.into_owned(); + + if null_byte_added { + let _removed_null_byte: Option<u8> = s.pop(); + debug_assert_eq!(_removed_null_byte, Some(0)); + } + + // Safety: The caller of this function asserts that this is valid UTF-8. We + // have not changed it other than adding a null byte at the end. + unsafe { String::from_utf8_unchecked(s) } + } + }; + + JvmError::NullOptString(s) + }; + + // Now, try to convert. Exactly how to do this, and exactly how to handle errors, depends + // on whether it's borrowed or owned. + let s: Cow<'a, CStr> = match s { + Cow::Owned(s) => Cow::Owned({ + CString::from_vec_with_nul(s) + .map_err(|error| convert_error(Cow::Owned(error.into_bytes())))? + }), + + Cow::Borrowed(s) => Cow::Borrowed({ + CStr::from_bytes_with_nul(s).map_err(|_error| convert_error(Cow::Borrowed(s)))? + }), + }; + + // Done. + Ok(s) +} + +/// Converts `s: Cow<str>` into a `Cow<CStr>`, still in UTF-8 encoding, adding a null byte if +/// necessary. +pub(super) fn utf8_to_cstr<'a>(s: Cow<'a, str>) -> Result<Cow<'a, CStr>, JvmError> { + let s: Cow<'a, [u8]> = match s { + Cow::Owned(s) => Cow::Owned(s.into_bytes()), + Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()), + }; + + // Safety: `s` was just converted from type `str`, so it's already known to contain valid + // UTF-8. + unsafe { bytes_to_cstr(s, None) } +} + +#[test] +fn test() { + use assert_matches::assert_matches; + + { + let result = utf8_to_cstr("Hello, world 😎".into()).unwrap(); + assert_eq!( + result.to_bytes_with_nul(), + b"Hello, world \xf0\x9f\x98\x8e\0" + ); + assert_matches!(result, Cow::Owned(_)); + } + + { + let result = utf8_to_cstr("Hello, world 😎\0".into()).unwrap(); + assert_eq!( + result.to_bytes_with_nul(), + b"Hello, world \xf0\x9f\x98\x8e\0" + ); + assert_matches!(result, Cow::Borrowed(_)); + } + + { + let result = utf8_to_cstr("Hello,\0world".into()).unwrap_err(); + let error_string = assert_matches!(result, JvmError::NullOptString(string) => string); + assert_eq!(error_string, "Hello,\0world"); + } +} diff --git a/src/wrapper/java_vm/init_args/char_encoding_windows.rs b/src/wrapper/java_vm/init_args/char_encoding_windows.rs new file mode 100644 index 0000000..0a1fbbc --- /dev/null +++ b/src/wrapper/java_vm/init_args/char_encoding_windows.rs @@ -0,0 +1,443 @@ +use super::{char_encoding_generic::*, JvmError}; +use std::{ + borrow::Cow, + convert::TryInto, + ffi::{c_int, c_uint, CStr}, + io, + mem::MaybeUninit, + ptr, +}; +use windows_sys::Win32::Globalization as winnls; + +// The integer type used by `WideCharToMultiByte` for string lengths. +type WSize = c_int; + +// The type of Windows codepage numbers. +type WCodepage = c_uint; + +// The maximum length, in UTF-8 bytes, of strings that will be accepted for transcoding. +// +// The purpose of this limit is to prevent overflow. `WideCharToMultiByte` behaves rather badly +// (see https://github.com/jni-rs/jni-rs/pull/414 for discussion) if the string is long enough to +// overflow its counters. +// +// Although it is possible to transcode a string of any length by splitting it into smaller +// substrings, the code complexity needed to do so isn't worthwhile just for transcoding JVM +// options. Also, `test_overflow` would take a very long time to run, which was deemed unacceptable +// (see https://github.com/jni-rs/jni-rs/pull/414#issuecomment-1419130483). We set this arbitrary +// limit instead. +const MAX_INPUT_LEN: usize = 1048576; + +/// Converts `s` into a `Cow<CStr>` encoded in the specified Windows code page. +pub(super) fn str_to_cstr_win32<'a>( + s: Cow<'a, str>, + needed_codepage: WCodepage, +) -> Result<Cow<'static, CStr>, JvmError> { + // First, check if the input string (UTF-8) is too long to transcode. Bail early if so. + if s.len() > MAX_INPUT_LEN { + return Err(JvmError::OptStringTooLong { + opt_string: s.into_owned(), + }); + } + + // This function will generate an error if `WideCharToMultiByte` fails. + fn convert_error(s: Cow<str>) -> JvmError { + JvmError::OptStringTranscodeFailure { + opt_string: s.into_owned(), + error: io::Error::last_os_error(), + } + } + + // Convert the string to UTF-16 first. + let s_utf16: Vec<u16> = s.encode_utf16().collect(); + + // Determine how long the string is, in UTF-16 units, in the integer type that Win32 expects. + // Overflow should be impossible; panic if it happens. + let s_utf16_len: WSize = s_utf16 + .len() + .try_into() + .expect("UTF-16 form of input string is too long"); + + // Decide which flags we're going to use. + let conversion_flags = match needed_codepage { + // No flags may be given for the following code pages. + // https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte + 42 + | 50220 + | 50221 + | 50222 + | 50225 + | 50227 + | 50229 + | 54936 + | 57002..=57011 + | 65000 + | 65001 => 0, + + _ => winnls::WC_COMPOSITECHECK | winnls::WC_NO_BEST_FIT_CHARS, + }; + + // Find out how much buffer space will be needed for the output and whether the string is + // fully representable. + let mut is_non_representable: Option<MaybeUninit<_>> = match needed_codepage { + // All characters are representable in UTF-7 and UTF-8, and moreover + // `WideCharToMultiByte` will fail if the target encoding is UTF-7 or UTF-8 and this is not + // `None`. + winnls::CP_UTF7 | winnls::CP_UTF8 => None, + _ => Some(MaybeUninit::uninit()), + }; + + // Safety: `s_utf16.as_ptr()` is a valid pointer to a UTF-16 string, and `s_utf16_len` is its + // length. `lpDefaultChar` is null. `lpUsedDefaultChar` is either null or valid. `cbMultiByte` + // is zero. + let required_buffer_space = unsafe { + winnls::WideCharToMultiByte( + needed_codepage, + conversion_flags, + s_utf16.as_ptr(), + s_utf16_len, + ptr::null_mut(), + 0, + ptr::null(), + match &mut is_non_representable { + Some(x) => x.as_mut_ptr(), + None => ptr::null_mut(), + }, + ) + }; + + // Bail on error. + if required_buffer_space == 0 { + drop(s_utf16); + + return Err(convert_error(s)); + } + + // Check if the string is not fully representable. + if let Some(is_non_representable) = is_non_representable { + // Safety: `is_non_representable` has been initialized by `WideCharToMultiByte`. + let is_non_representable = unsafe { is_non_representable.assume_init() }; + + if is_non_representable != 0 { + drop(s_utf16); + + return Err(JvmError::OptStringNotRepresentable { + opt_string: s.into_owned(), + }); + } + } + + // Convert the required buffer space to `usize`, and increment it by one for the null + // terminator. + // + // This shouldn't overflow (see the comment on `MAX_INPUT_LEN` above), so we won't check for + // overflow here. + let required_buffer_space_usize: usize = required_buffer_space as _; + let required_buffer_space_usize_with_nul: usize = required_buffer_space_usize + 1; + + // Allocate enough buffer space, including one byte for the null terminator. + let mut output = Vec::<u8>::with_capacity(required_buffer_space_usize_with_nul); + + // Perform the actual conversion. + // + // Safety: `chunk.as_ptr()` is a valid pointer, and `chunk_len_i32` is its length. + // `chunk_output_ptr` is a valid pointer, and `required_buffer_space` is its length. + // All other raw pointers are null. + let used_buffer_space = unsafe { + winnls::WideCharToMultiByte( + needed_codepage, + conversion_flags, + s_utf16.as_ptr(), + s_utf16_len, + output.as_mut_ptr(), + required_buffer_space, + ptr::null(), + ptr::null_mut(), + ) + }; + + drop(s_utf16); + + // Bail on error. + if used_buffer_space == 0 { + drop(output); + + return Err(convert_error(s)); + } + + let used_buffer_space_usize: usize = used_buffer_space as usize; + + // Set the new length of the output buffer. Don't use `required_buffer_space`, just in case + // `WideCharToMultiByte` changes its mind about how much buffer space it's actually going to + // use. + // + // Safety: `used_buffer_space_usize` is the number of bytes that `WideCharToMultiByte` has + // just initialized. + unsafe { + output.set_len(used_buffer_space_usize); + } + + // That's it, it's converted. Now turn it into a `CString`. This will add a null terminator if + // there isn't one already and check for null bytes in the middle. + unsafe { bytes_to_cstr(Cow::Owned(output), Some(s.into())) } +} + +/// Converts `s` into the Windows default character encoding. +pub(super) fn str_to_cstr_win32_default_codepage<'a>( + s: Cow<'a, str>, +) -> Result<Cow<'a, CStr>, JvmError> { + // Get the code page. There is a remote possibility that it is UTF-8. If so, pass the + // string through unchanged (other than adding a null terminator). If not, we need to have + // Windows convert the string to the expected code page first. + + // Safety: This function isn't actually unsafe. + let needed_codepage = unsafe { winnls::GetACP() }; + + if needed_codepage == winnls::CP_UTF8 { + // The code page is UTF-8! Lucky us. + return utf8_to_cstr(s); + } + + // The code page is not UTF-8, so do the transcoding. + str_to_cstr_win32(s, needed_codepage) +} + +/// Transcodes text in an arbitrary Windows codepage into a Rust `String`. Used to test +/// round-tripping. +#[cfg(test)] +fn codepage_to_string_win32( + codepage_string: impl AsRef<[u8]>, + codepage: WCodepage, + max_expected_utf16_len: WSize, +) -> io::Result<String> { + let codepage_string_slice = codepage_string.as_ref(); + + let codepage_string_slice_len: WSize = codepage_string_slice + .len() + .try_into() + .expect("`codepage_string`'s length is too large to transcode with Win32"); + + let mut buf = Vec::<u16>::with_capacity( + max_expected_utf16_len + .try_into() + .expect("expected_utf16_len is negative or exceeds address space"), + ); + + // Safety: All of these pointers and lengths are valid and checked for overflow. + let utf16_units_transcoded = unsafe { + winnls::MultiByteToWideChar( + codepage, + 0, + codepage_string_slice.as_ptr() as *const _, + codepage_string_slice_len, + buf.as_mut_ptr(), + max_expected_utf16_len, + ) + }; + + if utf16_units_transcoded == 0 { + return Err(io::Error::last_os_error()); + } + + // Safety: `MultiByteToWideChar` claims to have initialized this many UTF-16 units. + unsafe { + buf.set_len(utf16_units_transcoded as _); + } + + drop(codepage_string); + + let string = + String::from_utf16(buf.as_slice()).expect("`MultiByteToWideChar` generated invalid UTF-16"); + + Ok(string) +} + +#[test] +fn test() { + use assert_matches::assert_matches; + + { + let result = str_to_cstr_win32("Hello, world 😎".into(), winnls::CP_UTF8).unwrap(); + assert_eq!( + result.to_bytes_with_nul(), + b"Hello, world \xf0\x9f\x98\x8e\0" + ); + assert_matches!(result, Cow::Owned(_)); + } + + { + let result = str_to_cstr_win32("Hello, world 😎\0".into(), winnls::CP_UTF8).unwrap(); + assert_eq!( + result.to_bytes_with_nul(), + b"Hello, world \xf0\x9f\x98\x8e\0" + ); + } + + { + let result = str_to_cstr_win32("Hello, world 😎".into(), 1252).unwrap_err(); + let error_string = assert_matches!(result, JvmError::OptStringNotRepresentable { opt_string } => opt_string); + assert_eq!(error_string, "Hello, world 😎"); + } + + { + let result = str_to_cstr_win32("Hello, world™".into(), 1252).unwrap(); + assert_eq!(result.to_bytes_with_nul(), b"Hello, world\x99\0"); + assert_matches!(result, Cow::Owned(_)); + } +} + +#[test] +fn test_overflow() { + use assert_matches::assert_matches; + + // Note: We avoid naïvely using `assert` here, because assertion failure will dump millions of + // characters to the console. Instead, here are some functions for handling errors without + // doing that. + + #[track_caller] + fn check_and_clear_error_opt_string(expected_opt_string: &str, error: &mut JvmError) { + if let Some(actual_opt_string) = error.opt_string_mut() { + if actual_opt_string != expected_opt_string { + panic!("opt_string was mangled in moving it to an error"); + } + + *actual_opt_string = String::new(); + } + } + + #[track_caller] + fn expect_success( + expected_opt_string: &str, + result: Result<Cow<'static, CStr>, JvmError>, + ) -> Cow<'static, CStr> { + match result { + Ok(ok) => ok, + Err(mut error) => { + check_and_clear_error_opt_string(expected_opt_string, &mut error); + panic!("unexpected transcoding failure: {}", error) + } + } + } + + #[track_caller] + fn expect_successful_roundtrip( + expected_opt_string: &str, + result: Result<Cow<'static, CStr>, JvmError>, + ) -> Cow<'static, CStr> { + let string = expect_success(expected_opt_string, result); + assert!( + expected_opt_string.as_bytes() == string.to_bytes(), + "opt_string was transcoded successfully but mangled" + ); + string + } + + #[track_caller] + fn expect_opt_string_too_long( + expected_opt_string: &str, + result: Result<Cow<'static, CStr>, JvmError>, + ) { + let mut error = match result { + Err(err) => err, + Ok(ok) => { + assert!( + expected_opt_string.as_bytes() == ok.to_bytes(), + "transcoding unexpectedly succeeded and resulted in mangled output" + ); + panic!("transcoding unexpectedly succeeded") + } + }; + + check_and_clear_error_opt_string(expected_opt_string, &mut error); + + assert_matches!(error, JvmError::OptStringTooLong { .. }); + } + + { + // Try transcoding a plain ASCII string. + + // First, allocate enough space to completely fill the maximum allowed length, plus one + // more. + //eprintln!("Allocating & filling ASCII"); + let string = vec![b'H'; MAX_INPUT_LEN.checked_add(1).unwrap()]; + + //eprintln!("Checking UTF-8 correctness"); + let mut string = String::from_utf8(string).unwrap(); + + // This string is currently one character too long to transcode, so there should be an + // overflow error. + //eprintln!("Transcoding ASCII string that's too long"); + expect_opt_string_too_long( + &string, + str_to_cstr_win32(string.as_str().into(), winnls::CP_UTF8), + ); + + // But if we remove one character… + assert_eq!(string.pop(), Some('H')); + + // …then it should transcode fine. + //eprintln!("Transcoding ASCII string that's not too long"); + expect_successful_roundtrip( + &string, + str_to_cstr_win32(string.as_str().into(), winnls::CP_UTF8), + ); + } + + { + // Try transcoding a non-ASCII string. + + // U+07FF is the highest code point that can be represnted in UTF-8 with only two bytes, so + // we'll use that. The UTF-8 encoding is `df bf`. We fill it this way because it's much + // faster than the naïve character-by-character approach (at least unless some future Rust + // compiler performs this optimization on its own, but 1.66 doesn't). + //eprintln!("Allocating & filling non-ASCII for UTF-8 and UTF-7"); + let string_byte_pairs = vec![u16::from_be(0xdfbf); MAX_INPUT_LEN / 2]; + + //eprintln!("Checking UTF-8 correctness"); + let string: &str = + std::str::from_utf8(bytemuck::cast_slice(string_byte_pairs.as_slice())).unwrap(); + + // Again, the string should transcode without overflow. + //eprintln!("Transcoding non-ASCII to UTF-8"); + expect_successful_roundtrip(string, str_to_cstr_win32(string.into(), winnls::CP_UTF8)); + + // This should work even with UTF-7. This is the real reason we're using U+07FF: we need + // to check that the highest code point that fits under the limit will not overflow even + // with the worst-case code page. + { + //eprintln!("Transcoding non-ASCII to UTF-7"); + let result = expect_success(string, str_to_cstr_win32(string.into(), winnls::CP_UTF7)); + + // *And* it should roundtrip back to UTF-8. + //eprintln!("Transcoding UTF-7 back to UTF-8"); + let result: String = codepage_to_string_win32( + result.to_bytes(), + winnls::CP_UTF7, + (string.len() / 2).try_into().unwrap(), + ) + .unwrap(); + + assert!(result == string, "didn't roundtrip via UTF-7"); + } + } + + { + // Try transcoding to Windows-1252. This is the slowest part of the test + // (`WideCharToMultiByte` is very slow at this, for some reason), so it's done last. + //eprintln!("Allocating & filling non-ASCII for Windows-1252"); + let string_byte_pairs = vec![u16::from_be(0xc2ae); MAX_INPUT_LEN / 2]; + + //eprintln!("Checking UTF-8 correctness"); + let string: &str = + std::str::from_utf8(bytemuck::cast_slice(string_byte_pairs.as_slice())).unwrap(); + + //eprintln!("Transcoding non-ASCII to Windows-1252"); + let result = expect_success(string, str_to_cstr_win32(string.into(), 1252)); + + //eprintln!("Checking Windows-1252 for correctness"); + assert!( + result.to_bytes().iter().all(|byte| *byte == 0xae), + "string didn't transcode to Windows-1252 properly" + ); + } +} diff --git a/src/wrapper/java_vm/vm.rs b/src/wrapper/java_vm/vm.rs index e127634..60f553a 100644 --- a/src/wrapper/java_vm/vm.rs +++ b/src/wrapper/java_vm/vm.rs @@ -1,9 +1,9 @@ use std::{ cell::RefCell, - ops::Deref, + ops::{Deref, DerefMut}, ptr, sync::atomic::{AtomicUsize, Ordering}, - thread::current, + thread::{current, Thread}, }; use log::{debug, error}; @@ -11,8 +11,11 @@ use log::{debug, error}; use crate::{errors::*, sys, JNIEnv}; #[cfg(feature = "invocation")] -use crate::InitArgs; -use std::thread::Thread; +use { + crate::InitArgs, + std::os::raw::c_void, + std::{ffi::OsStr, path::PathBuf}, +}; /// The Java VM, providing [Invocation API][invocation-api] support. /// @@ -55,23 +58,22 @@ use std::thread::Thread; /// /// ## Launching JVM from Rust /// -/// To [launch][launch-vm] a JVM from a native process, enable the `invocation` feature -/// in the Cargo.toml: +/// To [launch][launch-vm] a JVM from a native process, enable the `invocation` +/// feature in the Cargo.toml: /// /// ```toml -/// jni = { version = "0.20.0", features = ["invocation"] } +/// jni = { version = "0.21.1", features = ["invocation"] } /// ``` /// -/// The application will require linking to the dynamic `jvm` library, which is distributed -/// with the JVM, and allow to use `JavaVM#new`: +/// The application will be able to use [`JavaVM::new`] which will dynamically +/// load a `jvm` library (which is distributed with the JVM) at runtime: /// /// ```rust /// # use jni::errors; /// # // -/// # fn main() -> errors::Result<()> { /// # // Ignore this test without invocation feature, so that simple `cargo test` works -/// # #[cfg(feature = "invocation")] { -/// # // +/// # #[cfg(feature = "invocation")] +/// # fn main() -> errors::StartJvmResult<()> { /// # use jni::{AttachGuard, objects::JValue, InitArgsBuilder, JNIEnv, JNIVersion, JavaVM, sys::jint}; /// # // /// // Build the VM properties @@ -93,7 +95,7 @@ use std::thread::Thread; /// // /// // This method returns the guard that will detach the current thread when dropped, /// // also freeing any local references created in it -/// let env = jvm.attach_current_thread()?; +/// let mut env = jvm.attach_current_thread()?; /// /// // Call Java Math#abs(-10) /// let x = JValue::from(-10); @@ -102,28 +104,28 @@ use std::thread::Thread; /// /// assert_eq!(val, 10); /// -/// # } /// # Ok(()) } +/// # +/// # // This is a stub that gets run instead if the invocation feature is not built +/// # #[cfg(not(feature = "invocation"))] +/// # fn main() {} /// ``` /// -/// During build time, the JVM installation path is determined: -/// 1. By `JAVA_HOME` environment variable, if it is set. +/// At runtime, the JVM installation path is determined via the [java-locator] crate: +/// 1. By the `JAVA_HOME` environment variable, if it is set. /// 2. Otherwise — from `java` output. /// -/// It is recommended to set `JAVA_HOME` to have reproducible builds, -/// especially, in case of multiple VMs installed. +/// It is recommended to set `JAVA_HOME` /// -/// At application run time, you must specify the path -/// to the `jvm` library so that the loader can locate it. -/// * On **Windows**, append the path to `jvm.dll` to `PATH` environment variable. +/// For the operating system to correctly load the `jvm` library it may also be +/// necessary to update the path that the OS uses to find dependencies of the +/// `jvm` library. +/// * On **Windows**, append the path to `$JAVA_HOME/bin` to the `PATH` environment variable. /// * On **MacOS**, append the path to `libjvm.dylib` to `LD_LIBRARY_PATH` environment variable. /// * On **Linux**, append the path to `libjvm.so` to `LD_LIBRARY_PATH` environment variable. /// /// The exact relative path to `jvm` library is version-specific. /// -/// For more information on linking — see documentation -/// in [build.rs](https://github.com/jni-rs/jni-rs/tree/master/build.rs). -/// /// [invocation-api]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html /// [get-vm]: struct.JNIEnv.html#method.get_java_vm /// [launch-vm]: struct.JavaVM.html#method.new @@ -131,6 +133,7 @@ use std::thread::Thread; /// [actp]: struct.JavaVM.html#method.attach_current_thread_permanently /// [actd]: struct.JavaVM.html#method.attach_current_thread_as_daemon /// [spec-references]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/design.html#referencing-java-objects +/// [java-locator]: https://crates.io/crates/java-locator #[repr(transparent)] #[derive(Debug)] pub struct JavaVM(*mut sys::JavaVM); @@ -145,27 +148,87 @@ impl JavaVM { /// not be attached to JVM. You must explicitly use `attach_current_thread…` methods (refer /// to [Attaching Native Threads section](#attaching-native-threads)). /// - /// *This API requires "invocation" feature to be enabled, + /// *This API requires the "invocation" feature to be enabled, /// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).* + /// + /// This will attempt to locate a JVM using + /// [java-locator], if the JVM has not already been loaded. Use the + /// [`with_libjvm`][Self::with_libjvm] method to give an explicit location for the JVM shared + /// library (`jvm.dll`, `libjvm.so`, or `libjvm.dylib`, depending on the platform). #[cfg(feature = "invocation")] - pub fn new(args: InitArgs) -> Result<Self> { - use std::os::raw::c_void; + pub fn new(args: InitArgs) -> StartJvmResult<Self> { + Self::with_libjvm(args, || { + Ok([ + java_locator::locate_jvm_dyn_library() + .map_err(StartJvmError::NotFound)? + .as_str(), + java_locator::get_jvm_dyn_lib_file_name(), + ] + .iter() + .collect::<PathBuf>()) + }) + } + + /// Launch a new JavaVM using the provided init args, loading it from the given shared library file if it's not already loaded. + /// + /// Unlike original JNI API, the main thread (the thread from which this method is called) will + /// not be attached to JVM. You must explicitly use `attach_current_thread…` methods (refer + /// to [Attaching Native Threads section](#attaching-native-threads)). + /// + /// *This API requires the "invocation" feature to be enabled, + /// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).* + /// + /// The `libjvm_path` parameter takes a *closure* which returns the path to the JVM shared + /// library. The closure is only called if the JVM is not already loaded. Any work that needs + /// to be done to locate the JVM shared library should be done inside that closure. + #[cfg(feature = "invocation")] + pub fn with_libjvm<P: AsRef<OsStr>>( + args: InitArgs, + libjvm_path: impl FnOnce() -> StartJvmResult<P>, + ) -> StartJvmResult<Self> { + // Determine the path to the shared library. + let libjvm_path = libjvm_path()?; + let libjvm_path_string = libjvm_path.as_ref().to_string_lossy().into_owned(); + + // Try to load it. + let libjvm = match unsafe { libloading::Library::new(libjvm_path.as_ref()) } { + Ok(ok) => ok, + Err(error) => return Err(StartJvmError::LoadError(libjvm_path_string, error)), + }; + unsafe { + // Try to find the `JNI_CreateJavaVM` function in the loaded library. + let create_fn = libjvm + .get(b"JNI_CreateJavaVM\0") + .map_err(|error| StartJvmError::LoadError(libjvm_path_string.to_owned(), error))?; + + // Create the JVM. + Self::with_create_fn_ptr(args, *create_fn).map_err(StartJvmError::Create) + } + } + + #[cfg(feature = "invocation")] + unsafe fn with_create_fn_ptr( + args: InitArgs, + create_fn_ptr: unsafe extern "system" fn( + pvm: *mut *mut sys::JavaVM, + penv: *mut *mut c_void, + args: *mut c_void, + ) -> sys::jint, + ) -> Result<Self> { let mut ptr: *mut sys::JavaVM = ::std::ptr::null_mut(); let mut env: *mut sys::JNIEnv = ::std::ptr::null_mut(); - unsafe { - jni_error_code_to_result(sys::JNI_CreateJavaVM( - &mut ptr as *mut _, - &mut env as *mut *mut sys::JNIEnv as *mut *mut c_void, - args.inner_ptr(), - ))?; + jni_error_code_to_result(create_fn_ptr( + &mut ptr as *mut _, + &mut env as *mut *mut sys::JNIEnv as *mut *mut c_void, + args.inner_ptr(), + ))?; - let vm = Self::from_raw(ptr)?; - java_vm_unchecked!(vm.0, DetachCurrentThread); + let vm = Self::from_raw(ptr)?; + java_vm_unchecked!(vm.0, DetachCurrentThread); - Ok(vm) - } + Ok(vm) } /// Create a JavaVM from a raw pointer. @@ -223,12 +286,31 @@ impl JavaVM { } } - /// Detaches current thread from the JVM. This operation is _rarely_ appropriate to use, - /// because the attachment methods [ensure](#attaching-native-threads) that the thread is - /// promptly detached. + /// Explicitly detaches the current thread from the JVM. + /// + /// _**Note**: This operation is _rarely_ appropriate to use, because the + /// attachment methods [ensure](#attaching-native-threads) that the thread + /// is automatically detached._ /// /// Detaching a non-attached thread is a no-op. /// + /// To support the use of `JavaVM::destroy()` it may be necessary to use this API to + /// explicitly detach daemon threads before `JavaVM::destroy()` is called because + /// `JavaVM::destroy()` does not synchronize and wait for daemon threads. + /// + /// Any daemon thread that is still "attached" after `JavaVM::destroy()` returns would + /// cause undefined behaviour if it then tries to make any JNI calls or tries + /// to detach itself. + /// + /// Normally `jni-rs` will automatically detach threads from the `JavaVM` by storing + /// a guard in thread-local-storage that will detach on `Drop` but this will cause + /// undefined behaviour if `JavaVM::destroy()` has been called. + /// + /// Calling this will clear the thread-local-storage guard and detach the thread + /// early to avoid any attempt to automatically detach when the thread exits. + /// + /// # Safety + /// /// __Any existing `JNIEnv`s and `AttachGuard`s created in the calling thread /// will be invalidated after this method completes. It is the__ caller’s __responsibility /// to ensure that no JNI calls are subsequently performed on these objects.__ @@ -239,8 +321,7 @@ impl JavaVM { /// never appropriate to use it with the scoped attachment (`attach_current_thread`). // This method is hidden because it is almost never needed and its use requires some // extra care. Its status might be reconsidered if we learn of any use cases that require it. - #[doc(hidden)] - pub fn detach_current_thread(&self) { + pub unsafe fn detach_current_thread(&self) { InternalAttachGuard::clear_tls(); } @@ -290,6 +371,91 @@ impl JavaVM { unsafe { JNIEnv::from_raw(env_ptr as *mut sys::JNIEnv) } } + + /// Unloads the JavaVM and frees all it's associated resources + /// + /// Firstly if this thread is not already attached to the `JavaVM` then + /// it will be attached. + /// + /// This thread will then wait until there are no other non-daemon threads + /// attached to the `JavaVM` before unloading it (including threads spawned + /// by Java and those that are attached via JNI) + /// + /// # Safety + /// + /// IF YOU ARE USING DAEMON THREADS THIS MAY BE DIFFICULT TO USE SAFELY! + /// + /// ## Daemon thread rules + /// + /// Since the JNI spec makes it clear that `DestroyJavaVM` will not wait for + /// attached deamon threads to exit, this also means that if you do have any + /// attached daemon threads it is your responsibility to ensure that they + /// don't try and use JNI after the `JavaVM` is destroyed and you won't be able + /// to detach them after the `JavaVM` has been destroyed. + /// + /// This creates a very unsafe hazard in `jni-rs` because it normally automatically + /// ensures that any thread that gets attached will be detached before it exits. + /// + /// Normally `jni-rs` will automatically detach threads from the `JavaVM` by storing + /// a guard in thread-local-storage that will detach on `Drop` but this will cause + /// undefined behaviour if `JavaVM::destroy()` has been called before the thread + /// exits. + /// + /// To clear this thread-local-storage guard from daemon threads you can call + /// [`JavaVM::detach_current_thread()`] within each daemon thread, before calling + /// this API. + /// + /// Calling this will clear the thread-local-storage guard and detach the thread + /// early to avoid any attempt to automatically detach when the thread exits. + /// + /// ## Don't call from a Java native function + /// + /// There must be no Java methods on the call stack when `JavaVM::destroy()` is called. + /// + /// ## Drop all JNI state, including auto-release types before calling `JavaVM::destroy()` + /// + /// There is currently no `'vm` lifetime associated with a `JavaVM` that + /// would allow the borrow checker to enforce that all `jni` resources + /// associated with the `JavaVM` have been released. + /// + /// Since these JNI resources could lead to undefined behaviour through any + /// use after the `JavaVM` has been destroyed then it is your responsibility + /// to release these resources. + /// + /// In particular, there are numerous auto-release types in the `jni` API + /// that will automatically make JNI calls within their `Drop` + /// implementation. All such types _must_ be dropped before `destroy()` is + /// called to avoid undefined bahaviour. + /// + /// Here is an non-exhaustive list of auto-release types to consider: + /// - `AttachGuard` + /// - `AutoElements` + /// - `AutoElementsCritical` + /// - `AutoLocal` + /// - `GlobalRef` + /// - `JavaStr` + /// - `JMap` + /// - `WeakRef` + /// + /// ## Invalid `JavaVM` on return + /// + /// After `destroy()` returns then the `JavaVM` will be in an undefined state + /// and must be dropped (e.g. via `std::mem::drop()`) to avoid undefined behaviour. + /// + /// This method doesn't take ownership of the `JavaVM` before it is + /// destroyed because the `JavaVM` may have been shared (E.g. via an `Arc`) + /// between all the threads that have not yet necessarily exited before this + /// is called. + /// + /// So although the `JavaVM` won't necessarily be solely owned by this + /// thread when `destroy()` is first called it will conceptually own the + /// `JavaVM` before `destroy()` returns. + pub unsafe fn destroy(&self) -> Result<()> { + unsafe { + let res = java_vm_unchecked!(self.0, DestroyJavaVM); + jni_error_code_to_result(res) + } + } } thread_local! { @@ -301,14 +467,14 @@ static ATTACHED_THREADS: AtomicUsize = AtomicUsize::new(0); /// A RAII implementation of scoped guard which detaches the current thread /// when dropped. The attached `JNIEnv` can be accessed through this guard /// via its `Deref` implementation. -pub struct AttachGuard<'a> { - env: JNIEnv<'a>, +pub struct AttachGuard<'local> { + env: JNIEnv<'local>, should_detach: bool, } -impl<'a> AttachGuard<'a> { +impl<'local> AttachGuard<'local> { /// AttachGuard created with this method will detach current thread on drop - fn new(env: JNIEnv<'a>) -> Self { + fn new(env: JNIEnv<'local>) -> Self { Self { env, should_detach: true, @@ -317,7 +483,7 @@ impl<'a> AttachGuard<'a> { /// AttachGuard created with this method will not detach current thread on drop, which is /// the case for nested attaches. - fn new_nested(env: JNIEnv<'a>) -> Self { + fn new_nested(env: JNIEnv<'local>) -> Self { Self { env, should_detach: false, @@ -325,15 +491,21 @@ impl<'a> AttachGuard<'a> { } } -impl<'a> Deref for AttachGuard<'a> { - type Target = JNIEnv<'a>; +impl<'local> Deref for AttachGuard<'local> { + type Target = JNIEnv<'local>; fn deref(&self) -> &Self::Target { &self.env } } -impl<'a> Drop for AttachGuard<'a> { +impl<'local> DerefMut for AttachGuard<'local> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.env + } +} + +impl<'local> Drop for AttachGuard<'local> { fn drop(&mut self) { if self.should_detach { InternalAttachGuard::clear_tls(); diff --git a/src/wrapper/jnienv.rs b/src/wrapper/jnienv.rs index f0da5d2..12c5c71 100644 --- a/src/wrapper/jnienv.rs +++ b/src/wrapper/jnienv.rs @@ -8,24 +8,30 @@ use std::{ use log::warn; -use crate::signature::ReturnType; use crate::{ descriptors::Desc, errors::*, objects::{ - AutoArray, AutoLocal, AutoPrimitiveArray, GlobalRef, JByteBuffer, JClass, JFieldID, JList, - JMap, JMethodID, JObject, JStaticFieldID, JStaticMethodID, JString, JThrowable, JValue, - ReleaseMode, TypeArray, + AutoElements, AutoElementsCritical, AutoLocal, GlobalRef, JByteBuffer, JClass, JFieldID, + JList, JMap, JMethodID, JObject, JStaticFieldID, JStaticMethodID, JString, JThrowable, + JValue, JValueOwned, ReleaseMode, TypeArray, WeakRef, }, signature::{JavaType, Primitive, TypeSignature}, strings::{JNIString, JavaStr}, sys::{ - self, jarray, jboolean, jbooleanArray, jbyte, jbyteArray, jchar, jcharArray, jdouble, - jdoubleArray, jfloat, jfloatArray, jint, jintArray, jlong, jlongArray, jobjectArray, - jshort, jshortArray, jsize, jvalue, JNINativeMethod, + self, jarray, jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort, jsize, jvalue, + JNINativeMethod, }, JNIVersion, JavaVM, }; +use crate::{ + errors::Error::JniCall, + objects::{ + JBooleanArray, JByteArray, JCharArray, JDoubleArray, JFloatArray, JIntArray, JLongArray, + JObjectArray, JPrimitiveArray, JShortArray, + }, +}; +use crate::{objects::AsJArrayRaw, signature::ReturnType}; /// FFI-compatible JNIEnv struct. You can safely use this as the JNIEnv argument /// to exported methods that will be called by java. This is where most of the @@ -40,6 +46,49 @@ use crate::{ /// will _not_ clear the exception - it's up to the caller to decide whether to /// do so or to let it continue being thrown. /// +/// # References and Lifetimes +/// +/// As in C JNI, interactions with Java objects happen through <dfn>references</dfn>, either local +/// or global, represented by [`JObject`] and [`GlobalRef`] respectively. So long as there is at +/// least one such reference to a Java object, the JVM garbage collector will not reclaim it. +/// +/// <dfn>Global references</dfn> exist until deleted. Deletion occurs when the `GlobalRef` is +/// dropped. +/// +/// <dfn>Local references</dfn> belong to a local reference frame, and exist until +/// [deleted][JNIEnv::delete_local_ref] or until the local reference frame is exited. A <dfn>local +/// reference frame</dfn> is entered when a native method is called from Java, or when Rust code +/// does so explicitly using [`JNIEnv::with_local_frame`]. That local reference frame is exited +/// when the native method or `with_local_frame` returns. When a local reference frame is exited, +/// all local references created inside it are deleted. +/// +/// Unlike C JNI, this crate creates a separate `JNIEnv` for each local reference frame. The +/// associated Rust lifetime `'local` represents that local reference frame. Rust's borrow checker +/// will ensure that local references are not used after their local reference frame exits (which +/// would cause undefined behavior). +/// +/// Unlike global references, local references are not deleted when dropped by default. This is for +/// performance: it is faster for the JVM to delete all of the local references in a frame all at +/// once, than to delete each local reference one at a time. However, this can cause a memory leak +/// if the local reference frame remains entered for a long time, such as a long-lasting loop, in +/// which case local references should be deleted explicitly. Local references can be deleted when +/// dropped if desired; use [`JNIEnv::auto_local`] to arrange that. +/// +/// ## Lifetime Names +/// +/// This crate uses the following convention for lifetime names: +/// +/// * `'local` is the lifetime of a local reference frame, as described above. +/// +/// * `'other_local`, `'other_local_1`, and `'other_local_2` are the lifetimes of some other local +/// reference frame, which may be but doesn't have to be the same as `'local`. For example, +/// [`JNIEnv::new_local_ref`] accepts a local reference in any local reference frame +/// `'other_local` and creates a new local reference to the same object in `'local`. +/// +/// * `'obj_ref` is the lifetime of a borrow of a JNI reference, like <code>&[JObject]</code> +/// or <code>&[GlobalRef]</code>. For example, [`JNIEnv::get_list`] constructs a new +/// [`JList`] that borrows a `&'obj_ref JObject`. +/// /// ## `null` Java references /// `null` Java references are handled by the following rules: /// - If a `null` Java reference is passed to a method that expects a non-`null` @@ -52,6 +101,73 @@ use crate::{ /// values (e.g., `get_object_array_element` or `get_field_unchecked`), /// it is converted to `JObject::null()`. /// +/// # `&self` and `&mut self` +/// +/// Most of the methods on this type take a `&mut self` reference, specifically all methods that +/// can enter a new local reference frame. This includes anything that might invoke user-defined +/// Java code, which can indirectly enter a new local reference frame by calling a native method. +/// +/// The reason for this restriction is to ensure that a `JNIEnv` instance can only be used in the +/// local reference frame that it belongs to. This, in turn, ensures that it is not possible to +/// create [`JObject`]s with the lifetime of a different local reference frame, which would lead to +/// undefined behavior. (See [issue #392] for background discussion.) +/// +/// [issue #392]: https://github.com/jni-rs/jni-rs/issues/392 +/// +/// ## `cannot borrow as mutable` +/// +/// If a function takes two or more parameters, one of them is `JNIEnv`, and another is something +/// returned by a `JNIEnv` method (like [`JObject`]), then calls to that function may not compile: +/// +/// ```rust,compile_fail +/// # use jni::{errors::Result, JNIEnv, objects::*}; +/// # +/// # fn f(env: &mut JNIEnv) -> Result<()> { +/// fn example_function( +/// env: &mut JNIEnv, +/// obj: &JObject, +/// ) { +/// // … +/// } +/// +/// example_function( +/// env, +/// // ERROR: cannot borrow `*env` as mutable more than once at a time +/// &env.new_object( +/// "com/example/SomeClass", +/// "()V", +/// &[], +/// )?, +/// ) +/// # ; Ok(()) +/// # } +/// ``` +/// +/// To fix this, the `JNIEnv` parameter needs to come *last*: +/// +/// ```rust,no_run +/// # use jni::{errors::Result, JNIEnv, objects::*}; +/// # +/// # fn f(env: &mut JNIEnv) -> Result<()> { +/// fn example_function( +/// obj: &JObject, +/// env: &mut JNIEnv, +/// ) { +/// // … +/// } +/// +/// example_function( +/// &env.new_object( +/// "com/example/SomeClass", +/// "()V", +/// &[], +/// )?, +/// env, +/// ) +/// # ; Ok(()) +/// # } +/// ``` +/// /// # Checked and unchecked methods /// /// Some of the methods come in two versions: checked (e.g. `call_method`) and @@ -75,19 +191,19 @@ use crate::{ /// /// Calling unchecked methods with invalid arguments and/or invalid class and /// method descriptors may lead to segmentation fault. -#[derive(Clone, Copy)] #[repr(transparent)] -pub struct JNIEnv<'a> { +#[derive(Debug)] +pub struct JNIEnv<'local> { internal: *mut sys::JNIEnv, - lifetime: PhantomData<&'a ()>, + lifetime: PhantomData<&'local ()>, } -impl<'a> JNIEnv<'a> { +impl<'local> JNIEnv<'local> { /// Create a JNIEnv from a raw pointer. /// /// # Safety /// - /// Expects a valid pointer retrieved from the `GetEnv` JNI function. Only does a null check. + /// Expects a valid pointer retrieved from the `GetEnv` JNI function or [Self::get_raw] function. Only does a null check. pub unsafe fn from_raw(ptr: *mut sys::JNIEnv) -> Result<Self> { non_null!(ptr, "from_raw ptr argument"); Ok(JNIEnv { @@ -96,6 +212,31 @@ impl<'a> JNIEnv<'a> { }) } + /// Get the raw JNIEnv pointer + pub fn get_raw(&self) -> *mut sys::JNIEnv { + self.internal + } + + /// Duplicates this `JNIEnv`. + /// + /// # Safety + /// + /// The duplicate `JNIEnv` must not be used to create any local references, unless they are + /// discarded before the current [local reference frame] is exited. Otherwise, they may have a + /// lifetime longer than they are actually valid for, resulting in a use-after-free bug and + /// undefined behavior. + /// + /// See [issue #392] for background. + /// + /// [local reference frame]: JNIEnv::with_local_frame + /// [issue #392]: https://github.com/jni-rs/jni-rs/issues/392 + pub unsafe fn unsafe_clone(&self) -> Self { + Self { + internal: self.internal, + lifetime: self.lifetime, + } + } + /// Get the java version that we're being executed from. pub fn get_version(&self) -> Result<JNIVersion> { Ok(jni_unchecked!(self.internal, GetVersion).into()) @@ -103,7 +244,12 @@ impl<'a> JNIEnv<'a> { /// Load a class from a buffer of raw class data. The name of the class must match the name /// encoded within the class file data. - pub fn define_class<S>(&self, name: S, loader: JObject<'a>, buf: &[u8]) -> Result<JClass<'a>> + pub fn define_class<S>( + &mut self, + name: S, + loader: &JObject, + buf: &[u8], + ) -> Result<JClass<'local>> where S: Into<JNIString>, { @@ -113,37 +259,63 @@ impl<'a> JNIEnv<'a> { /// Load a class from a buffer of raw class data. The name of the class is inferred from the /// buffer. - pub fn define_unnamed_class<S>(&self, loader: JObject<'a>, buf: &[u8]) -> Result<JClass<'a>> - where - S: Into<JNIString>, - { + pub fn define_unnamed_class(&mut self, loader: &JObject, buf: &[u8]) -> Result<JClass<'local>> { self.define_class_impl(ptr::null(), loader, buf) } + // Note: This requires `&mut` because it might invoke a method on a user-defined `ClassLoader`. fn define_class_impl( - &self, + &mut self, name: *const c_char, - loader: JObject<'a>, + loader: &JObject, buf: &[u8], - ) -> Result<JClass<'a>> { + ) -> Result<JClass<'local>> { let class = jni_non_null_call!( self.internal, DefineClass, name, - loader.into_raw(), + loader.as_raw(), buf.as_ptr() as *const jbyte, buf.len() as jsize ); Ok(unsafe { JClass::from_raw(class) }) } + /// Load a class from a buffer of raw class data. The name of the class must match the name + /// encoded within the class file data. + pub fn define_class_bytearray<S>( + &mut self, + name: S, + loader: &JObject, + buf: &AutoElements<'_, '_, '_, jbyte>, + ) -> Result<JClass<'local>> + where + S: Into<JNIString>, + { + let name = name.into(); + let class = jni_non_null_call!( + self.internal, + DefineClass, + name.as_ptr(), + loader.as_raw(), + buf.as_ptr(), + buf.len() as _ + ); + Ok(unsafe { JClass::from_raw(class) }) + } + /// Look up a class by name. /// /// # Example - /// ```rust,ignore - /// let class: JClass<'a> = env.find_class("java/lang/String"); + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv, objects::JClass}; + /// # + /// # fn example<'local>(env: &mut JNIEnv<'local>) -> Result<()> { + /// let class: JClass<'local> = env.find_class("java/lang/String")?; + /// # Ok(()) + /// # } /// ``` - pub fn find_class<S>(&self, name: S) -> Result<JClass<'a>> + pub fn find_class<S>(&mut self, name: S) -> Result<JClass<'local>> where S: Into<JNIString>, { @@ -152,35 +324,45 @@ impl<'a> JNIEnv<'a> { Ok(unsafe { JClass::from_raw(class) }) } - /// Returns the superclass for a particular class OR `JObject::null()` for `java.lang.Object` or - /// an interface. As with `find_class`, takes a descriptor. - pub fn get_superclass<'c, T>(&self, class: T) -> Result<JClass<'a>> + /// Returns the superclass for a particular class. Returns None for `java.lang.Object` or + /// an interface. As with [Self::find_class], takes a descriptor + /// + /// # Errors + /// + /// If a JNI call fails + pub fn get_superclass<'other_local, T>(&mut self, class: T) -> Result<Option<JClass<'local>>> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, { let class = class.lookup(self)?; - Ok(unsafe { - JClass::from_raw(jni_non_void_call!( + let superclass = unsafe { + JClass::from_raw(jni_unchecked!( self.internal, GetSuperclass, - class.into_raw() + class.as_ref().as_raw() )) - }) + }; + + Ok((!superclass.is_null()).then_some(superclass)) } /// Tests whether class1 is assignable from class2. - pub fn is_assignable_from<'t, 'u, T, U>(&self, class1: T, class2: U) -> Result<bool> + pub fn is_assignable_from<'other_local_1, 'other_local_2, T, U>( + &mut self, + class1: T, + class2: U, + ) -> Result<bool> where - T: Desc<'a, JClass<'t>>, - U: Desc<'a, JClass<'u>>, + T: Desc<'local, JClass<'other_local_1>>, + U: Desc<'local, JClass<'other_local_2>>, { let class1 = class1.lookup(self)?; let class2 = class2.lookup(self)?; Ok(jni_unchecked!( self.internal, IsAssignableFrom, - class1.into_raw(), - class2.into_raw() + class1.as_ref().as_raw(), + class2.as_ref().as_raw() ) == sys::JNI_TRUE) } @@ -191,32 +373,40 @@ impl<'a> JNIEnv<'a> { /// /// See [JNI documentation](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#IsInstanceOf) /// for details. - pub fn is_instance_of<'c, O, T>(&self, object: O, class: T) -> Result<bool> + pub fn is_instance_of<'other_local_1, 'other_local_2, O, T>( + &mut self, + object: O, + class: T, + ) -> Result<bool> where - O: Into<JObject<'a>>, - T: Desc<'a, JClass<'c>>, + O: AsRef<JObject<'other_local_1>>, + T: Desc<'local, JClass<'other_local_2>>, { let class = class.lookup(self)?; Ok(jni_unchecked!( self.internal, IsInstanceOf, - object.into().into_raw(), - class.into_raw() + object.as_ref().as_raw(), + class.as_ref().as_raw() ) == sys::JNI_TRUE) } /// Returns true if ref1 and ref2 refer to the same Java object, or are both `NULL`. Otherwise, /// returns false. - pub fn is_same_object<'b, 'c, O, T>(&self, ref1: O, ref2: T) -> Result<bool> + pub fn is_same_object<'other_local_1, 'other_local_2, O, T>( + &self, + ref1: O, + ref2: T, + ) -> Result<bool> where - O: Into<JObject<'b>>, - T: Into<JObject<'c>>, + O: AsRef<JObject<'other_local_1>>, + T: AsRef<JObject<'other_local_2>>, { Ok(jni_unchecked!( self.internal, IsSameObject, - ref1.into().into_raw(), - ref2.into().into_raw() + ref1.as_ref().as_raw(), + ref2.as_ref().as_raw() ) == sys::JNI_TRUE) } @@ -224,21 +414,35 @@ impl<'a> JNIEnv<'a> { /// thrown in java unless `exception_clear` is called. /// /// # Examples - /// ```rust,ignore - /// let _ = env.throw(("java/lang/Exception", "something bad happened")); + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv}; + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { + /// env.throw(("java/lang/Exception", "something bad happened"))?; + /// # Ok(()) + /// # } /// ``` /// /// Defaulting to "java/lang/Exception": /// - /// ```rust,ignore - /// let _ = env.throw("something bad happened"); + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv}; + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { + /// env.throw("something bad happened")?; + /// # Ok(()) + /// # } /// ``` - pub fn throw<'e, E>(&self, obj: E) -> Result<()> + pub fn throw<'other_local, E>(&mut self, obj: E) -> Result<()> where - E: Desc<'a, JThrowable<'e>>, + E: Desc<'local, JThrowable<'other_local>>, { let throwable = obj.lookup(self)?; - let res: i32 = jni_unchecked!(self.internal, Throw, throwable.into_raw()); + let res: i32 = jni_unchecked!(self.internal, Throw, throwable.as_ref().as_raw()); + + // Ensure that `throwable` isn't dropped before the JNI call returns. + drop(throwable); + if res == 0 { Ok(()) } else { @@ -250,17 +454,31 @@ impl<'a> JNIEnv<'a> { /// message. /// /// # Example - /// ```rust,ignore - /// let _ = env.throw_new("java/lang/Exception", "something bad happened"); + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv}; + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { + /// env.throw_new("java/lang/Exception", "something bad happened")?; + /// # Ok(()) + /// # } /// ``` - pub fn throw_new<'c, S, T>(&self, class: T, msg: S) -> Result<()> + pub fn throw_new<'other_local, S, T>(&mut self, class: T, msg: S) -> Result<()> where S: Into<JNIString>, - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, { let class = class.lookup(self)?; let msg = msg.into(); - let res: i32 = jni_unchecked!(self.internal, ThrowNew, class.into_raw(), msg.as_ptr()); + let res: i32 = jni_unchecked!( + self.internal, + ThrowNew, + class.as_ref().as_raw(), + msg.as_ptr() + ); + + // Ensure that `class` isn't dropped before the JNI call returns. + drop(class); + if res == 0 { Ok(()) } else { @@ -271,7 +489,7 @@ impl<'a> JNIEnv<'a> { /// Check whether or not an exception is currently in the process of being /// thrown. An exception is in this state from the time it gets thrown and /// not caught in a java function until `exception_clear` is called. - pub fn exception_occurred(&self) -> Result<JThrowable<'a>> { + pub fn exception_occurred(&mut self) -> Result<JThrowable<'local>> { let throwable = jni_unchecked!(self.internal, ExceptionOccurred); Ok(unsafe { JThrowable::from_raw(throwable) }) } @@ -313,13 +531,18 @@ impl<'a> JNIEnv<'a> { /// Create a new instance of a direct java.nio.ByteBuffer /// /// # Example - /// ```rust,ignore + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv}; + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { /// let buf = vec![0; 1024 * 1024]; /// let (addr, len) = { // (use buf.into_raw_parts() on nightly) /// let buf = buf.leak(); /// (buf.as_mut_ptr(), buf.len()) /// }; - /// let direct_buffer = unsafe { env.new_direct_byte_buffer(addr, len) }; + /// let direct_buffer = unsafe { env.new_direct_byte_buffer(addr, len) }?; + /// # Ok(()) + /// # } /// ``` /// /// # Safety @@ -330,10 +553,10 @@ impl<'a> JNIEnv<'a> { /// `ByteBuffer`. The JVM may maintain references to the `ByteBuffer` beyond the lifetime /// of this `JNIEnv`. pub unsafe fn new_direct_byte_buffer( - &self, + &mut self, data: *mut u8, len: usize, - ) -> Result<JByteBuffer<'a>> { + ) -> Result<JByteBuffer<'local>> { non_null!(data, "new_direct_byte_buffer data argument"); let obj = jni_non_null_call!( self.internal, @@ -346,9 +569,9 @@ impl<'a> JNIEnv<'a> { /// Returns the starting address of the memory of the direct /// java.nio.ByteBuffer. - pub fn get_direct_buffer_address(&self, buf: JByteBuffer) -> Result<*mut u8> { + pub fn get_direct_buffer_address(&self, buf: &JByteBuffer) -> Result<*mut u8> { non_null!(buf, "get_direct_buffer_address argument"); - let ptr = jni_unchecked!(self.internal, GetDirectBufferAddress, buf.into_raw()); + let ptr = jni_unchecked!(self.internal, GetDirectBufferAddress, buf.as_raw()); non_null!(ptr, "get_direct_buffer_address return value"); Ok(ptr as _) } @@ -362,9 +585,9 @@ impl<'a> JNIEnv<'a> { /// API). /// /// The terminology is simply kept from the original JNI API (`GetDirectBufferCapacity`). - pub fn get_direct_buffer_capacity(&self, buf: JByteBuffer) -> Result<usize> { + pub fn get_direct_buffer_capacity(&self, buf: &JByteBuffer) -> Result<usize> { non_null!(buf, "get_direct_buffer_capacity argument"); - let capacity = jni_unchecked!(self.internal, GetDirectBufferCapacity, buf.into_raw()); + let capacity = jni_unchecked!(self.internal, GetDirectBufferCapacity, buf.as_raw()); match capacity { -1 => Err(Error::JniCall(JniError::Unknown)), _ => Ok(capacity as usize), @@ -374,15 +597,54 @@ impl<'a> JNIEnv<'a> { /// Turns an object into a global ref. This has the benefit of removing the /// lifetime bounds since it's guaranteed to not get GC'd by java. It /// releases the GC pin upon being dropped. - pub fn new_global_ref<O>(&self, obj: O) -> Result<GlobalRef> + pub fn new_global_ref<'other_local, O>(&self, obj: O) -> Result<GlobalRef> where - O: Into<JObject<'a>>, + O: AsRef<JObject<'other_local>>, { - let new_ref = jni_unchecked!(self.internal, NewGlobalRef, obj.into().into_raw()); - let global = unsafe { GlobalRef::from_raw(self.get_java_vm()?, new_ref) }; + let jvm = self.get_java_vm()?; + let new_ref = jni_unchecked!(self.internal, NewGlobalRef, obj.as_ref().as_raw()); + let global = unsafe { GlobalRef::from_raw(jvm, new_ref) }; Ok(global) } + /// Creates a new [weak global reference][WeakRef]. + /// + /// If the provided object is null, this method returns `None`. Otherwise, it returns `Some` + /// containing the new weak global reference. + pub fn new_weak_ref<'other_local, O>(&self, obj: O) -> Result<Option<WeakRef>> + where + O: AsRef<JObject<'other_local>>, + { + // We need the `JavaVM` in order to construct a `WeakRef` below. But because `get_java_vm` + // is fallible, we need to call it before doing anything else, so that we don't leak + // memory if it fails. + let vm = self.get_java_vm()?; + + let obj = obj.as_ref().as_raw(); + + // Check if the pointer is null *before* calling `NewWeakGlobalRef`. + // + // This avoids a bug in some JVM implementations which, contrary to the JNI specification, + // will throw `java.lang.OutOfMemoryError: C heap space` from `NewWeakGlobalRef` if it is + // passed a null pointer. (The specification says it will return a null pointer in that + // situation, not throw an exception.) + if obj.is_null() { + return Ok(None); + } + + let weak: sys::jweak = jni_non_void_call!(self.internal, NewWeakGlobalRef, obj); + + // Check if the pointer returned by `NewWeakGlobalRef` is null. This can happen if `obj` is + // itself a weak reference that was already garbage collected. + if weak.is_null() { + return Ok(None); + } + + let weak = unsafe { WeakRef::from_raw(vm, weak) }; + + Ok(Some(weak)) + } + /// Create a new local reference to an object. /// /// Specifically, this calls the JNI function [`NewLocalRef`], which creates a reference in the @@ -400,15 +662,15 @@ impl<'a> JNIEnv<'a> { /// /// # Lifetimes /// - /// `'a` is the lifetime of this `JNIEnv`. This method creates a new local reference with - /// lifetime `'a`. + /// `'local` is the lifetime of the local reference frame that this `JNIEnv` belongs to. This + /// method creates a new local reference in that frame, with lifetime `'local`. /// - /// `'b` is the lifetime of the original reference. It can be any valid lifetime, even one that - /// `'a` outlives or vice versa. + /// `'other_local` is the lifetime of the original reference's frame. It can be any valid + /// lifetime, even one that `'local` outlives or vice versa. /// - /// Think of `'a` as meaning `'new` and `'b` as meaning `'original`. (It is unfortunately not - /// possible to actually give these names to the two lifetimes because `'a` is a parameter to - /// the `JNIEnv` type, not a parameter to this method.) + /// Think of `'local` as meaning `'new` and `'other_local` as meaning `'original`. (It is + /// unfortunately not possible to actually give these names to the two lifetimes because + /// `'local` is a parameter to the `JNIEnv` type, not a parameter to this method.) /// /// # Example /// @@ -442,7 +704,7 @@ impl<'a> JNIEnv<'a> { /// /// If this is an `ExampleError::Exception`, then this extracts the enclosed Java /// /// exception object. Otherwise, a new exception object is created to represent this /// /// error. - /// fn extract_throwable(self, env: JNIEnv) -> jni::errors::Result<JThrowable> { + /// fn extract_throwable<'local>(self, env: &mut JNIEnv<'local>) -> jni::errors::Result<JThrowable<'local>> { /// let throwable: JObject = match self { /// ExampleError::Exception(exception) => { /// // The error was caused by a Java exception. @@ -452,18 +714,20 @@ impl<'a> JNIEnv<'a> { /// // `new_local_ref` to create a local reference that will outlive the /// // `GlobalRef`. /// - /// env.new_local_ref(exception.as_obj())? + /// env.new_local_ref(&exception)? /// } /// /// ExampleError::Other(error) => { /// // The error was caused by something that happened in Rust code. Create a /// // new `java.lang.Error` to represent it. /// + /// let error_string = env.new_string(error.to_string())?; + /// /// env.new_object( /// "java/lang/Error", /// "(Ljava/lang/String;)V", /// &[ - /// env.new_string(error.to_string())?.into(), + /// (&error_string).into(), /// ], /// )? /// } @@ -475,11 +739,11 @@ impl<'a> JNIEnv<'a> { /// ``` /// /// [`NewLocalRef`]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/functions.html#newlocalref - pub fn new_local_ref<'b, O>(&self, obj: O) -> Result<JObject<'a>> + pub fn new_local_ref<'other_local, O>(&self, obj: O) -> Result<JObject<'local>> where - O: Into<JObject<'b>>, + O: AsRef<JObject<'other_local>>, { - let local = jni_unchecked!(self.internal, NewLocalRef, obj.into().into_raw()); + let local = jni_unchecked!(self.internal, NewLocalRef, obj.as_ref().as_raw()); Ok(unsafe { JObject::from_raw(local) }) } @@ -489,11 +753,11 @@ impl<'a> JNIEnv<'a> { /// can be more convenient when you create a _bounded_ number of local references /// but cannot rely on automatic de-allocation (e.g., in case of recursion, deep call stacks, /// [permanently-attached](struct.JavaVM.html#attaching-native-threads) native threads, etc.). - pub fn auto_local<'b, O>(&'b self, obj: O) -> AutoLocal<'a, 'b> + pub fn auto_local<O>(&self, obj: O) -> AutoLocal<'local, O> where - O: Into<JObject<'a>>, + O: Into<JObject<'local>>, { - AutoLocal::new(self, obj.into()) + AutoLocal::new(obj, self) } /// Deletes the local reference. @@ -514,8 +778,17 @@ impl<'a> JNIEnv<'a> { /// /// In most cases it is better to use `AutoLocal` (see `auto_local` method) /// or `with_local_frame` instead of direct `delete_local_ref` calls. - pub fn delete_local_ref(&self, obj: JObject) -> Result<()> { - jni_unchecked!(self.internal, DeleteLocalRef, obj.into_raw()); + /// + /// `obj` can be a mutable borrow of a local reference (such as + /// `&mut JObject`) instead of the local reference itself (such as + /// `JObject`). In this case, the local reference will still exist after + /// this method returns, but it will be null. + pub fn delete_local_ref<'other_local, O>(&self, obj: O) -> Result<()> + where + O: Into<JObject<'other_local>>, + { + let raw = obj.into().into_raw(); + jni_unchecked!(self.internal, DeleteLocalRef, raw); Ok(()) } @@ -524,8 +797,9 @@ impl<'a> JNIEnv<'a> { /// /// Returns `Err` on failure, with a pending `OutOfMemoryError`. /// - /// Prefer to use [`with_local_frame`](struct.JNIEnv.html#method.with_local_frame) instead of - /// direct `push_local_frame`/`pop_local_frame` calls. + /// Prefer to use + /// [`with_local_frame`](struct.JNIEnv.html#method.with_local_frame) + /// instead of direct `push_local_frame`/`pop_local_frame` calls. /// /// See also [`auto_local`](struct.JNIEnv.html#method.auto_local) method /// and `AutoLocal` type — that approach can be more convenient in loops. @@ -540,71 +814,125 @@ impl<'a> JNIEnv<'a> { /// which is returned from this function and remains valid. /// /// The resulting `JObject` will be `NULL` iff `result` is `NULL`. - pub fn pop_local_frame(&self, result: JObject<'a>) -> Result<JObject<'a>> { + /// + /// This method allows direct control of local frames, but it can cause + /// undefined behavior and is therefore unsafe. Prefer + /// [`JNIEnv::with_local_frame`] instead. + /// + /// # Safety + /// + /// Any local references created after the most recent call to + /// [`JNIEnv::push_local_frame`] (or the underlying JNI function) must not + /// be used after calling this method. + pub unsafe fn pop_local_frame(&self, result: &JObject) -> Result<JObject<'local>> { // This method is safe to call in case of pending exceptions (see chapter 2 of the spec) - Ok(unsafe { - JObject::from_raw(jni_unchecked!( - self.internal, - PopLocalFrame, - result.into_raw() - )) - }) + Ok(JObject::from_raw(jni_unchecked!( + self.internal, + PopLocalFrame, + result.as_raw() + ))) } /// Executes the given function in a new local reference frame, in which at least a given number /// of references can be created. Once this method returns, all references allocated - /// in the frame are freed, except the one that the function returns, which remains valid. + /// in the frame are freed. /// - /// If _no_ new frames can be allocated, returns `Err` with a pending `OutOfMemoryError`. + /// If a frame can't be allocated with the requested capacity for local + /// references, returns `Err` with a pending `OutOfMemoryError`. /// - /// See also [`auto_local`](struct.JNIEnv.html#method.auto_local) method - /// and `AutoLocal` type - that approach can be more convenient in loops. - pub fn with_local_frame<F>(&self, capacity: i32, f: F) -> Result<JObject<'a>> + /// Since local references created within this frame won't be accessible to the calling + /// frame then if you need to pass an object back to the caller then you can do that via a + /// [`GlobalRef`] / [`Self::make_global`]. + pub fn with_local_frame<F, T, E>(&mut self, capacity: i32, f: F) -> std::result::Result<T, E> where - F: FnOnce() -> Result<JObject<'a>>, + F: FnOnce(&mut JNIEnv) -> std::result::Result<T, E>, + E: From<Error>, { - self.push_local_frame(capacity)?; - let res = f(); - match res { - Ok(obj) => self.pop_local_frame(obj), - Err(e) => { - self.pop_local_frame(JObject::null())?; - Err(e) + unsafe { + self.push_local_frame(capacity)?; + let ret = f(self); + self.pop_local_frame(&JObject::null())?; + ret + } + } + + /// Executes the given function in a new local reference frame, in which at least a given number + /// of references can be created. Once this method returns, all references allocated + /// in the frame are freed, except the one that the function returns, which remains valid. + /// + /// If a frame can't be allocated with the requested capacity for local + /// references, returns `Err` with a pending `OutOfMemoryError`. + /// + /// Since the low-level JNI interface has support for passing back a single local reference + /// from a local frame as special-case optimization, this alternative to `with_local_frame` + /// exposes that capability to return a local reference without needing to create a + /// temporary [`GlobalRef`]. + pub fn with_local_frame_returning_local<F, E>( + &mut self, + capacity: i32, + f: F, + ) -> std::result::Result<JObject<'local>, E> + where + F: for<'new_local> FnOnce( + &mut JNIEnv<'new_local>, + ) -> std::result::Result<JObject<'new_local>, E>, + E: From<Error>, + { + unsafe { + self.push_local_frame(capacity)?; + match f(self) { + Ok(obj) => { + let obj = self.pop_local_frame(&obj)?; + Ok(obj) + } + Err(err) => { + self.pop_local_frame(&JObject::null())?; + Err(err) + } } } } /// Allocates a new object from a class descriptor without running a /// constructor. - pub fn alloc_object<'c, T>(&self, class: T) -> Result<JObject<'a>> + pub fn alloc_object<'other_local, T>(&mut self, class: T) -> Result<JObject<'local>> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, { let class = class.lookup(self)?; - let obj = jni_non_null_call!(self.internal, AllocObject, class.into_raw()); + let obj = jni_non_null_call!(self.internal, AllocObject, class.as_ref().as_raw()); + + // Ensure that `class` isn't dropped before the JNI call returns. + drop(class); + Ok(unsafe { JObject::from_raw(obj) }) } /// Common functionality for finding methods. #[allow(clippy::redundant_closure_call)] - fn get_method_id_base<'c, T, U, V, C, R>( - &self, + fn get_method_id_base<'other_local_1, T, U, V, C, R>( + &mut self, class: T, name: U, sig: V, get_method: C, ) -> Result<R> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local_1>>, U: Into<JNIString>, V: Into<JNIString>, - C: for<'d> Fn(&JClass<'d>, &JNIString, &JNIString) -> Result<R>, + C: for<'other_local_2> Fn( + &mut Self, + &JClass<'other_local_2>, + &JNIString, + &JNIString, + ) -> Result<R>, { let class = class.lookup(self)?; let ffi_name = name.into(); let sig = sig.into(); - let res: Result<R> = catch!({ get_method(&class, &ffi_name, &sig) }); + let res: Result<R> = catch!({ get_method(self, class.as_ref(), &ffi_name, &sig) }); match res { Ok(m) => Ok(m), @@ -623,21 +951,31 @@ impl<'a> JNIEnv<'a> { /// signature. /// /// # Example - /// ```rust,ignore + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv, objects::JMethodID}; + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { /// let method_id: JMethodID = - /// env.get_method_id("java/lang/String", "substring", "(II)Ljava/lang/String;"); + /// env.get_method_id("java/lang/String", "substring", "(II)Ljava/lang/String;")?; + /// # Ok(()) + /// # } /// ``` - pub fn get_method_id<'c, T, U, V>(&self, class: T, name: U, sig: V) -> Result<JMethodID> + pub fn get_method_id<'other_local, T, U, V>( + &mut self, + class: T, + name: U, + sig: V, + ) -> Result<JMethodID> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, U: Into<JNIString>, V: Into<JNIString>, { - self.get_method_id_base(class, name, sig, |class, name, sig| { + self.get_method_id_base(class, name, sig, |env, class, name, sig| { let method_id = jni_non_null_call!( - self.internal, + env.internal, GetMethodID, - class.into_raw(), + class.as_raw(), name.as_ptr(), sig.as_ptr() ); @@ -649,26 +987,31 @@ impl<'a> JNIEnv<'a> { /// signature. /// /// # Example - /// ```rust,ignore - /// let method_id: JMethodID = - /// env.get_static_method_id("java/lang/String", "valueOf", "(I)Ljava/lang/String;"); + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv, objects::JStaticMethodID}; + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { + /// let method_id: JStaticMethodID = + /// env.get_static_method_id("java/lang/String", "valueOf", "(I)Ljava/lang/String;")?; + /// # Ok(()) + /// # } /// ``` - pub fn get_static_method_id<'c, T, U, V>( - &self, + pub fn get_static_method_id<'other_local, T, U, V>( + &mut self, class: T, name: U, sig: V, ) -> Result<JStaticMethodID> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, U: Into<JNIString>, V: Into<JNIString>, { - self.get_method_id_base(class, name, sig, |class, name, sig| { + self.get_method_id_base(class, name, sig, |env, class, name, sig| { let method_id = jni_non_null_call!( - self.internal, + env.internal, GetStaticMethodID, - class.into_raw(), + class.as_raw(), name.as_ptr(), sig.as_ptr() ); @@ -679,12 +1022,22 @@ impl<'a> JNIEnv<'a> { /// Look up the field ID for a class/name/type combination. /// /// # Example - /// ```rust,ignore - /// let field_id = env.get_field_id("com/my/Class", "intField", "I"); + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv, objects::JFieldID}; + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { + /// let field_id: JFieldID = env.get_field_id("com/my/Class", "intField", "I")?; + /// # Ok(()) + /// # } /// ``` - pub fn get_field_id<'c, T, U, V>(&self, class: T, name: U, sig: V) -> Result<JFieldID> + pub fn get_field_id<'other_local, T, U, V>( + &mut self, + class: T, + name: U, + sig: V, + ) -> Result<JFieldID> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, U: Into<JNIString>, V: Into<JNIString>, { @@ -696,7 +1049,7 @@ impl<'a> JNIEnv<'a> { let field_id = jni_non_null_call!( self.internal, GetFieldID, - class.into_raw(), + class.as_ref().as_raw(), ffi_name.as_ptr(), ffi_sig.as_ptr() ); @@ -719,17 +1072,22 @@ impl<'a> JNIEnv<'a> { /// Look up the static field ID for a class/name/type combination. /// /// # Example - /// ```rust,ignore - /// let field_id = env.get_static_field_id("com/my/Class", "intField", "I"); + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv, objects::JStaticFieldID}; + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { + /// let field_id: JStaticFieldID = env.get_static_field_id("com/my/Class", "intField", "I")?; + /// # Ok(()) + /// # } /// ``` - pub fn get_static_field_id<'c, T, U, V>( - &self, + pub fn get_static_field_id<'other_local, T, U, V>( + &mut self, class: T, name: U, sig: V, ) -> Result<JStaticFieldID> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, U: Into<JNIString>, V: Into<JNIString>, { @@ -741,13 +1099,16 @@ impl<'a> JNIEnv<'a> { let field_id = jni_non_null_call!( self.internal, GetStaticFieldID, - class.into_raw(), + class.as_ref().as_raw(), ffi_name.as_ptr(), ffi_sig.as_ptr() ); Ok(unsafe { JStaticFieldID::from_raw(field_id) }) }); + // Ensure that `class` isn't dropped before the JNI call returns. + drop(class); + match res { Ok(m) => Ok(m), Err(e) => match e { @@ -762,19 +1123,19 @@ impl<'a> JNIEnv<'a> { } /// Get the class for an object. - pub fn get_object_class<'b, O>(&self, obj: O) -> Result<JClass<'a>> + pub fn get_object_class<'other_local, O>(&self, obj: O) -> Result<JClass<'local>> where - O: Into<JObject<'b>>, + O: AsRef<JObject<'other_local>>, { - let obj = obj.into(); + let obj = obj.as_ref(); non_null!(obj, "get_object_class"); - Ok(unsafe { - JClass::from_raw(jni_unchecked!( + unsafe { + Ok(JClass::from_raw(jni_unchecked!( self.internal, GetObjectClass, - obj.into_raw() - )) - }) + obj.as_raw() + ))) + } } /// Call a static method in an unsafe manner. This does nothing to check @@ -783,31 +1144,36 @@ impl<'a> JNIEnv<'a> { /// /// Under the hood, this simply calls the `CallStatic<Type>MethodA` method /// with the provided arguments. - pub fn call_static_method_unchecked<'c, T, U>( - &self, + /// + /// # Safety + /// + /// The provided JMethodID must be valid, and match the types and number of arguments, and return type. + /// If these are incorrect, the JVM may crash. The JMethodID must also match the passed type. + pub unsafe fn call_static_method_unchecked<'other_local, T, U>( + &mut self, class: T, method_id: U, ret: ReturnType, args: &[jvalue], - ) -> Result<JValue<'a>> + ) -> Result<JValueOwned<'local>> where - T: Desc<'a, JClass<'c>>, - U: Desc<'a, JStaticMethodID>, + T: Desc<'local, JClass<'other_local>>, + U: Desc<'local, JStaticMethodID>, { let class = class.lookup(self)?; - let method_id = method_id.lookup(self)?.into_raw(); + let method_id = method_id.lookup(self)?.as_ref().into_raw(); - let class = class.into_raw(); + let class_raw = class.as_ref().as_raw(); let jni_args = args.as_ptr(); // TODO clean this up - Ok(match ret { + let ret = Ok(match ret { ReturnType::Object | ReturnType::Array => { let obj = jni_non_void_call!( self.internal, CallStaticObjectMethodA, - class, + class_raw, method_id, jni_args ); @@ -818,7 +1184,7 @@ impl<'a> JNIEnv<'a> { Primitive::Boolean => jni_non_void_call!( self.internal, CallStaticBooleanMethodA, - class, + class_raw, method_id, jni_args ) @@ -826,7 +1192,7 @@ impl<'a> JNIEnv<'a> { Primitive::Char => jni_non_void_call!( self.internal, CallStaticCharMethodA, - class, + class_raw, method_id, jni_args ) @@ -834,7 +1200,7 @@ impl<'a> JNIEnv<'a> { Primitive::Short => jni_non_void_call!( self.internal, CallStaticShortMethodA, - class, + class_raw, method_id, jni_args ) @@ -842,7 +1208,7 @@ impl<'a> JNIEnv<'a> { Primitive::Int => jni_non_void_call!( self.internal, CallStaticIntMethodA, - class, + class_raw, method_id, jni_args ) @@ -850,7 +1216,7 @@ impl<'a> JNIEnv<'a> { Primitive::Long => jni_non_void_call!( self.internal, CallStaticLongMethodA, - class, + class_raw, method_id, jni_args ) @@ -858,7 +1224,7 @@ impl<'a> JNIEnv<'a> { Primitive::Float => jni_non_void_call!( self.internal, CallStaticFloatMethodA, - class, + class_raw, method_id, jni_args ) @@ -866,7 +1232,7 @@ impl<'a> JNIEnv<'a> { Primitive::Double => jni_non_void_call!( self.internal, CallStaticDoubleMethodA, - class, + class_raw, method_id, jni_args ) @@ -874,7 +1240,7 @@ impl<'a> JNIEnv<'a> { Primitive::Byte => jni_non_void_call!( self.internal, CallStaticByteMethodA, - class, + class_raw, method_id, jni_args ) @@ -883,14 +1249,19 @@ impl<'a> JNIEnv<'a> { jni_void_call!( self.internal, CallStaticVoidMethodA, - class, + class_raw, method_id, jni_args ); - return Ok(JValue::Void); + return Ok(JValueOwned::Void); } }, // JavaType::Primitive - }) // match parsed.ret + }); // match parsed.ret + + // Ensure that `class` isn't dropped before the JNI call returns. + drop(class); + + ret } /// Call an object method in an unsafe manner. This does nothing to check @@ -899,20 +1270,25 @@ impl<'a> JNIEnv<'a> { /// /// Under the hood, this simply calls the `Call<Type>MethodA` method with /// the provided arguments. - pub fn call_method_unchecked<O, T>( - &self, + /// + /// # Safety + /// + /// The provided JMethodID must be valid, and match the types and number of arguments, and return type. + /// If these are incorrect, the JVM may crash. The JMethodID must also match the passed type. + pub unsafe fn call_method_unchecked<'other_local, O, T>( + &mut self, obj: O, method_id: T, ret: ReturnType, args: &[jvalue], - ) -> Result<JValue<'a>> + ) -> Result<JValueOwned<'local>> where - O: Into<JObject<'a>>, - T: Desc<'a, JMethodID>, + O: AsRef<JObject<'other_local>>, + T: Desc<'local, JMethodID>, { - let method_id = method_id.lookup(self)?.into_raw(); + let method_id = method_id.lookup(self)?.as_ref().into_raw(); - let obj = obj.into().into_raw(); + let obj = obj.as_ref().as_raw(); let jni_args = args.as_ptr(); @@ -959,7 +1335,7 @@ impl<'a> JNIEnv<'a> { } Primitive::Void => { jni_void_call!(self.internal, CallVoidMethodA, obj, method_id, jni_args); - return Ok(JValue::Void); + return Ok(JValueOwned::Void); } }, // JavaType::Primitive }) // match parsed.ret @@ -972,24 +1348,25 @@ impl<'a> JNIEnv<'a> { /// type /// * Looks up the JClass for the given object. /// * Looks up the JMethodID for the class/name/signature combination - /// * Ensures that the number of args matches the signature + /// * Ensures that the number/types of args matches the signature + /// * Cannot check an object's type - but primitive types are matched against each other (including Object) /// * Calls `call_method_unchecked` with the verified safe arguments. /// - /// Note: this may cause a java exception if the arguments are the wrong + /// Note: this may cause a Java exception if the arguments are the wrong /// type, in addition to if the method itself throws. - pub fn call_method<O, S, T>( - &self, + pub fn call_method<'other_local, O, S, T>( + &mut self, obj: O, name: S, sig: T, args: &[JValue], - ) -> Result<JValue<'a>> + ) -> Result<JValueOwned<'local>> where - O: Into<JObject<'a>>, + O: AsRef<JObject<'other_local>>, S: Into<JNIString>, T: Into<JNIString> + AsRef<str>, { - let obj = obj.into(); + let obj = obj.as_ref(); non_null!(obj, "call_method obj argument"); // parse the signature @@ -998,10 +1375,30 @@ impl<'a> JNIEnv<'a> { return Err(Error::InvalidArgList(parsed)); } + // check arguments types + let base_types_match = parsed + .args + .iter() + .zip(args.iter()) + .all(|(exp, act)| match exp { + JavaType::Primitive(p) => act.primitive_type() == Some(*p), + JavaType::Object(_) | JavaType::Array(_) => act.primitive_type().is_none(), + JavaType::Method(_) => { + unreachable!("JavaType::Method(_) should not come from parsing a method sig") + } + }); + if !base_types_match { + return Err(Error::InvalidArgList(parsed)); + } + let class = self.auto_local(self.get_object_class(obj)?); - let args: Vec<jvalue> = args.iter().map(|v| v.to_jni()).collect(); - self.call_method_unchecked(obj, (&class, name, sig), parsed.ret, &args) + let args: Vec<jvalue> = args.iter().map(|v| v.as_jni()).collect(); + + // SAFETY: We've obtained the method_id above, so it is valid for this class. + // We've also validated the argument counts and types using the same type signature + // we fetched the original method ID from. + unsafe { self.call_method_unchecked(obj, (&class, name, sig), parsed.ret, &args) } } /// Calls a static method safely. This comes with a number of @@ -1010,20 +1407,21 @@ impl<'a> JNIEnv<'a> { /// * Parses the type signature to find the number of arguments and return /// type /// * Looks up the JMethodID for the class/name/signature combination - /// * Ensures that the number of args matches the signature + /// * Ensures that the number/types of args matches the signature + /// * Cannot check an object's type - but primitive types are matched against each other (including Object) /// * Calls `call_method_unchecked` with the verified safe arguments. /// - /// Note: this may cause a java exception if the arguments are the wrong + /// Note: this may cause a Java exception if the arguments are the wrong /// type, in addition to if the method itself throws. - pub fn call_static_method<'c, T, U, V>( - &self, + pub fn call_static_method<'other_local, T, U, V>( + &mut self, class: T, name: U, sig: V, args: &[JValue], - ) -> Result<JValue<'a>> + ) -> Result<JValueOwned<'local>> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, U: Into<JNIString>, V: Into<JNIString> + AsRef<str>, { @@ -1032,76 +1430,134 @@ impl<'a> JNIEnv<'a> { return Err(Error::InvalidArgList(parsed)); } - // go ahead and look up the class since it's already Copy, - // and we'll need that for the next call. + // check arguments types + let base_types_match = parsed + .args + .iter() + .zip(args.iter()) + .all(|(exp, act)| match exp { + JavaType::Primitive(p) => act.primitive_type() == Some(*p), + JavaType::Object(_) | JavaType::Array(_) => act.primitive_type().is_none(), + JavaType::Method(_) => { + unreachable!("JavaType::Method(_) should not come from parsing a method sig") + } + }); + if !base_types_match { + return Err(Error::InvalidArgList(parsed)); + } + + // go ahead and look up the class since we'll need that for the next call. let class = class.lookup(self)?; + let class = class.as_ref(); + + let args: Vec<jvalue> = args.iter().map(|v| v.as_jni()).collect(); - let args: Vec<jvalue> = args.iter().map(|v| v.to_jni()).collect(); - self.call_static_method_unchecked(class, (class, name, sig), parsed.ret, &args) + // SAFETY: We've obtained the method_id above, so it is valid for this class. + // We've also validated the argument counts and types using the same type signature + // we fetched the original method ID from. + unsafe { self.call_static_method_unchecked(class, (class, name, sig), parsed.ret, &args) } } /// Create a new object using a constructor. This is done safely using /// checks similar to those in `call_static_method`. - pub fn new_object<'c, T, U>( - &self, + pub fn new_object<'other_local, T, U>( + &mut self, class: T, ctor_sig: U, ctor_args: &[JValue], - ) -> Result<JObject<'a>> + ) -> Result<JObject<'local>> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, U: Into<JNIString> + AsRef<str>, { // parse the signature let parsed = TypeSignature::from_str(&ctor_sig)?; + // check arguments length if parsed.args.len() != ctor_args.len() { return Err(Error::InvalidArgList(parsed)); } + // check arguments types + let base_types_match = + parsed + .args + .iter() + .zip(ctor_args.iter()) + .all(|(exp, act)| match exp { + JavaType::Primitive(p) => act.primitive_type() == Some(*p), + JavaType::Object(_) | JavaType::Array(_) => act.primitive_type().is_none(), + JavaType::Method(_) => { + unreachable!("JavaType::Method(_) should not come from parsing a ctor sig") + } + }); + if !base_types_match { + return Err(Error::InvalidArgList(parsed)); + } + + // check return value if parsed.ret != ReturnType::Primitive(Primitive::Void) { return Err(Error::InvalidCtorReturn); } // build strings let class = class.lookup(self)?; + let class = class.as_ref(); - let method_id: JMethodID = (class, ctor_sig).lookup(self)?; + let method_id: JMethodID = Desc::<JMethodID>::lookup((class, ctor_sig), self)?; - self.new_object_unchecked(class, method_id, ctor_args) + let ctor_args: Vec<jvalue> = ctor_args.iter().map(|v| v.as_jni()).collect(); + // SAFETY: We've obtained the method_id above, so it is valid for this class. + // We've also validated the argument counts and types using the same type signature + // we fetched the original method ID from. + unsafe { self.new_object_unchecked(class, method_id, &ctor_args) } } /// Create a new object using a constructor. Arguments aren't checked - /// because - /// of the `JMethodID` usage. - pub fn new_object_unchecked<'c, T>( - &self, + /// because of the `JMethodID` usage. + /// + /// # Safety + /// + /// The provided JMethodID must be valid, and match the types and number of arguments, as well as return type + /// (always an Object for a constructor). If these are incorrect, the JVM may crash. The JMethodID must also match + /// the passed type. + pub unsafe fn new_object_unchecked<'other_local, T>( + &mut self, class: T, ctor_id: JMethodID, - ctor_args: &[JValue], - ) -> Result<JObject<'a>> + ctor_args: &[jvalue], + ) -> Result<JObject<'local>> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, { let class = class.lookup(self)?; - let jni_args: Vec<jvalue> = ctor_args.iter().map(|v| v.to_jni()).collect(); - let jni_args = jni_args.as_ptr(); + let jni_args = ctor_args.as_ptr(); let obj = jni_non_null_call!( self.internal, NewObjectA, - class.into_raw(), + class.as_ref().as_raw(), ctor_id.into_raw(), jni_args ); + + // Ensure that `class` isn't dropped before the JNI call returns. + drop(class); + Ok(unsafe { JObject::from_raw(obj) }) } /// Cast a JObject to a `JList`. This won't throw exceptions or return errors /// in the event that the object isn't actually a list, but the methods on /// the resulting map object will. - pub fn get_list(&self, obj: JObject<'a>) -> Result<JList<'a, '_>> { + pub fn get_list<'other_local_1, 'obj_ref>( + &mut self, + obj: &'obj_ref JObject<'other_local_1>, + ) -> Result<JList<'local, 'other_local_1, 'obj_ref>> + where + 'other_local_1: 'obj_ref, + { non_null!(obj, "get_list obj argument"); JList::from_env(self, obj) } @@ -1109,78 +1565,96 @@ impl<'a> JNIEnv<'a> { /// Cast a JObject to a JMap. This won't throw exceptions or return errors /// in the event that the object isn't actually a map, but the methods on /// the resulting map object will. - pub fn get_map(&self, obj: JObject<'a>) -> Result<JMap<'a, '_>> { + pub fn get_map<'other_local_1, 'obj_ref>( + &mut self, + obj: &'obj_ref JObject<'other_local_1>, + ) -> Result<JMap<'local, 'other_local_1, 'obj_ref>> + where + 'other_local_1: 'obj_ref, + { non_null!(obj, "get_map obj argument"); JMap::from_env(self, obj) } - /// Get a JavaStr from a JString. This allows conversions from java string + /// Get a [`JavaStr`] from a [`JString`]. This allows conversions from java string /// objects to rust strings. /// - /// This entails a call to `GetStringUTFChars` and only decodes java's - /// modified UTF-8 format on conversion to a rust-compatible string. + /// This only entails calling `GetStringUTFChars`, which will return a [`JavaStr`] in Java's + /// [Modified UTF-8](https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8) + /// format. + /// + /// This doesn't automatically decode Java's modified UTF-8 format but you + /// can use `.into()` to convert the returned [`JavaStr`] into a Rust [`String`]. /// - /// # Panics + /// # Safety /// - /// This call panics when given an Object that is not a java.lang.String - pub fn get_string(&self, obj: JString<'a>) -> Result<JavaStr<'a, '_>> { + /// The caller must guarantee that the Object passed in is an instance of `java.lang.String`, + /// passing in anything else will lead to undefined behaviour (The JNI implementation + /// is likely to crash or abort the process). + /// + /// # Errors + /// + /// Returns an error if `obj` is `null` + pub unsafe fn get_string_unchecked<'other_local: 'obj_ref, 'obj_ref>( + &self, + obj: &'obj_ref JString<'other_local>, + ) -> Result<JavaStr<'local, 'other_local, 'obj_ref>> { non_null!(obj, "get_string obj argument"); JavaStr::from_env(self, obj) } - /// Get a pointer to the character array beneath a JString. + /// Get a [`JavaStr`] from a [`JString`]. This allows conversions from java string + /// objects to rust strings. /// - /// Array contains Java's modified UTF-8. + /// This entails checking that the given object is a `java.lang.String` and + /// calling `GetStringUTFChars`, which will return a [`JavaStr`] in Java's + /// [Modified UTF-8](https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8) + /// format. /// - /// # Attention - /// This will leak memory if `release_string_utf_chars` is never called. - pub fn get_string_utf_chars(&self, obj: JString) -> Result<*const c_char> { - non_null!(obj, "get_string_utf_chars obj argument"); - let ptr: *const c_char = jni_non_null_call!( - self.internal, - GetStringUTFChars, - obj.into_raw(), - ::std::ptr::null::<jboolean>() as *mut jboolean - ); - Ok(ptr) - } - - /// Unpin the array returned by `get_string_utf_chars`. + /// This doesn't automatically decode Java's modified UTF-8 format but you + /// can use `.into()` to convert the returned [`JavaStr`] into a Rust [`String`]. /// - /// # Safety + /// # Performance /// - /// The behaviour is undefined if the array isn't returned by the `get_string_utf_chars` - /// function. + /// This function has a large relative performance impact compared to + /// [Self::get_string_unchecked]. For example it may be about five times + /// slower than `get_string_unchecked` for very short string. This + /// performance penalty comes from the extra validation performed by this + /// function. If and only if you can guarantee that your `obj` is of + /// `java.lang.String`, use [Self::get_string_unchecked]. /// - /// # Examples + /// # Errors /// - /// ```no_run - /// # let env = unsafe { jni::JNIEnv::from_raw(std::ptr::null_mut()).unwrap() }; - /// let s = env.new_string("test").unwrap(); - /// let array = env.get_string_utf_chars(s).unwrap(); - /// unsafe { env.release_string_utf_chars(s, array).unwrap() }; - /// ``` - #[allow(unused_unsafe)] - pub unsafe fn release_string_utf_chars(&self, obj: JString, arr: *const c_char) -> Result<()> { - non_null!(obj, "release_string_utf_chars obj argument"); - // This method is safe to call in case of pending exceptions (see the chapter 2 of the spec) - jni_unchecked!(self.internal, ReleaseStringUTFChars, obj.into_raw(), arr); - Ok(()) + /// Returns an error if `obj` is `null` or is not an instance of `java.lang.String` + pub fn get_string<'other_local: 'obj_ref, 'obj_ref>( + &mut self, + obj: &'obj_ref JString<'other_local>, + ) -> Result<JavaStr<'local, 'other_local, 'obj_ref>> { + let string_class = self.find_class("java/lang/String")?; + if !self.is_assignable_from(string_class, self.get_object_class(obj)?)? { + return Err(JniCall(JniError::InvalidArguments)); + } + + // SAFETY: We check that the passed in Object is actually a java.lang.String + unsafe { self.get_string_unchecked(obj) } } /// Create a new java string object from a rust string. This requires a /// re-encoding of rusts *real* UTF-8 strings to java's modified UTF-8 /// format. - pub fn new_string<S: Into<JNIString>>(&self, from: S) -> Result<JString<'a>> { + pub fn new_string<S: Into<JNIString>>(&self, from: S) -> Result<JString<'local>> { let ffi_str = from.into(); let s = jni_non_null_call!(self.internal, NewStringUTF, ffi_str.as_ptr()); Ok(unsafe { JString::from_raw(s) }) } - /// Get the length of a java array - pub fn get_array_length(&self, array: jarray) -> Result<jsize> { - non_null!(array, "get_array_length array argument"); - let len: jsize = jni_unchecked!(self.internal, GetArrayLength, array); + /// Get the length of a [`JPrimitiveArray`] or [`JObjectArray`]. + pub fn get_array_length<'other_local, 'array>( + &self, + array: &'array impl AsJArrayRaw<'other_local>, + ) -> Result<jsize> { + non_null!(array.as_jarray_raw(), "get_array_length array argument"); + let len: jsize = jni_unchecked!(self.internal, GetArrayLength, array.as_jarray_raw()); Ok(len) } @@ -1192,72 +1666,77 @@ impl<'a> JNIEnv<'a> { /// See [Java documentation][1] for details. /// /// [1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#global_and_local_references - pub fn new_object_array<'c, T, U>( - &self, + pub fn new_object_array<'other_local_1, 'other_local_2, T, U>( + &mut self, length: jsize, element_class: T, initial_element: U, - ) -> Result<jobjectArray> + ) -> Result<JObjectArray<'local>> where - T: Desc<'a, JClass<'c>>, - U: Into<JObject<'a>>, + T: Desc<'local, JClass<'other_local_2>>, + U: AsRef<JObject<'other_local_1>>, { let class = element_class.lookup(self)?; - Ok(jni_non_null_call!( + + let array: jarray = jni_non_null_call!( self.internal, NewObjectArray, length, - class.into_raw(), - initial_element.into().into_raw() - )) + class.as_ref().as_raw(), + initial_element.as_ref().as_raw() + ); + + let array = unsafe { JObjectArray::from_raw(array) }; + + // Ensure that `class` isn't dropped before the JNI call returns. + drop(class); + + Ok(array) } - /// Returns an element of the `jobjectArray` array. - pub fn get_object_array_element( - &self, - array: jobjectArray, + /// Returns a local reference to an element of the [`JObjectArray`] `array`. + pub fn get_object_array_element<'other_local>( + &mut self, + array: impl AsRef<JObjectArray<'other_local>>, index: jsize, - ) -> Result<JObject<'a>> { - non_null!(array, "get_object_array_element array argument"); + ) -> Result<JObject<'local>> { + non_null!(array.as_ref(), "get_object_array_element array argument"); Ok(unsafe { JObject::from_raw(jni_non_void_call!( self.internal, GetObjectArrayElement, - array, + array.as_ref().as_raw(), index )) }) } - /// Sets an element of the `jobjectArray` array. - pub fn set_object_array_element<O>( + /// Sets an element of the [`JObjectArray`] `array`. + pub fn set_object_array_element<'other_local_1, 'other_local_2>( &self, - array: jobjectArray, + array: impl AsRef<JObjectArray<'other_local_1>>, index: jsize, - value: O, - ) -> Result<()> - where - O: Into<JObject<'a>>, - { - non_null!(array, "set_object_array_element array argument"); + value: impl AsRef<JObject<'other_local_2>>, + ) -> Result<()> { + non_null!(array.as_ref(), "set_object_array_element array argument"); jni_void_call!( self.internal, SetObjectArrayElement, - array, + array.as_ref().as_raw(), index, - value.into().into_raw() + value.as_ref().as_raw() ); Ok(()) } /// Create a new java byte array from a rust byte slice. - pub fn byte_array_from_slice(&self, buf: &[u8]) -> Result<jbyteArray> { + pub fn byte_array_from_slice(&self, buf: &[u8]) -> Result<JByteArray<'local>> { let length = buf.len() as i32; - let bytes: jbyteArray = self.new_byte_array(length)?; + let bytes = self.new_byte_array(length)?; jni_unchecked!( self.internal, SetByteArrayRegion, - bytes, + bytes.as_raw(), 0, length, buf.as_ptr() as *const i8 @@ -1266,7 +1745,11 @@ impl<'a> JNIEnv<'a> { } /// Converts a java byte array to a rust vector of bytes. - pub fn convert_byte_array(&self, array: jbyteArray) -> Result<Vec<u8>> { + pub fn convert_byte_array<'other_local>( + &self, + array: impl AsRef<JByteArray<'other_local>>, + ) -> Result<Vec<u8>> { + let array = array.as_ref().as_raw(); non_null!(array, "convert_byte_array array argument"); let length = jni_non_void_call!(self.internal, GetArrayLength, array); let mut vec = vec![0u8; length as usize]; @@ -1282,50 +1765,58 @@ impl<'a> JNIEnv<'a> { } /// Create a new java boolean array of supplied length. - pub fn new_boolean_array(&self, length: jsize) -> Result<jbooleanArray> { - let array: jbooleanArray = jni_non_null_call!(self.internal, NewBooleanArray, length); + pub fn new_boolean_array(&self, length: jsize) -> Result<JBooleanArray<'local>> { + let array: jarray = jni_non_null_call!(self.internal, NewBooleanArray, length); + let array = unsafe { JBooleanArray::from_raw(array) }; Ok(array) } /// Create a new java byte array of supplied length. - pub fn new_byte_array(&self, length: jsize) -> Result<jbyteArray> { - let array: jbyteArray = jni_non_null_call!(self.internal, NewByteArray, length); + pub fn new_byte_array(&self, length: jsize) -> Result<JByteArray<'local>> { + let array: jarray = jni_non_null_call!(self.internal, NewByteArray, length); + let array = unsafe { JByteArray::from_raw(array) }; Ok(array) } /// Create a new java char array of supplied length. - pub fn new_char_array(&self, length: jsize) -> Result<jcharArray> { - let array: jcharArray = jni_non_null_call!(self.internal, NewCharArray, length); + pub fn new_char_array(&self, length: jsize) -> Result<JCharArray<'local>> { + let array: jarray = jni_non_null_call!(self.internal, NewCharArray, length); + let array = unsafe { JCharArray::from_raw(array) }; Ok(array) } /// Create a new java short array of supplied length. - pub fn new_short_array(&self, length: jsize) -> Result<jshortArray> { - let array: jshortArray = jni_non_null_call!(self.internal, NewShortArray, length); + pub fn new_short_array(&self, length: jsize) -> Result<JShortArray<'local>> { + let array: jarray = jni_non_null_call!(self.internal, NewShortArray, length); + let array = unsafe { JShortArray::from_raw(array) }; Ok(array) } /// Create a new java int array of supplied length. - pub fn new_int_array(&self, length: jsize) -> Result<jintArray> { - let array: jintArray = jni_non_null_call!(self.internal, NewIntArray, length); + pub fn new_int_array(&self, length: jsize) -> Result<JIntArray<'local>> { + let array: jarray = jni_non_null_call!(self.internal, NewIntArray, length); + let array = unsafe { JIntArray::from_raw(array) }; Ok(array) } /// Create a new java long array of supplied length. - pub fn new_long_array(&self, length: jsize) -> Result<jlongArray> { - let array: jlongArray = jni_non_null_call!(self.internal, NewLongArray, length); + pub fn new_long_array(&self, length: jsize) -> Result<JLongArray<'local>> { + let array: jarray = jni_non_null_call!(self.internal, NewLongArray, length); + let array = unsafe { JLongArray::from_raw(array) }; Ok(array) } /// Create a new java float array of supplied length. - pub fn new_float_array(&self, length: jsize) -> Result<jfloatArray> { - let array: jfloatArray = jni_non_null_call!(self.internal, NewFloatArray, length); + pub fn new_float_array(&self, length: jsize) -> Result<JFloatArray<'local>> { + let array: jarray = jni_non_null_call!(self.internal, NewFloatArray, length); + let array = unsafe { JFloatArray::from_raw(array) }; Ok(array) } /// Create a new java double array of supplied length. - pub fn new_double_array(&self, length: jsize) -> Result<jdoubleArray> { - let array: jdoubleArray = jni_non_null_call!(self.internal, NewDoubleArray, length); + pub fn new_double_array(&self, length: jsize) -> Result<JDoubleArray<'local>> { + let array: jarray = jni_non_null_call!(self.internal, NewDoubleArray, length); + let array = unsafe { JDoubleArray::from_raw(array) }; Ok(array) } @@ -1338,17 +1829,17 @@ impl<'a> JNIEnv<'a> { /// and `Err` is returned. /// /// [`array.length`]: struct.JNIEnv.html#method.get_array_length - pub fn get_boolean_array_region( + pub fn get_boolean_array_region<'other_local>( &self, - array: jbooleanArray, + array: impl AsRef<JBooleanArray<'other_local>>, start: jsize, buf: &mut [jboolean], ) -> Result<()> { - non_null!(array, "get_boolean_array_region array argument"); + non_null!(array.as_ref(), "get_boolean_array_region array argument"); jni_void_call!( self.internal, GetBooleanArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_mut_ptr() @@ -1365,21 +1856,22 @@ impl<'a> JNIEnv<'a> { /// and `Err` is returned. /// /// [`array.length`]: struct.JNIEnv.html#method.get_array_length - pub fn get_byte_array_region( + pub fn get_byte_array_region<'other_local>( &self, - array: jbyteArray, + array: impl AsRef<JByteArray<'other_local>>, start: jsize, buf: &mut [jbyte], ) -> Result<()> { - non_null!(array, "get_byte_array_region array argument"); + non_null!(array.as_ref(), "get_byte_array_region array argument"); jni_void_call!( self.internal, GetByteArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_mut_ptr() ); + Ok(()) } @@ -1392,17 +1884,17 @@ impl<'a> JNIEnv<'a> { /// and `Err` is returned. /// /// [`array.length`]: struct.JNIEnv.html#method.get_array_length - pub fn get_char_array_region( + pub fn get_char_array_region<'other_local>( &self, - array: jcharArray, + array: impl AsRef<JCharArray<'other_local>>, start: jsize, buf: &mut [jchar], ) -> Result<()> { - non_null!(array, "get_char_array_region array argument"); + non_null!(array.as_ref(), "get_char_array_region array argument"); jni_void_call!( self.internal, GetCharArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_mut_ptr() @@ -1419,17 +1911,17 @@ impl<'a> JNIEnv<'a> { /// and `Err` is returned. /// /// [`array.length`]: struct.JNIEnv.html#method.get_array_length - pub fn get_short_array_region( + pub fn get_short_array_region<'other_local>( &self, - array: jshortArray, + array: impl AsRef<JShortArray<'other_local>>, start: jsize, buf: &mut [jshort], ) -> Result<()> { - non_null!(array, "get_short_array_region array argument"); + non_null!(array.as_ref(), "get_short_array_region array argument"); jni_void_call!( self.internal, GetShortArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_mut_ptr() @@ -1446,17 +1938,17 @@ impl<'a> JNIEnv<'a> { /// and `Err` is returned. /// /// [`array.length`]: struct.JNIEnv.html#method.get_array_length - pub fn get_int_array_region( + pub fn get_int_array_region<'other_local>( &self, - array: jintArray, + array: impl AsRef<JIntArray<'other_local>>, start: jsize, buf: &mut [jint], ) -> Result<()> { - non_null!(array, "get_int_array_region array argument"); + non_null!(array.as_ref(), "get_int_array_region array argument"); jni_void_call!( self.internal, GetIntArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_mut_ptr() @@ -1473,17 +1965,17 @@ impl<'a> JNIEnv<'a> { /// and `Err` is returned. /// /// [`array.length`]: struct.JNIEnv.html#method.get_array_length - pub fn get_long_array_region( + pub fn get_long_array_region<'other_local>( &self, - array: jlongArray, + array: impl AsRef<JLongArray<'other_local>>, start: jsize, buf: &mut [jlong], ) -> Result<()> { - non_null!(array, "get_long_array_region array argument"); + non_null!(array.as_ref(), "get_long_array_region array argument"); jni_void_call!( self.internal, GetLongArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_mut_ptr() @@ -1500,17 +1992,17 @@ impl<'a> JNIEnv<'a> { /// and `Err` is returned. /// /// [`array.length`]: struct.JNIEnv.html#method.get_array_length - pub fn get_float_array_region( + pub fn get_float_array_region<'other_local>( &self, - array: jfloatArray, + array: impl AsRef<JFloatArray<'other_local>>, start: jsize, buf: &mut [jfloat], ) -> Result<()> { - non_null!(array, "get_float_array_region array argument"); + non_null!(array.as_ref(), "get_float_array_region array argument"); jni_void_call!( self.internal, GetFloatArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_mut_ptr() @@ -1527,17 +2019,17 @@ impl<'a> JNIEnv<'a> { /// and `Err` is returned. /// /// [`array.length`]: struct.JNIEnv.html#method.get_array_length - pub fn get_double_array_region( + pub fn get_double_array_region<'other_local>( &self, - array: jdoubleArray, + array: impl AsRef<JDoubleArray<'other_local>>, start: jsize, buf: &mut [jdouble], ) -> Result<()> { - non_null!(array, "get_double_array_region array argument"); + non_null!(array.as_ref(), "get_double_array_region array argument"); jni_void_call!( self.internal, GetDoubleArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_mut_ptr() @@ -1547,17 +2039,17 @@ impl<'a> JNIEnv<'a> { /// Copy the contents of the `buf` slice to the java boolean array at the /// `start` index. - pub fn set_boolean_array_region( + pub fn set_boolean_array_region<'other_local>( &self, - array: jbooleanArray, + array: impl AsRef<JBooleanArray<'other_local>>, start: jsize, buf: &[jboolean], ) -> Result<()> { - non_null!(array, "set_boolean_array_region array argument"); + non_null!(array.as_ref(), "set_boolean_array_region array argument"); jni_void_call!( self.internal, SetBooleanArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_ptr() @@ -1567,17 +2059,17 @@ impl<'a> JNIEnv<'a> { /// Copy the contents of the `buf` slice to the java byte array at the /// `start` index. - pub fn set_byte_array_region( + pub fn set_byte_array_region<'other_local>( &self, - array: jbyteArray, + array: impl AsRef<JByteArray<'other_local>>, start: jsize, buf: &[jbyte], ) -> Result<()> { - non_null!(array, "set_byte_array_region array argument"); + non_null!(array.as_ref(), "set_byte_array_region array argument"); jni_void_call!( self.internal, SetByteArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_ptr() @@ -1587,17 +2079,17 @@ impl<'a> JNIEnv<'a> { /// Copy the contents of the `buf` slice to the java char array at the /// `start` index. - pub fn set_char_array_region( + pub fn set_char_array_region<'other_local>( &self, - array: jcharArray, + array: impl AsRef<JCharArray<'other_local>>, start: jsize, buf: &[jchar], ) -> Result<()> { - non_null!(array, "set_char_array_region array argument"); + non_null!(array.as_ref(), "set_char_array_region array argument"); jni_void_call!( self.internal, SetCharArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_ptr() @@ -1607,17 +2099,17 @@ impl<'a> JNIEnv<'a> { /// Copy the contents of the `buf` slice to the java short array at the /// `start` index. - pub fn set_short_array_region( + pub fn set_short_array_region<'other_local>( &self, - array: jshortArray, + array: impl AsRef<JShortArray<'other_local>>, start: jsize, buf: &[jshort], ) -> Result<()> { - non_null!(array, "set_short_array_region array argument"); + non_null!(array.as_ref(), "set_short_array_region array argument"); jni_void_call!( self.internal, SetShortArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_ptr() @@ -1627,12 +2119,17 @@ impl<'a> JNIEnv<'a> { /// Copy the contents of the `buf` slice to the java int array at the /// `start` index. - pub fn set_int_array_region(&self, array: jintArray, start: jsize, buf: &[jint]) -> Result<()> { - non_null!(array, "set_int_array_region array argument"); + pub fn set_int_array_region<'other_local>( + &self, + array: impl AsRef<JIntArray<'other_local>>, + start: jsize, + buf: &[jint], + ) -> Result<()> { + non_null!(array.as_ref(), "set_int_array_region array argument"); jni_void_call!( self.internal, SetIntArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_ptr() @@ -1642,17 +2139,17 @@ impl<'a> JNIEnv<'a> { /// Copy the contents of the `buf` slice to the java long array at the /// `start` index. - pub fn set_long_array_region( + pub fn set_long_array_region<'other_local>( &self, - array: jlongArray, + array: impl AsRef<JLongArray<'other_local>>, start: jsize, buf: &[jlong], ) -> Result<()> { - non_null!(array, "set_long_array_region array argument"); + non_null!(array.as_ref(), "set_long_array_region array argument"); jni_void_call!( self.internal, SetLongArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_ptr() @@ -1662,17 +2159,17 @@ impl<'a> JNIEnv<'a> { /// Copy the contents of the `buf` slice to the java float array at the /// `start` index. - pub fn set_float_array_region( + pub fn set_float_array_region<'other_local>( &self, - array: jfloatArray, + array: impl AsRef<JFloatArray<'other_local>>, start: jsize, buf: &[jfloat], ) -> Result<()> { - non_null!(array, "set_float_array_region array argument"); + non_null!(array.as_ref(), "set_float_array_region array argument"); jni_void_call!( self.internal, SetFloatArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_ptr() @@ -1682,17 +2179,17 @@ impl<'a> JNIEnv<'a> { /// Copy the contents of the `buf` slice to the java double array at the /// `start` index. - pub fn set_double_array_region( + pub fn set_double_array_region<'other_local>( &self, - array: jdoubleArray, + array: impl AsRef<JDoubleArray<'other_local>>, start: jsize, buf: &[jdouble], ) -> Result<()> { - non_null!(array, "set_double_array_region array argument"); + non_null!(array.as_ref(), "set_double_array_region array argument"); jni_void_call!( self.internal, SetDoubleArrayRegion, - array, + array.as_ref().as_raw(), start, buf.len() as jsize, buf.as_ptr() @@ -1701,16 +2198,21 @@ impl<'a> JNIEnv<'a> { } /// Get a field without checking the provided type against the actual field. - pub fn get_field_unchecked<O, T>(&self, obj: O, field: T, ty: ReturnType) -> Result<JValue<'a>> + pub fn get_field_unchecked<'other_local, O, T>( + &mut self, + obj: O, + field: T, + ty: ReturnType, + ) -> Result<JValueOwned<'local>> where - O: Into<JObject<'a>>, - T: Desc<'a, JFieldID>, + O: AsRef<JObject<'other_local>>, + T: Desc<'local, JFieldID>, { - let obj = obj.into(); + let obj = obj.as_ref(); non_null!(obj, "get_field_typed obj argument"); - let field = field.lookup(self)?.into_raw(); - let obj = obj.into_raw(); + let field = field.lookup(self)?.as_ref().into_raw(); + let obj = obj.as_raw(); // TODO clean this up Ok(match ty { @@ -1740,21 +2242,26 @@ impl<'a> JNIEnv<'a> { } /// Set a field without any type checking. - pub fn set_field_unchecked<O, T>(&self, obj: O, field: T, val: JValue) -> Result<()> + pub fn set_field_unchecked<'other_local, O, T>( + &mut self, + obj: O, + field: T, + val: JValue, + ) -> Result<()> where - O: Into<JObject<'a>>, - T: Desc<'a, JFieldID>, + O: AsRef<JObject<'other_local>>, + T: Desc<'local, JFieldID>, { - let obj = obj.into(); + let obj = obj.as_ref(); non_null!(obj, "set_field_typed obj argument"); - let field = field.lookup(self)?.into_raw(); - let obj = obj.into_raw(); + let field = field.lookup(self)?.as_ref().into_raw(); + let obj = obj.as_raw(); // TODO clean this up match val { JValue::Object(o) => { - jni_unchecked!(self.internal, SetObjectField, obj, field, o.into_raw()); + jni_unchecked!(self.internal, SetObjectField, obj, field, o.as_raw()); } // JavaType::Object JValue::Bool(b) => { @@ -1791,31 +2298,42 @@ impl<'a> JNIEnv<'a> { /// Get a field. Requires an object class lookup and a field id lookup /// internally. - pub fn get_field<O, S, T>(&self, obj: O, name: S, ty: T) -> Result<JValue<'a>> + pub fn get_field<'other_local, O, S, T>( + &mut self, + obj: O, + name: S, + ty: T, + ) -> Result<JValueOwned<'local>> where - O: Into<JObject<'a>>, + O: AsRef<JObject<'other_local>>, S: Into<JNIString>, T: Into<JNIString> + AsRef<str>, { - let obj = obj.into(); + let obj = obj.as_ref(); let class = self.auto_local(self.get_object_class(obj)?); let parsed = ReturnType::from_str(ty.as_ref())?; - let field_id: JFieldID = (&class, name, ty).lookup(self)?; + let field_id: JFieldID = Desc::<JFieldID>::lookup((&class, name, ty), self)?; self.get_field_unchecked(obj, field_id, parsed) } /// Set a field. Does the same lookups as `get_field` and ensures that the /// type matches the given value. - pub fn set_field<O, S, T>(&self, obj: O, name: S, ty: T, val: JValue) -> Result<()> + pub fn set_field<'other_local, O, S, T>( + &mut self, + obj: O, + name: S, + ty: T, + val: JValue, + ) -> Result<()> where - O: Into<JObject<'a>>, + O: AsRef<JObject<'other_local>>, S: Into<JNIString>, T: Into<JNIString> + AsRef<str>, { - let obj = obj.into(); + let obj = obj.as_ref(); let parsed = JavaType::from_str(ty.as_ref())?; let in_type = val.primitive_type(); @@ -1846,106 +2364,208 @@ impl<'a> JNIEnv<'a> { /// Get a static field without checking the provided type against the actual /// field. - pub fn get_static_field_unchecked<'c, T, U>( - &self, + pub fn get_static_field_unchecked<'other_local, T, U>( + &mut self, class: T, field: U, ty: JavaType, - ) -> Result<JValue<'a>> + ) -> Result<JValueOwned<'local>> where - T: Desc<'a, JClass<'c>>, - U: Desc<'a, JStaticFieldID>, + T: Desc<'local, JClass<'other_local>>, + U: Desc<'local, JStaticFieldID>, { use JavaType::Primitive as JP; - let class = class.lookup(self)?.into_raw(); - let field = field.lookup(self)?.into_raw(); + let class = class.lookup(self)?; + let field = field.lookup(self)?; let result = match ty { JavaType::Object(_) | JavaType::Array(_) => { - let obj = jni_non_void_call!(self.internal, GetStaticObjectField, class, field); + let obj = jni_non_void_call!( + self.internal, + GetStaticObjectField, + class.as_ref().as_raw(), + field.as_ref().into_raw() + ); let obj = unsafe { JObject::from_raw(obj) }; obj.into() } JavaType::Method(_) => return Err(Error::WrongJValueType("Method", "see java field")), - JP(Primitive::Boolean) => { - jni_unchecked!(self.internal, GetStaticBooleanField, class, field).into() - } - JP(Primitive::Char) => { - jni_unchecked!(self.internal, GetStaticCharField, class, field).into() - } - JP(Primitive::Short) => { - jni_unchecked!(self.internal, GetStaticShortField, class, field).into() - } - JP(Primitive::Int) => { - jni_unchecked!(self.internal, GetStaticIntField, class, field).into() - } - JP(Primitive::Long) => { - jni_unchecked!(self.internal, GetStaticLongField, class, field).into() - } - JP(Primitive::Float) => { - jni_unchecked!(self.internal, GetStaticFloatField, class, field).into() - } - JP(Primitive::Double) => { - jni_unchecked!(self.internal, GetStaticDoubleField, class, field).into() - } - JP(Primitive::Byte) => { - jni_unchecked!(self.internal, GetStaticByteField, class, field).into() - } + JP(Primitive::Boolean) => jni_unchecked!( + self.internal, + GetStaticBooleanField, + class.as_ref().as_raw(), + field.as_ref().into_raw() + ) + .into(), + JP(Primitive::Char) => jni_unchecked!( + self.internal, + GetStaticCharField, + class.as_ref().as_raw(), + field.as_ref().into_raw() + ) + .into(), + JP(Primitive::Short) => jni_unchecked!( + self.internal, + GetStaticShortField, + class.as_ref().as_raw(), + field.as_ref().into_raw() + ) + .into(), + JP(Primitive::Int) => jni_unchecked!( + self.internal, + GetStaticIntField, + class.as_ref().as_raw(), + field.as_ref().into_raw() + ) + .into(), + JP(Primitive::Long) => jni_unchecked!( + self.internal, + GetStaticLongField, + class.as_ref().as_raw(), + field.as_ref().into_raw() + ) + .into(), + JP(Primitive::Float) => jni_unchecked!( + self.internal, + GetStaticFloatField, + class.as_ref().as_raw(), + field.as_ref().into_raw() + ) + .into(), + JP(Primitive::Double) => jni_unchecked!( + self.internal, + GetStaticDoubleField, + class.as_ref().as_raw(), + field.as_ref().into_raw() + ) + .into(), + JP(Primitive::Byte) => jni_unchecked!( + self.internal, + GetStaticByteField, + class.as_ref().as_raw(), + field.as_ref().into_raw() + ) + .into(), JP(Primitive::Void) => return Err(Error::WrongJValueType("void", "see java field")), }; + + // Ensure that `class` isn't dropped before the JNI call returns. + drop(class); + Ok(result) } /// Get a static field. Requires a class lookup and a field id lookup /// internally. - pub fn get_static_field<'c, T, U, V>(&self, class: T, field: U, sig: V) -> Result<JValue<'a>> + pub fn get_static_field<'other_local, T, U, V>( + &mut self, + class: T, + field: U, + sig: V, + ) -> Result<JValueOwned<'local>> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, U: Into<JNIString>, V: Into<JNIString> + AsRef<str>, { let ty = JavaType::from_str(sig.as_ref())?; - // go ahead and look up the class since it's already Copy, - // and we'll need that for the next call. + // go ahead and look up the class sincewe'll need that for the next + // call. let class = class.lookup(self)?; - self.get_static_field_unchecked(class, (class, field, sig), ty) + self.get_static_field_unchecked(class.as_ref(), (class.as_ref(), field, sig), ty) } /// Set a static field. Requires a class lookup and a field id lookup internally. - pub fn set_static_field<'c, T, U>(&self, class: T, field: U, value: JValue) -> Result<()> + pub fn set_static_field<'other_local, T, U>( + &mut self, + class: T, + field: U, + value: JValue, + ) -> Result<()> where - T: Desc<'a, JClass<'c>>, - U: Desc<'a, JStaticFieldID>, + T: Desc<'local, JClass<'other_local>>, + U: Desc<'local, JStaticFieldID>, { - let class = class.lookup(self)?.into_raw(); - let field = field.lookup(self)?.into_raw(); + let class = class.lookup(self)?; + let field = field.lookup(self)?; match value { JValue::Object(v) => jni_unchecked!( self.internal, SetStaticObjectField, - class, - field, - v.into_raw() + class.as_ref().as_raw(), + field.as_ref().into_raw(), + v.as_raw() + ), + JValue::Byte(v) => jni_unchecked!( + self.internal, + SetStaticByteField, + class.as_ref().as_raw(), + field.as_ref().into_raw(), + v + ), + JValue::Char(v) => jni_unchecked!( + self.internal, + SetStaticCharField, + class.as_ref().as_raw(), + field.as_ref().into_raw(), + v + ), + JValue::Short(v) => jni_unchecked!( + self.internal, + SetStaticShortField, + class.as_ref().as_raw(), + field.as_ref().into_raw(), + v + ), + JValue::Int(v) => jni_unchecked!( + self.internal, + SetStaticIntField, + class.as_ref().as_raw(), + field.as_ref().into_raw(), + v + ), + JValue::Long(v) => jni_unchecked!( + self.internal, + SetStaticLongField, + class.as_ref().as_raw(), + field.as_ref().into_raw(), + v ), - JValue::Byte(v) => jni_unchecked!(self.internal, SetStaticByteField, class, field, v), - JValue::Char(v) => jni_unchecked!(self.internal, SetStaticCharField, class, field, v), - JValue::Short(v) => jni_unchecked!(self.internal, SetStaticShortField, class, field, v), - JValue::Int(v) => jni_unchecked!(self.internal, SetStaticIntField, class, field, v), - JValue::Long(v) => jni_unchecked!(self.internal, SetStaticLongField, class, field, v), JValue::Bool(v) => { - jni_unchecked!(self.internal, SetStaticBooleanField, class, field, v) + jni_unchecked!( + self.internal, + SetStaticBooleanField, + class.as_ref().as_raw(), + field.as_ref().into_raw(), + v + ) } - JValue::Float(v) => jni_unchecked!(self.internal, SetStaticFloatField, class, field, v), + JValue::Float(v) => jni_unchecked!( + self.internal, + SetStaticFloatField, + class.as_ref().as_raw(), + field.as_ref().into_raw(), + v + ), JValue::Double(v) => { - jni_unchecked!(self.internal, SetStaticDoubleField, class, field, v) + jni_unchecked!( + self.internal, + SetStaticDoubleField, + class.as_ref().as_raw(), + field.as_ref().into_raw(), + v + ) } JValue::Void => return Err(Error::WrongJValueType("void", "?")), } + // Ensure that `class` isn't dropped before the JNI call returns. + drop(class); + Ok(()) } @@ -1953,9 +2573,23 @@ impl<'a> JNIEnv<'a> { /// /// This requires an object with a `long` field to store the pointer. /// + /// In Java the property may look like: + /// ```java + /// private long myRustValueHandle = 0; + /// ``` + /// + /// Or, in Kotlin the property may look like: + /// ```java + /// private var myRustValueHandle: Long = 0 + /// ``` + /// + /// _Note that `private` properties are accessible to JNI which may be + /// preferable to avoid exposing the handles to more code than necessary + /// (since the handles are usually only meaningful to Rust code)_. + /// /// The Rust value will be implicitly wrapped in a `Box<Mutex<T>>`. /// - /// The Java object will be locked before changing the field value. + /// The Java object will be locked while changing the field value. /// /// # Safety /// @@ -1967,19 +2601,26 @@ impl<'a> JNIEnv<'a> { /// cleaned up later is for the Java object to implement `Closeable` and let /// people use a `use` block (Kotlin) or `try-with-resources` (Java). /// - /// **DO NOT** make a copy of the object containing one of these fields - /// since that will lead to a use-after-free error if the Rust type is - /// taken and dropped multiple times from Rust. + /// **DO NOT** make a copy of the handle stored in one of these fields + /// since that could lead to a use-after-free error if the Rust type is + /// taken and dropped multiple times from Rust. If you need to copy an + /// object with one of these fields then the field should be zero + /// initialized in the copy. #[allow(unused_variables)] - pub unsafe fn set_rust_field<O, S, T>(&self, obj: O, field: S, rust_object: T) -> Result<()> + pub unsafe fn set_rust_field<'other_local, O, S, T>( + &mut self, + obj: O, + field: S, + rust_object: T, + ) -> Result<()> where - O: Into<JObject<'a>>, + O: AsRef<JObject<'other_local>>, S: AsRef<str>, T: Send + 'static, { - let obj = obj.into(); + let obj = obj.as_ref(); let class = self.auto_local(self.get_object_class(obj)?); - let field_id: JFieldID = (&class, &field, "J").lookup(self)?; + let field_id: JFieldID = Desc::<JFieldID>::lookup((&class, &field, "J"), self)?; let guard = self.lock_obj(obj)?; @@ -2012,13 +2653,17 @@ impl<'a> JNIEnv<'a> { /// /// Checks for a null pointer, but assumes that the data it points to is valid for T. #[allow(unused_variables)] - pub unsafe fn get_rust_field<O, S, T>(&self, obj: O, field: S) -> Result<MutexGuard<T>> + pub unsafe fn get_rust_field<'other_local, O, S, T>( + &mut self, + obj: O, + field: S, + ) -> Result<MutexGuard<T>> where - O: Into<JObject<'a>>, + O: AsRef<JObject<'other_local>>, S: Into<JNIString>, T: Send + 'static, { - let obj = obj.into(); + let obj = obj.as_ref(); let guard = self.lock_obj(obj)?; let ptr = self.get_field(obj, field, "J")?.j()? as *mut Mutex<T>; @@ -2038,15 +2683,15 @@ impl<'a> JNIEnv<'a> { /// This will make sure that the pointer is non-null, but still assumes that /// the data it points to is valid for T. #[allow(unused_variables)] - pub unsafe fn take_rust_field<O, S, T>(&self, obj: O, field: S) -> Result<T> + pub unsafe fn take_rust_field<'other_local, O, S, T>(&mut self, obj: O, field: S) -> Result<T> where - O: Into<JObject<'a>>, + O: AsRef<JObject<'other_local>>, S: AsRef<str>, T: Send + 'static, { - let obj = obj.into(); + let obj = obj.as_ref(); let class = self.auto_local(self.get_object_class(obj)?); - let field_id: JFieldID = (&class, &field, "J").lookup(self)?; + let field_id: JFieldID = Desc::<JFieldID>::lookup((&class, &field, "J"), self)?; let mbox = { let guard = self.lock_obj(obj)?; @@ -2078,11 +2723,11 @@ impl<'a> JNIEnv<'a> { /// Lock a Java object. The MonitorGuard that this returns is responsible /// for ensuring that it gets unlocked. - pub fn lock_obj<O>(&self, obj: O) -> Result<MonitorGuard<'a>> + pub fn lock_obj<'other_local, O>(&self, obj: O) -> Result<MonitorGuard<'local>> where - O: Into<JObject<'a>>, + O: AsRef<JObject<'other_local>>, { - let inner = obj.into().into_raw(); + let inner = obj.as_ref().as_raw(); let _ = jni_unchecked!(self.internal, MonitorEnter, inner); Ok(MonitorGuard { @@ -2115,9 +2760,13 @@ impl<'a> JNIEnv<'a> { /// Bind function pointers to native methods of class /// according to method name and signature. /// For details see [documentation](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives). - pub fn register_native_methods<'c, T>(&self, class: T, methods: &[NativeMethod]) -> Result<()> + pub fn register_native_methods<'other_local, T>( + &mut self, + class: T, + methods: &[NativeMethod], + ) -> Result<()> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, { let class = class.lookup(self)?; let jni_native_methods: Vec<JNINativeMethod> = methods @@ -2131,170 +2780,194 @@ impl<'a> JNIEnv<'a> { let res = jni_non_void_call!( self.internal, RegisterNatives, - class.into_raw(), + class.as_ref().as_raw(), jni_native_methods.as_ptr(), jni_native_methods.len() as jint ); + + // Ensure that `class` isn't dropped before the JNI call returns. + drop(class); + jni_error_code_to_result(res) } /// Unbind all native methods of class. - pub fn unregister_native_methods<'c, T>(&self, class: T) -> Result<()> + pub fn unregister_native_methods<'other_local, T>(&mut self, class: T) -> Result<()> where - T: Desc<'a, JClass<'c>>, + T: Desc<'local, JClass<'other_local>>, { let class = class.lookup(self)?; - let res = jni_non_void_call!(self.internal, UnregisterNatives, class.into_raw()); + let res = jni_non_void_call!(self.internal, UnregisterNatives, class.as_ref().as_raw()); + + // Ensure that `class` isn't dropped before the JNI call returns. + drop(class); + jni_error_code_to_result(res) } - /// Return an AutoArray of the given Java array. + /// Returns an [`AutoElements`] to access the elements of the given Java `array`. /// - /// The result is valid until the AutoArray object goes out of scope, when the - /// release happens automatically according to the mode parameter. + /// The elements are accessible until the returned auto-release guard is dropped. + /// + /// The returned array may be a copy of the Java array and changes made to + /// the returned array will not necessarily be reflected in the original + /// array until the [`AutoElements`] guard is dropped. + /// + /// If you know in advance that you will only be reading from the array then + /// pass [`ReleaseMode::NoCopyBack`] so that the JNI implementation knows + /// that it's not necessary to copy any data back to the original Java array + /// when the [`AutoElements`] guard is dropped. /// /// Since the returned array may be a copy of the Java array, changes made to the /// returned array will not necessarily be reflected in the original array until - /// the corresponding Release*ArrayElements JNI method is called. - /// AutoArray has a commit() method, to force a copy of the array if needed (and without - /// releasing it). - - /// Prefer to use the convenience wrappers: - /// [`get_int_array_elements`](struct.JNIEnv.html#method.get_int_array_elements) - /// [`get_long_array_elements`](struct.JNIEnv.html#method.get_long_array_elements) - /// [`get_byte_array_elements`](struct.JNIEnv.html#method.get_byte_array_elements) - /// [`get_boolean_array_elements`](struct.JNIEnv.html#method.get_boolean_array_elements) - /// [`get_char_array_elements`](struct.JNIEnv.html#method.get_char_array_elements) - /// [`get_short_array_elements`](struct.JNIEnv.html#method.get_short_array_elements) - /// [`get_float_array_elements`](struct.JNIEnv.html#method.get_float_array_elements) - /// [`get_double_array_elements`](struct.JNIEnv.html#method.get_double_array_elements) - /// And the associated [`AutoArray`](struct.objects.AutoArray) struct. - pub fn get_array_elements<T: TypeArray>( - &self, - array: jarray, + /// the corresponding `Release*ArrayElements` JNI method is called. + /// [`AutoElements`] has a commit() method, to force a copy back of pending + /// array changes if needed (and without releasing it). + /// + /// # Safety + /// + /// ## No data races + /// + /// This API has no built-in synchronization that ensures there won't be any data + /// races while accessing the array elements. + /// + /// To avoid undefined behaviour it is the caller's responsibility to ensure there + /// will be no data races between other Rust or Java threads trying to access the + /// same array. + /// + /// Acquiring a [`MonitorGuard`] lock for the `array` could be one way of ensuring + /// mutual exclusion between Rust and Java threads, so long as the Java threads + /// also acquire the same lock via `synchronized(array) {}`. + /// + /// ## No aliasing + /// + /// Callers must not create more than one [`AutoElements`] or + /// [`AutoElementsCritical`] per Java array at the same time - even if + /// there is no risk of a data race. + /// + /// The reason for this restriction is that [`AutoElements`] and + /// [`AutoElementsCritical`] implement `DerefMut` which can provide a + /// mutable `&mut [T]` slice reference for the elements and it would + /// constitute undefined behaviour to allow there to be more than one + /// mutable reference that points to the same memory. + /// + /// # jboolean elements + /// + /// Keep in mind that arrays of `jboolean` values should only ever hold + /// values of `0` or `1` because any other value could lead to undefined + /// behaviour within the JVM. + /// + /// Also see + /// [`get_array_elements_critical`](Self::get_array_elements_critical) which + /// imposes additional restrictions that make it less likely to incur the + /// cost of copying the array elements. + pub unsafe fn get_array_elements<'other_local, 'array, T: TypeArray>( + &mut self, + array: &'array JPrimitiveArray<'other_local, T>, mode: ReleaseMode, - ) -> Result<AutoArray<'a, T>> { + ) -> Result<AutoElements<'local, 'other_local, 'array, T>> { non_null!(array, "get_array_elements array argument"); - AutoArray::new(self, unsafe { JObject::from_raw(array) }, mode) - } - - /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) - pub fn get_int_array_elements( - &self, - array: jintArray, - mode: ReleaseMode, - ) -> Result<AutoArray<'a, jint>> { - self.get_array_elements(array, mode) - } - - /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) - pub fn get_long_array_elements( - &self, - array: jlongArray, - mode: ReleaseMode, - ) -> Result<AutoArray<'a, jlong>> { - self.get_array_elements(array, mode) - } - - /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) - pub fn get_byte_array_elements( - &self, - array: jbyteArray, - mode: ReleaseMode, - ) -> Result<AutoArray<'a, jbyte>> { - self.get_array_elements(array, mode) + AutoElements::new(self, array, mode) } - /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) - pub fn get_boolean_array_elements( - &self, - array: jbooleanArray, - mode: ReleaseMode, - ) -> Result<AutoArray<'a, jboolean>> { - self.get_array_elements(array, mode) - } - - /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) - pub fn get_char_array_elements( - &self, - array: jcharArray, - mode: ReleaseMode, - ) -> Result<AutoArray<'a, jchar>> { - self.get_array_elements(array, mode) - } - - /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) - pub fn get_short_array_elements( - &self, - array: jshortArray, - mode: ReleaseMode, - ) -> Result<AutoArray<'a, jshort>> { - self.get_array_elements(array, mode) - } - - /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) - pub fn get_float_array_elements( - &self, - array: jfloatArray, - mode: ReleaseMode, - ) -> Result<AutoArray<'a, jfloat>> { - self.get_array_elements(array, mode) - } - - /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) - pub fn get_double_array_elements( - &self, - array: jdoubleArray, - mode: ReleaseMode, - ) -> Result<AutoArray<'a, jdouble>> { - self.get_array_elements(array, mode) - } - - /// Return an AutoPrimitiveArray of the given Java primitive array. + /// Returns an [`AutoElementsCritical`] to access the elements of the given Java `array`. /// - /// The result is valid until the corresponding AutoPrimitiveArray object goes out of scope, - /// when the release happens automatically according to the mode parameter. + /// The elements are accessible during the critical section that exists until the + /// returned auto-release guard is dropped. /// - /// Given that Critical sections must be as short as possible, and that they come with a - /// number of important restrictions (see GetPrimitiveArrayCritical JNI doc), use this - /// wrapper wisely, to avoid holding the array longer that strictly necessary. - /// In any case, you can: - /// - Use std::mem::drop explicitly, to force / anticipate resource release. - /// - Use a nested scope, to release the array at the nested scope's exit. + /// This API imposes some strict restrictions that help the JNI implementation + /// avoid any need to copy the underlying array elements before making them + /// accessible to native code: /// - /// Since the returned array may be a copy of the Java array, changes made to the - /// returned array will not necessarily be reflected in the original array until - /// ReleasePrimitiveArrayCritical is called; which happens at AutoPrimitiveArray - /// destruction. + /// 1. No other use of JNI calls are allowed (on the same thread) within the critical + /// section that exists while holding the [`AutoElementsCritical`] guard. + /// 2. No system calls can be made (Such as `read`) that may depend on a result + /// from another Java thread. + /// + /// The JNI spec does not specify what will happen if these rules aren't adhered to + /// but it should be assumed it will lead to undefined behaviour, likely deadlock + /// and possible program termination. + /// + /// Even with these restrictions the returned array may still be a copy of + /// the Java array and changes made to the returned array will not + /// necessarily be reflected in the original array until the [`AutoElementsCritical`] + /// guard is dropped. + /// + /// If you know in advance that you will only be reading from the array then + /// pass [`ReleaseMode::NoCopyBack`] so that the JNI implementation knows + /// that it's not necessary to copy any data back to the original Java array + /// when the [`AutoElementsCritical`] guard is dropped. + /// + /// A nested scope or explicit use of `std::mem::drop` can be used to + /// control when the returned [`AutoElementsCritical`] is dropped to + /// minimize the length of the critical section. /// /// If the given array is `null`, an `Error::NullPtr` is returned. /// - /// See also [`get_byte_array_elements`](struct.JNIEnv.html#method.get_array_elements) - pub fn get_primitive_array_critical( - &self, - array: jarray, + /// # Safety + /// + /// ## Critical Section Restrictions + /// + /// Although this API takes a mutable reference to a [`JNIEnv`] which should + /// ensure that it's not possible to call JNI, this API is still marked as + /// `unsafe` due to the complex, far-reaching nature of the critical-section + /// restrictions imposed here that can't be guaranteed simply through Rust's + /// borrow checker rules. + /// + /// The rules above about JNI usage and system calls _must_ be adhered to. + /// + /// Using this API implies: + /// + /// 1. All garbage collection will likely be paused during the critical section + /// 2. Any use of JNI in other threads may block if they need to allocate memory + /// (due to the garbage collector being paused) + /// 3. Any use of system calls that will wait for a result from another Java thread + /// could deadlock if that other thread is blocked by a paused garbage collector. + /// + /// A failure to adhere to the critical section rules could lead to any + /// undefined behaviour, including aborting the program. + /// + /// ## No data races + /// + /// This API has no built-in synchronization that ensures there won't be any data + /// races while accessing the array elements. + /// + /// To avoid undefined behaviour it is the caller's responsibility to ensure there + /// will be no data races between other Rust or Java threads trying to access the + /// same array. + /// + /// Acquiring a [`MonitorGuard`] lock for the `array` could be one way of ensuring + /// mutual exclusion between Rust and Java threads, so long as the Java threads + /// also acquire the same lock via `synchronized(array) {}`. + /// + /// ## No aliasing + /// + /// Callers must not create more than one [`AutoElements`] or + /// [`AutoElementsCritical`] per Java array at the same time - even if + /// there is no risk of a data race. + /// + /// The reason for this restriction is that [`AutoElements`] and + /// [`AutoElementsCritical`] implement `DerefMut` which can provide a + /// mutable `&mut [T]` slice reference for the elements and it would + /// constitute undefined behaviour to allow there to be more than one + /// mutable reference that points to the same memory. + /// + /// ## jboolean elements + /// + /// Keep in mind that arrays of `jboolean` values should only ever hold + /// values of `0` or `1` because any other value could lead to undefined + /// behaviour within the JVM. + /// + /// Also see [`get_array_elements`](Self::get_array_elements) which has fewer + /// restrictions, but is is more likely to incur a cost from copying the + /// array elements. + pub unsafe fn get_array_elements_critical<'other_local, 'array, 'env, T: TypeArray>( + &'env mut self, + array: &'array JPrimitiveArray<'other_local, T>, mode: ReleaseMode, - ) -> Result<AutoPrimitiveArray> { + ) -> Result<AutoElementsCritical<'local, 'other_local, 'array, 'env, T>> { non_null!(array, "get_primitive_array_critical array argument"); - let mut is_copy: jboolean = 0xff; - // Even though this method may throw OoME, use `jni_unchecked` - // instead of `jni_non_null_call` to remove (a slight) overhead - // of exception checking. An error will still be detected as a `null` - // result inside AutoPrimitiveArray ctor; and, as this method is unlikely - // to create a copy, an OoME is highly unlikely. - let ptr = jni_unchecked!( - self.internal, - GetPrimitiveArrayCritical, - array, - &mut is_copy - ); - AutoPrimitiveArray::new( - self, - unsafe { JObject::from_raw(array) }, - ptr, - mode, - is_copy == sys::JNI_TRUE, - ) + AutoElementsCritical::new(self, array, mode) } } @@ -2314,13 +2987,13 @@ pub struct NativeMethod { /// Guard for a lock on a java object. This gets returned from the `lock_obj` /// method. -pub struct MonitorGuard<'a> { +pub struct MonitorGuard<'local> { obj: sys::jobject, env: *mut sys::JNIEnv, - life: PhantomData<&'a ()>, + life: PhantomData<&'local ()>, } -impl<'a> Drop for MonitorGuard<'a> { +impl<'local> Drop for MonitorGuard<'local> { fn drop(&mut self) { let res: Result<()> = catch!({ jni_unchecked!(self.env, MonitorExit, self.obj); diff --git a/src/wrapper/objects/auto_array.rs b/src/wrapper/objects/auto_array.rs deleted file mode 100644 index 2c73fac..0000000 --- a/src/wrapper/objects/auto_array.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::sys::jsize; -use log::error; - -use std::ptr::NonNull; - -use crate::objects::release_mode::ReleaseMode; -use crate::sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort}; -use crate::{errors::*, objects::JObject, sys, JNIEnv}; - -/// Trait to define type array access/release -pub trait TypeArray { - /// getter - fn get(env: &JNIEnv, obj: JObject, is_copy: &mut jboolean) -> Result<*mut Self>; - - /// releaser - fn release(env: &JNIEnv, obj: JObject, ptr: NonNull<Self>, mode: i32) -> Result<()>; -} - -// TypeArray builder -macro_rules! type_array { - ( $jni_type:ty, $jni_get:tt, $jni_release:tt ) => { - /// $jni_type array access/release impl - impl TypeArray for $jni_type { - /// Get Java $jni_type array - fn get(env: &JNIEnv, obj: JObject, is_copy: &mut jboolean) -> Result<*mut Self> { - let internal = env.get_native_interface(); - // Even though this method may throw OoME, use `jni_unchecked` - // instead of `jni_non_null_call` to remove (a slight) overhead - // of exception checking. An error will still be detected as a `null` - // result inside AutoArray ctor. Also, modern Hotspot in case of lack - // of memory will return null and won't throw an exception: - // https://sourcegraph.com/github.com/openjdk/jdk/-/blob/src/hotspot/share/memory/allocation.hpp#L488-489 - let res = jni_unchecked!(internal, $jni_get, *obj, is_copy); - Ok(res) - } - - /// Release Java $jni_type array - fn release(env: &JNIEnv, obj: JObject, ptr: NonNull<Self>, mode: i32) -> Result<()> { - let internal = env.get_native_interface(); - jni_unchecked!(internal, $jni_release, *obj, ptr.as_ptr(), mode as i32); - Ok(()) - } - } - }; -} - -type_array!(jint, GetIntArrayElements, ReleaseIntArrayElements); -type_array!(jlong, GetLongArrayElements, ReleaseLongArrayElements); -type_array!(jbyte, GetByteArrayElements, ReleaseByteArrayElements); -type_array!( - jboolean, - GetBooleanArrayElements, - ReleaseBooleanArrayElements -); -type_array!(jchar, GetCharArrayElements, ReleaseCharArrayElements); -type_array!(jshort, GetShortArrayElements, ReleaseShortArrayElements); -type_array!(jfloat, GetFloatArrayElements, ReleaseFloatArrayElements); -type_array!(jdouble, GetDoubleArrayElements, ReleaseDoubleArrayElements); - -/// Auto-release wrapper for pointer-based generic arrays. -/// -/// This wrapper is used to wrap pointers returned by Get<Type>ArrayElements. -/// While wrapped, the object can be accessed via the `From` impl. -/// -/// AutoArray provides automatic array release through a call to appropriate -/// Release<Type>ArrayElements when it goes out of scope. -pub struct AutoArray<'a, T: TypeArray> { - obj: JObject<'a>, - ptr: NonNull<T>, - mode: ReleaseMode, - is_copy: bool, - env: JNIEnv<'a>, -} - -impl<'a, T: TypeArray> AutoArray<'a, T> { - pub(crate) fn new(env: &JNIEnv<'a>, obj: JObject<'a>, mode: ReleaseMode) -> Result<Self> { - let mut is_copy: jboolean = 0xff; - Ok(AutoArray { - obj, - ptr: { - let ptr = T::get(env, obj, &mut is_copy)?; - NonNull::new(ptr).ok_or(Error::NullPtr("Non-null ptr expected"))? - }, - mode, - is_copy: is_copy == sys::JNI_TRUE, - env: *env, - }) - } - - /// Get a reference to the wrapped pointer - pub fn as_ptr(&self) -> *mut T { - self.ptr.as_ptr() - } - - /// Commits the changes to the array, if it is a copy - pub fn commit(&self) -> Result<()> { - self.release_array_elements(sys::JNI_COMMIT) - } - - fn release_array_elements(&self, mode: i32) -> Result<()> { - T::release(&self.env, self.obj, self.ptr, mode) - } - - /// Don't commit the changes to the array on release (if it is a copy). - /// This has no effect if the array is not a copy. - /// This method is useful to change the release mode of an array originally created - /// with `ReleaseMode::CopyBack`. - pub fn discard(&mut self) { - self.mode = ReleaseMode::NoCopyBack; - } - - /// Indicates if the array is a copy or not - pub fn is_copy(&self) -> bool { - self.is_copy - } - - /// Returns the array size - pub fn size(&self) -> Result<jsize> { - self.env.get_array_length(*self.obj) - } -} - -impl<'a, T: TypeArray> Drop for AutoArray<'a, T> { - fn drop(&mut self) { - let res = self.release_array_elements(self.mode as i32); - match res { - Ok(()) => {} - Err(e) => error!("error releasing array: {:#?}", e), - } - } -} - -impl<'a, T: TypeArray> From<&'a AutoArray<'a, T>> for *mut T { - fn from(other: &'a AutoArray<T>) -> *mut T { - other.as_ptr() - } -} diff --git a/src/wrapper/objects/auto_elements.rs b/src/wrapper/objects/auto_elements.rs new file mode 100644 index 0000000..f40014a --- /dev/null +++ b/src/wrapper/objects/auto_elements.rs @@ -0,0 +1,264 @@ +use log::error; +use std::ptr::NonNull; + +use crate::sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort}; +use crate::wrapper::objects::ReleaseMode; +use crate::{errors::*, sys, JNIEnv}; + +use super::JPrimitiveArray; + +#[cfg(doc)] +use super::JByteArray; + +mod type_array_sealed { + use crate::sys::{jarray, jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort}; + use crate::{errors::*, JNIEnv}; + use std::ptr::NonNull; + + /// Trait to define type array access/release + /// + /// # Safety + /// + /// The methods of this trait must uphold the invariants described in [`JNIEnv::unsafe_clone`] when + /// using the provided [`JNIEnv`]. + /// + /// The `get` method must return a valid pointer to the beginning of the JNI array. + /// + /// The `release` method must not invalidate the `ptr` if the `mode` is [`sys::JNI_COMMIT`]. + pub unsafe trait TypeArraySealed: Copy { + /// getter + /// + /// # Safety + /// + /// `array` must be a valid pointer to an `Array` object, or `null` + /// + /// The caller is responsible for passing the returned pointer to [`release`], along + /// with the same `env` and `array` reference (which needs to still be valid) + unsafe fn get(env: &mut JNIEnv, array: jarray, is_copy: &mut jboolean) + -> Result<*mut Self>; + + /// releaser + /// + /// # Safety + /// + /// `ptr` must have been previously returned by the `get` function. + /// + /// If `mode` is not [`sys::JNI_COMMIT`], `ptr` must not be used again after calling this + /// function. + unsafe fn release( + env: &mut JNIEnv, + array: jarray, + ptr: NonNull<Self>, + mode: i32, + ) -> Result<()>; + } + + // TypeArray builder + macro_rules! type_array { + ( $jni_type:ty, $jni_get:tt, $jni_release:tt ) => { + /// $jni_type array access/release impl + unsafe impl TypeArraySealed for $jni_type { + /// Get Java $jni_type array + unsafe fn get( + env: &mut JNIEnv, + array: jarray, + is_copy: &mut jboolean, + ) -> Result<*mut Self> { + let internal = env.get_native_interface(); + // Even though this method may throw OoME, use `jni_unchecked` + // instead of `jni_non_null_call` to remove (a slight) overhead + // of exception checking. An error will still be detected as a `null` + // result inside AutoElements ctor. Also, modern Hotspot in case of lack + // of memory will return null and won't throw an exception: + // https://sourcegraph.com/github.com/openjdk/jdk/-/blob/src/hotspot/share/memory/allocation.hpp#L488-489 + let res = jni_unchecked!(internal, $jni_get, array, is_copy); + Ok(res) + } + + /// Release Java $jni_type array + unsafe fn release( + env: &mut JNIEnv, + array: jarray, + ptr: NonNull<Self>, + mode: i32, + ) -> Result<()> { + let internal = env.get_native_interface(); + jni_unchecked!(internal, $jni_release, array, ptr.as_ptr(), mode as i32); + Ok(()) + } + } + }; + } + + type_array!(jint, GetIntArrayElements, ReleaseIntArrayElements); + type_array!(jlong, GetLongArrayElements, ReleaseLongArrayElements); + type_array!(jbyte, GetByteArrayElements, ReleaseByteArrayElements); + type_array!( + jboolean, + GetBooleanArrayElements, + ReleaseBooleanArrayElements + ); + type_array!(jchar, GetCharArrayElements, ReleaseCharArrayElements); + type_array!(jshort, GetShortArrayElements, ReleaseShortArrayElements); + type_array!(jfloat, GetFloatArrayElements, ReleaseFloatArrayElements); + type_array!(jdouble, GetDoubleArrayElements, ReleaseDoubleArrayElements); +} + +/// A sealed trait to define type array access/release for primitive JNI types +pub trait TypeArray: type_array_sealed::TypeArraySealed {} + +impl TypeArray for jint {} +impl TypeArray for jlong {} +impl TypeArray for jbyte {} +impl TypeArray for jboolean {} +impl TypeArray for jchar {} +impl TypeArray for jshort {} +impl TypeArray for jfloat {} +impl TypeArray for jdouble {} + +/// Auto-release wrapper for a mutable pointer to the elements of a [`JPrimitiveArray`] +/// (such as [`JByteArray`]) +/// +/// This type is used to wrap pointers returned by `Get<Type>ArrayElements` +/// and ensure the pointer is released via `Release<Type>ArrayElements` when dropped. +pub struct AutoElements<'local, 'other_local, 'array, T: TypeArray> { + array: &'array JPrimitiveArray<'other_local, T>, + len: usize, + ptr: NonNull<T>, + mode: ReleaseMode, + is_copy: bool, + env: JNIEnv<'local>, +} + +impl<'local, 'other_local, 'array, T: TypeArray> AutoElements<'local, 'other_local, 'array, T> { + /// # Safety + /// + /// `len` must be the correct length (number of elements) of the given `array` + pub(crate) unsafe fn new_with_len( + env: &mut JNIEnv<'local>, + array: &'array JPrimitiveArray<'other_local, T>, + len: usize, + mode: ReleaseMode, + ) -> Result<Self> { + // Safety: The cloned `JNIEnv` will not be used to create any local references. It will be + // passed to the methods of the `TypeArray` implementation, but that trait is `unsafe` and + // implementations are required to uphold the invariants of `unsafe_clone`. + let mut env = unsafe { env.unsafe_clone() }; + + let mut is_copy: jboolean = 0xff; + let ptr = unsafe { T::get(&mut env, array.as_raw(), &mut is_copy) }?; + Ok(AutoElements { + array, + len, + ptr: NonNull::new(ptr).ok_or(Error::NullPtr("Non-null ptr expected"))?, + mode, + is_copy: is_copy == sys::JNI_TRUE, + env, + }) + } + + pub(crate) fn new( + env: &mut JNIEnv<'local>, + array: &'array JPrimitiveArray<'other_local, T>, + mode: ReleaseMode, + ) -> Result<Self> { + let len = env.get_array_length(array)? as usize; + unsafe { Self::new_with_len(env, array, len, mode) } + } + + /// Get a reference to the wrapped pointer + pub fn as_ptr(&self) -> *mut T { + self.ptr.as_ptr() + } + + /// Commits the changes to the array, if it is a copy + pub fn commit(&mut self) -> Result<()> { + unsafe { self.release_array_elements(sys::JNI_COMMIT) } + } + + /// Calls the release function. + /// + /// # Safety + /// + /// `mode` must be a valid parameter to the JNI `Release<PrimitiveType>ArrayElements`' `mode` + /// parameter. + /// + /// If `mode` is not [`sys::JNI_COMMIT`], then `self.ptr` must not have already been released. + unsafe fn release_array_elements(&mut self, mode: i32) -> Result<()> { + T::release(&mut self.env, self.array.as_raw(), self.ptr, mode) + } + + /// Don't copy back the changes to the array on release (if it is a copy). + /// + /// This has no effect if the array is not a copy. + /// + /// This method is useful to change the release mode of an array originally created + /// with `ReleaseMode::CopyBack`. + pub fn discard(&mut self) { + self.mode = ReleaseMode::NoCopyBack; + } + + /// Indicates if the array is a copy or not + pub fn is_copy(&self) -> bool { + self.is_copy + } + + /// Returns the array length (number of elements) + pub fn len(&self) -> usize { + self.len + } + + /// Returns true if the vector contains no elements. + pub fn is_empty(&self) -> bool { + self.len == 0 + } +} + +impl<'local, 'other_local, 'array, T: TypeArray> + AsRef<AutoElements<'local, 'other_local, 'array, T>> + for AutoElements<'local, 'other_local, 'array, T> +{ + fn as_ref(&self) -> &AutoElements<'local, 'other_local, 'array, T> { + self + } +} + +impl<'local, 'other_local, 'array, T: TypeArray> Drop + for AutoElements<'local, 'other_local, 'array, T> +{ + fn drop(&mut self) { + // Safety: `self.mode` is valid and the array has not yet been released. + let res = unsafe { self.release_array_elements(self.mode as i32) }; + + match res { + Ok(()) => {} + Err(e) => error!("error releasing array: {:#?}", e), + } + } +} + +impl<'local, 'other_local, 'array, T: TypeArray> + From<&AutoElements<'local, 'other_local, 'array, T>> for *mut T +{ + fn from(other: &AutoElements<T>) -> *mut T { + other.as_ptr() + } +} + +impl<'local, 'other_local, 'array, T: TypeArray> std::ops::Deref + for AutoElements<'local, 'other_local, 'array, T> +{ + type Target = [T]; + + fn deref(&self) -> &Self::Target { + unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) } + } +} + +impl<'local, 'other_local, 'array, T: TypeArray> std::ops::DerefMut + for AutoElements<'local, 'other_local, 'array, T> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { std::slice::from_raw_parts_mut(self.ptr.as_mut(), self.len) } + } +} diff --git a/src/wrapper/objects/auto_elements_critical.rs b/src/wrapper/objects/auto_elements_critical.rs new file mode 100644 index 0000000..d089d22 --- /dev/null +++ b/src/wrapper/objects/auto_elements_critical.rs @@ -0,0 +1,168 @@ +use log::error; +use std::ptr::NonNull; + +use crate::sys::jboolean; +use crate::wrapper::objects::ReleaseMode; +use crate::{errors::*, sys, JNIEnv}; + +use super::{JPrimitiveArray, TypeArray}; + +#[cfg(doc)] +use super::JByteArray; + +/// Auto-release wrapper for a mutable pointer to the elements of a [`JPrimitiveArray`] +/// (such as [`JByteArray`]) +/// +/// This type is used to wrap pointers returned by `GetPrimitiveArrayCritical` +/// and ensure the pointer is released via `ReleasePrimitiveArrayCritical` when dropped. +pub struct AutoElementsCritical<'local, 'other_local, 'array, 'env, T: TypeArray> { + array: &'array JPrimitiveArray<'other_local, T>, + len: usize, + ptr: NonNull<T>, + mode: ReleaseMode, + is_copy: bool, + env: &'env mut JNIEnv<'local>, +} + +impl<'local, 'other_local, 'array, 'env, T: TypeArray> + AutoElementsCritical<'local, 'other_local, 'array, 'env, T> +{ + /// # Safety + /// + /// `len` must be the correct length (number of elements) of the given `array` + pub(crate) unsafe fn new_with_len( + env: &'env mut JNIEnv<'local>, + array: &'array JPrimitiveArray<'other_local, T>, + len: usize, + mode: ReleaseMode, + ) -> Result<Self> { + let mut is_copy: jboolean = 0xff; + // Even though this method may throw OoME, use `jni_unchecked` + // instead of `jni_non_null_call` to remove (a slight) overhead + // of exception checking. An error will still be detected as a `null` + // result below; and, as this method is unlikely to create a copy, + // an OoME is highly unlikely. + let ptr = jni_unchecked!( + env.get_native_interface(), + GetPrimitiveArrayCritical, + array.as_raw(), + &mut is_copy + ) as *mut T; + + Ok(AutoElementsCritical { + array, + len, + ptr: NonNull::new(ptr).ok_or(Error::NullPtr("Non-null ptr expected"))?, + mode, + is_copy: is_copy == sys::JNI_TRUE, + env, + }) + } + + pub(crate) fn new( + env: &'env mut JNIEnv<'local>, + array: &'array JPrimitiveArray<'other_local, T>, + mode: ReleaseMode, + ) -> Result<Self> { + let len = env.get_array_length(array)? as usize; + unsafe { Self::new_with_len(env, array, len, mode) } + } + + /// Get a reference to the wrapped pointer + pub fn as_ptr(&self) -> *mut T { + self.ptr.as_ptr() + } + + /// Calls `ReleasePrimitiveArrayCritical`. + /// + /// # Safety + /// + /// `mode` must be a valid parameter to the JNI `ReleasePrimitiveArrayCritical` `mode` + /// parameter. + /// + /// If `mode` is not [`sys::JNI_COMMIT`], then `self.ptr` must not have already been released. + unsafe fn release_primitive_array_critical(&mut self, mode: i32) -> Result<()> { + jni_unchecked!( + self.env.get_native_interface(), + ReleasePrimitiveArrayCritical, + self.array.as_raw(), + self.ptr.as_ptr().cast(), + mode + ); + Ok(()) + } + + /// Don't copy back the changes to the array on release (if it is a copy). + /// + /// This has no effect if the array is not a copy. + /// + /// This method is useful to change the release mode of an array originally created + /// with `ReleaseMode::CopyBack`. + pub fn discard(&mut self) { + self.mode = ReleaseMode::NoCopyBack; + } + + /// Indicates if the array is a copy or not + pub fn is_copy(&self) -> bool { + self.is_copy + } + + /// Returns the array length (number of elements) + pub fn len(&self) -> usize { + self.len + } + + /// Returns true if the vector contains no elements. + pub fn is_empty(&self) -> bool { + self.len == 0 + } +} + +impl<'local, 'other_local, 'array, 'env, T: TypeArray> + AsRef<AutoElementsCritical<'local, 'other_local, 'array, 'env, T>> + for AutoElementsCritical<'local, 'other_local, 'array, 'env, T> +{ + fn as_ref(&self) -> &AutoElementsCritical<'local, 'other_local, 'array, 'env, T> { + self + } +} + +impl<'local, 'other_local, 'array, 'env, T: TypeArray> Drop + for AutoElementsCritical<'local, 'other_local, 'array, 'env, T> +{ + fn drop(&mut self) { + // Safety: `self.mode` is valid and the array has not yet been released. + let res = unsafe { self.release_primitive_array_critical(self.mode as i32) }; + + match res { + Ok(()) => {} + Err(e) => error!("error releasing primitive array: {:#?}", e), + } + } +} + +impl<'local, 'other_local, 'array, 'env, T: TypeArray> + From<&AutoElementsCritical<'local, 'other_local, 'array, 'env, T>> for *mut T +{ + fn from(other: &AutoElementsCritical<T>) -> *mut T { + other.as_ptr() + } +} + +impl<'local, 'other_local, 'array, 'env, T: TypeArray> std::ops::Deref + for AutoElementsCritical<'local, 'other_local, 'array, 'env, T> +{ + type Target = [T]; + + fn deref(&self) -> &Self::Target { + unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) } + } +} + +impl<'local, 'other_local, 'array, 'env, T: TypeArray> std::ops::DerefMut + for AutoElementsCritical<'local, 'other_local, 'array, 'env, T> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { std::slice::from_raw_parts_mut(self.ptr.as_mut(), self.len) } + } +} diff --git a/src/wrapper/objects/auto_local.rs b/src/wrapper/objects/auto_local.rs index 6d6cd2f..19c3279 100644 --- a/src/wrapper/objects/auto_local.rs +++ b/src/wrapper/objects/auto_local.rs @@ -1,4 +1,8 @@ -use std::mem; +use std::{ + mem::ManuallyDrop, + ops::{Deref, DerefMut}, + ptr, +}; use log::debug; @@ -14,28 +18,41 @@ use crate::{objects::JObject, JNIEnv}; /// This wrapper provides automatic local ref deletion when it goes out of /// scope. /// -/// NOTE: This comes with some potential safety risks. DO NOT use this to wrap -/// something unless you're SURE it won't be used after this wrapper gets -/// dropped. Otherwise, you'll get a nasty JVM crash. -/// /// See also the [JNI specification][spec-references] for details on referencing Java objects /// and some [extra information][android-jni-references]. /// /// [spec-references]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/design.html#referencing-java-objects /// [android-jni-references]: https://developer.android.com/training/articles/perf-jni#local-and-global-references -pub struct AutoLocal<'a: 'b, 'b> { - obj: JObject<'a>, - env: &'b JNIEnv<'a>, +#[derive(Debug)] +pub struct AutoLocal<'local, T> +where + T: Into<JObject<'local>>, +{ + obj: ManuallyDrop<T>, + env: JNIEnv<'local>, } -impl<'a, 'b> AutoLocal<'a, 'b> { +impl<'local, T> AutoLocal<'local, T> +where + // Note that this bound prevents `AutoLocal` from wrapping a `GlobalRef`, which implements + // `AsRef<JObject<'static>>` but *not* `Into<JObject<'static>>`. This is good, because trying + // to delete a global reference as though it were local would cause undefined behavior. + T: Into<JObject<'local>>, +{ /// Creates a new auto-delete wrapper for a local ref. /// /// Once this wrapper goes out of scope, the `delete_local_ref` will be /// called on the object. While wrapped, the object can be accessed via /// the `Deref` impl. - pub fn new(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Self { - AutoLocal { obj, env } + pub fn new(obj: T, env: &JNIEnv<'local>) -> Self { + // Safety: The cloned `JNIEnv` will not be used to create any local references, only to + // delete one. + let env = unsafe { env.unsafe_clone() }; + + AutoLocal { + obj: ManuallyDrop::new(obj), + env, + } } /// Forget the wrapper, returning the original object. @@ -45,27 +62,54 @@ impl<'a, 'b> AutoLocal<'a, 'b> { /// dropped. You must either remember to delete the local ref manually, or /// be /// ok with it getting deleted once the foreign method returns. - pub fn forget(self) -> JObject<'a> { - let obj = self.obj; - mem::forget(self); - obj - } + pub fn forget(self) -> T { + // We need to move `self.obj` out of `self`. Normally that's trivial, but moving out of a + // type with a `Drop` implementation is not allowed. We'll have to do it manually (and + // carefully) with `unsafe`. + // + // This could be done without `unsafe` by adding `where T: Default` and using + // `std::mem::replace` to extract `self.obj`, but doing it this way avoids unnecessarily + // running the drop routine on `self`. - /// Get a reference to the wrapped object - /// - /// Unlike `forget`, this ensures the wrapper from being dropped while the - /// returned `JObject` is still live. - pub fn as_obj<'c>(&self) -> JObject<'c> - where - 'a: 'c, - { - self.obj + // Before we mutilate `self`, make sure its drop code will not be automatically run. That + // would cause undefined behavior. + let mut self_md = ManuallyDrop::new(self); + + unsafe { + // Drop the `JNIEnv` in place. As of this writing, that's a no-op, but if `JNIEnv` + // gains any drop code in the future, this will run it. + // + // Safety: The `&mut` proves that `self_md.env` is valid and not aliased. It is not + // accessed again after this point. It is wrapped inside `ManuallyDrop`, and will + // therefore not be dropped twice. + ptr::drop_in_place(&mut self_md.env); + + // Move `obj` out of `self` and return it. + // + // Safety: The `&mut` proves that `self_md.obj` is valid and not aliased. It is not + // accessed again after this point. It is wrapped inside `ManuallyDrop`, and will + // therefore not be dropped after it is moved. + ptr::read(&*self_md.obj) + } } } -impl<'a, 'b> Drop for AutoLocal<'a, 'b> { +impl<'local, T> Drop for AutoLocal<'local, T> +where + T: Into<JObject<'local>>, +{ fn drop(&mut self) { - let res = self.env.delete_local_ref(self.obj); + // Extract the local reference from `self.obj` so that we can delete it. + // + // This is needed because it is not allowed to move out of `self` during drop. A safe + // alternative would be to wrap `self.obj` in `Option`, but that would incur a run-time + // performance penalty from constantly checking if it's `None`. + // + // Safety: `self.obj` is not used again after this `take` call. + let obj = unsafe { ManuallyDrop::take(&mut self.obj) }; + + // Delete the extracted local reference. + let res = self.env.delete_local_ref(obj); match res { Ok(()) => {} Err(e) => debug!("error dropping global ref: {:#?}", e), @@ -73,8 +117,40 @@ impl<'a, 'b> Drop for AutoLocal<'a, 'b> { } } -impl<'a> From<&'a AutoLocal<'a, '_>> for JObject<'a> { - fn from(other: &'a AutoLocal) -> JObject<'a> { - other.as_obj() +impl<'local, T, U> AsRef<U> for AutoLocal<'local, T> +where + T: AsRef<U> + Into<JObject<'local>>, +{ + fn as_ref(&self) -> &U { + self.obj.as_ref() + } +} + +impl<'local, T, U> AsMut<U> for AutoLocal<'local, T> +where + T: AsMut<U> + Into<JObject<'local>>, +{ + fn as_mut(&mut self) -> &mut U { + self.obj.as_mut() + } +} + +impl<'local, T> Deref for AutoLocal<'local, T> +where + T: Into<JObject<'local>>, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.obj + } +} + +impl<'local, T> DerefMut for AutoLocal<'local, T> +where + T: Into<JObject<'local>>, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.obj } } diff --git a/src/wrapper/objects/auto_primitive_array.rs b/src/wrapper/objects/auto_primitive_array.rs deleted file mode 100644 index d67b1d7..0000000 --- a/src/wrapper/objects/auto_primitive_array.rs +++ /dev/null @@ -1,90 +0,0 @@ -use log::debug; - -use crate::sys::jsize; -use crate::wrapper::objects::ReleaseMode; -use crate::{errors::*, objects::JObject, JNIEnv}; -use std::os::raw::c_void; -use std::ptr::NonNull; - -/// Auto-release wrapper for pointer-based primitive arrays. -/// -/// This wrapper is used to wrap pointers returned by GetPrimitiveArrayCritical. -/// While wrapped, the object can be accessed via the `From` impl. -/// -/// AutoPrimitiveArray provides automatic array release through a call to -/// ReleasePrimitiveArrayCritical when it goes out of scope. -pub struct AutoPrimitiveArray<'a: 'b, 'b> { - obj: JObject<'a>, - ptr: NonNull<c_void>, - mode: ReleaseMode, - is_copy: bool, - env: &'b JNIEnv<'a>, -} - -impl<'a, 'b> AutoPrimitiveArray<'a, 'b> { - pub(crate) fn new( - env: &'b JNIEnv<'a>, - obj: JObject<'a>, - ptr: *mut c_void, - mode: ReleaseMode, - is_copy: bool, - ) -> Result<Self> { - Ok(AutoPrimitiveArray { - obj, - ptr: NonNull::new(ptr).ok_or(Error::NullPtr("Non-null ptr expected"))?, - mode, - is_copy, - env, - }) - } - - /// Get a reference to the wrapped pointer - pub fn as_ptr(&self) -> *mut c_void { - self.ptr.as_ptr() - } - - fn release_primitive_array_critical(&mut self, mode: i32) -> Result<()> { - jni_unchecked!( - self.env.get_native_interface(), - ReleasePrimitiveArrayCritical, - *self.obj, - self.ptr.as_mut(), - mode - ); - Ok(()) - } - - /// Don't copy the changes to the array on release (if it is a copy). - /// This has no effect if the array is not a copy. - /// This method is useful to change the release mode of an array originally created - /// with `ReleaseMode::CopyBack`. - pub fn discard(&mut self) { - self.mode = ReleaseMode::NoCopyBack; - } - - /// Indicates if the array is a copy or not - pub fn is_copy(&self) -> bool { - self.is_copy - } - - /// Returns the array size - pub fn size(&self) -> Result<jsize> { - self.env.get_array_length(*self.obj) - } -} - -impl<'a, 'b> Drop for AutoPrimitiveArray<'a, 'b> { - fn drop(&mut self) { - let res = self.release_primitive_array_critical(self.mode as i32); - match res { - Ok(()) => {} - Err(e) => debug!("error releasing primitive array: {:#?}", e), - } - } -} - -impl<'a> From<&'a AutoPrimitiveArray<'a, '_>> for *mut c_void { - fn from(other: &'a AutoPrimitiveArray) -> *mut c_void { - other.as_ptr() - } -} diff --git a/src/wrapper/objects/global_ref.rs b/src/wrapper/objects/global_ref.rs index 7e8b81c..034c1b8 100644 --- a/src/wrapper/objects/global_ref.rs +++ b/src/wrapper/objects/global_ref.rs @@ -1,9 +1,13 @@ -use std::{convert::From, sync::Arc}; +use std::{mem, ops::Deref, sync::Arc}; use log::{debug, warn}; use crate::{errors::Result, objects::JObject, sys, JNIEnv, JavaVM}; +// Note: `GlobalRef` must not implement `Into<JObject>`! If it did, then it would be possible to +// wrap it in `AutoLocal`, which would cause undefined behavior upon drop as a result of calling +// the wrong JNI function to delete the reference. + /// A global JVM reference. These are "pinned" by the garbage collector and are /// guaranteed to not get collected until released. Thus, this is allowed to /// outlive the `JNIEnv` that it came from and can be used in other threads. @@ -32,12 +36,23 @@ struct GlobalRefGuard { vm: JavaVM, } -unsafe impl Send for GlobalRef {} -unsafe impl Sync for GlobalRef {} +impl AsRef<GlobalRef> for GlobalRef { + fn as_ref(&self) -> &GlobalRef { + self + } +} + +impl AsRef<JObject<'static>> for GlobalRef { + fn as_ref(&self) -> &JObject<'static> { + self + } +} -impl<'a> From<&'a GlobalRef> for JObject<'a> { - fn from(other: &'a GlobalRef) -> JObject<'a> { - other.as_obj() +impl Deref for GlobalRef { + type Target = JObject<'static>; + + fn deref(&self) -> &Self::Target { + &self.inner.obj } } @@ -57,8 +72,8 @@ impl GlobalRef { /// /// This borrows the ref and prevents it from being dropped as long as the /// JObject sticks around. - pub fn as_obj(&self) -> JObject { - self.inner.as_obj() + pub fn as_obj(&self) -> &JObject<'static> { + self.as_ref() } } @@ -71,32 +86,26 @@ impl GlobalRefGuard { vm, } } - - /// Get the object from the global ref - /// - /// This borrows the ref and prevents it from being dropped as long as the - /// JObject sticks around. - pub fn as_obj(&self) -> JObject { - self.obj - } } impl Drop for GlobalRefGuard { fn drop(&mut self) { - fn drop_impl(env: &JNIEnv, global_ref: JObject) -> Result<()> { + let raw: sys::jobject = mem::take(&mut self.obj).into_raw(); + + let drop_impl = |env: &JNIEnv| -> Result<()> { let internal = env.get_native_interface(); // This method is safe to call in case of pending exceptions (see chapter 2 of the spec) - jni_unchecked!(internal, DeleteGlobalRef, global_ref.into_raw()); + jni_unchecked!(internal, DeleteGlobalRef, raw); Ok(()) - } + }; let res = match self.vm.get_env() { - Ok(env) => drop_impl(&env, self.as_obj()), + Ok(env) => drop_impl(&env), Err(_) => { warn!("Dropping a GlobalRef in a detached thread. Fix your code if this message appears frequently (see the GlobalRef docs)."); self.vm .attach_current_thread() - .and_then(|env| drop_impl(&env, self.as_obj())) + .and_then(|env| drop_impl(&env)) } }; diff --git a/src/wrapper/objects/jbytebuffer.rs b/src/wrapper/objects/jbytebuffer.rs index fd6cb60..0a5d4b1 100644 --- a/src/wrapper/objects/jbytebuffer.rs +++ b/src/wrapper/objects/jbytebuffer.rs @@ -3,36 +3,55 @@ use crate::{objects::JObject, sys::jobject}; /// Lifetime'd representation of a `jobject` that is an instance of the /// ByteBuffer Java class. Just a `JObject` wrapped in a new class. #[repr(transparent)] -#[derive(Clone, Copy, Debug)] -pub struct JByteBuffer<'a>(JObject<'a>); +#[derive(Debug)] +pub struct JByteBuffer<'local>(JObject<'local>); -impl<'a> ::std::ops::Deref for JByteBuffer<'a> { - type Target = JObject<'a>; +impl<'local> AsRef<JByteBuffer<'local>> for JByteBuffer<'local> { + fn as_ref(&self) -> &JByteBuffer<'local> { + self + } +} + +impl<'local> AsRef<JObject<'local>> for JByteBuffer<'local> { + fn as_ref(&self) -> &JObject<'local> { + self + } +} + +impl<'local> ::std::ops::Deref for JByteBuffer<'local> { + type Target = JObject<'local>; fn deref(&self) -> &Self::Target { &self.0 } } -impl<'a> From<JByteBuffer<'a>> for JObject<'a> { +impl<'local> From<JByteBuffer<'local>> for JObject<'local> { fn from(other: JByteBuffer) -> JObject { other.0 } } -impl<'a> From<JObject<'a>> for JByteBuffer<'a> { +impl<'local> From<JObject<'local>> for JByteBuffer<'local> { fn from(other: JObject) -> Self { unsafe { Self::from_raw(other.into_raw()) } } } -impl<'a> std::default::Default for JByteBuffer<'a> { +impl<'local, 'obj_ref> From<&'obj_ref JObject<'local>> for &'obj_ref JByteBuffer<'local> { + fn from(other: &'obj_ref JObject<'local>) -> Self { + // Safety: `JByteBuffer` is `repr(transparent)` around `JObject`. + unsafe { &*(other as *const JObject<'local> as *const JByteBuffer<'local>) } + } +} + +impl<'local> std::default::Default for JByteBuffer<'local> { fn default() -> Self { Self(JObject::null()) } } -impl<'a> JByteBuffer<'a> { +impl<'local> JByteBuffer<'local> { /// Creates a [`JByteBuffer`] that wraps the given `raw` [`jobject`] /// /// # Safety diff --git a/src/wrapper/objects/jclass.rs b/src/wrapper/objects/jclass.rs index aa7194b..afa71de 100644 --- a/src/wrapper/objects/jclass.rs +++ b/src/wrapper/objects/jclass.rs @@ -6,46 +6,76 @@ use crate::{ /// Lifetime'd representation of a `jclass`. Just a `JObject` wrapped in a new /// class. #[repr(transparent)] -#[derive(Clone, Copy, Debug)] -pub struct JClass<'a>(JObject<'a>); +#[derive(Debug)] +pub struct JClass<'local>(JObject<'local>); -impl<'a> ::std::ops::Deref for JClass<'a> { - type Target = JObject<'a>; +impl<'local> AsRef<JClass<'local>> for JClass<'local> { + fn as_ref(&self) -> &JClass<'local> { + self + } +} + +impl<'local> AsRef<JObject<'local>> for JClass<'local> { + fn as_ref(&self) -> &JObject<'local> { + self + } +} + +impl<'local> ::std::ops::Deref for JClass<'local> { + type Target = JObject<'local>; fn deref(&self) -> &Self::Target { &self.0 } } -impl<'a> From<JClass<'a>> for JObject<'a> { +impl<'local> From<JClass<'local>> for JObject<'local> { fn from(other: JClass) -> JObject { other.0 } } /// This conversion assumes that the `JObject` is a pointer to a class object. -impl<'a> From<JObject<'a>> for JClass<'a> { +impl<'local> From<JObject<'local>> for JClass<'local> { fn from(other: JObject) -> Self { unsafe { Self::from_raw(other.into_raw()) } } } -impl<'a> std::default::Default for JClass<'a> { +/// This conversion assumes that the `JObject` is a pointer to a class object. +impl<'local, 'obj_ref> From<&'obj_ref JObject<'local>> for &'obj_ref JClass<'local> { + fn from(other: &'obj_ref JObject<'local>) -> Self { + // Safety: `JClass` is `repr(transparent)` around `JObject`. + unsafe { &*(other as *const JObject<'local> as *const JClass<'local>) } + } +} + +impl<'local> std::default::Default for JClass<'local> { fn default() -> Self { Self(JObject::null()) } } -impl<'a> JClass<'a> { +impl<'local> JClass<'local> { /// Creates a [`JClass`] that wraps the given `raw` [`jclass`] /// /// # Safety /// - /// Expects a valid pointer or `null` + /// `raw` may be a null pointer. If `raw` is not a null pointer, then: + /// + /// * `raw` must be a valid raw JNI local reference. + /// * There must not be any other `JObject` representing the same local reference. + /// * The lifetime `'local` must not outlive the local reference frame that the local reference + /// was created in. pub unsafe fn from_raw(raw: jclass) -> Self { Self(JObject::from_raw(raw as jobject)) } + /// Returns the raw JNI pointer. + pub fn as_raw(&self) -> jclass { + self.0.as_raw() as jclass + } + /// Unwrap to the raw jni type. pub fn into_raw(self) -> jclass { self.0.into_raw() as jclass diff --git a/src/wrapper/objects/jfieldid.rs b/src/wrapper/objects/jfieldid.rs index c445ec6..e6a6bbc 100644 --- a/src/wrapper/objects/jfieldid.rs +++ b/src/wrapper/objects/jfieldid.rs @@ -46,3 +46,15 @@ impl JFieldID { self.internal } } + +impl AsRef<JFieldID> for JFieldID { + fn as_ref(&self) -> &JFieldID { + self + } +} + +impl AsMut<JFieldID> for JFieldID { + fn as_mut(&mut self) -> &mut JFieldID { + self + } +} diff --git a/src/wrapper/objects/jlist.rs b/src/wrapper/objects/jlist.rs index 1f07f71..406c5ef 100644 --- a/src/wrapper/objects/jlist.rs +++ b/src/wrapper/objects/jlist.rs @@ -1,46 +1,53 @@ use crate::{ errors::*, - objects::{JMethodID, JObject, JValue}, + objects::{AutoLocal, JClass, JMethodID, JObject, JValue}, signature::{Primitive, ReturnType}, sys::jint, JNIEnv, }; +use std::marker::PhantomData; + /// Wrapper for JObjects that implement `java/util/List`. Provides methods to get, /// add, and remove elements. /// /// Looks up the class and method ids on creation rather than for every method /// call. -pub struct JList<'a: 'b, 'b> { - internal: JObject<'a>, +pub struct JList<'local, 'other_local_1: 'obj_ref, 'obj_ref> { + internal: &'obj_ref JObject<'other_local_1>, + _phantom_class: PhantomData<AutoLocal<'local, JClass<'local>>>, get: JMethodID, add: JMethodID, add_idx: JMethodID, remove: JMethodID, size: JMethodID, - env: &'b JNIEnv<'a>, } -impl<'a: 'b, 'b> ::std::ops::Deref for JList<'a, 'b> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal +impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef<JList<'local, 'other_local_1, 'obj_ref>> + for JList<'local, 'other_local_1, 'obj_ref> +{ + fn as_ref(&self) -> &JList<'local, 'other_local_1, 'obj_ref> { + self } } -impl<'a: 'b, 'b> From<JList<'a, 'b>> for JObject<'a> { - fn from(other: JList<'a, 'b>) -> JObject<'a> { - other.internal +impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef<JObject<'other_local_1>> + for JList<'local, 'other_local_1, 'obj_ref> +{ + fn as_ref(&self) -> &JObject<'other_local_1> { + self.internal } } -impl<'a: 'b, 'b> JList<'a, 'b> { +impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> JList<'local, 'other_local_1, 'obj_ref> { /// Create a map from the environment and an object. This looks up the /// necessary class and method ids to call all of the methods on it so that /// exra work doesn't need to be done on every method call. - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result<JList<'a, 'b>> { - let class = env.auto_local(env.find_class("java/util/List")?); + pub fn from_env( + env: &mut JNIEnv<'local>, + obj: &'obj_ref JObject<'other_local_1>, + ) -> Result<JList<'local, 'other_local_1, 'obj_ref>> { + let class = AutoLocal::new(env.find_class("java/util/List")?, env); let get = env.get_method_id(&class, "get", "(I)Ljava/lang/Object;")?; let add = env.get_method_id(&class, "add", "(Ljava/lang/Object;)Z")?; @@ -50,24 +57,32 @@ impl<'a: 'b, 'b> JList<'a, 'b> { Ok(JList { internal: obj, + _phantom_class: PhantomData, get, add, add_idx, remove, size, - env, }) } /// Look up the value for a key. Returns `Some` if it's found and `None` if /// a null pointer would be returned. - pub fn get(&self, idx: jint) -> Result<Option<JObject<'a>>> { - let result = self.env.call_method_unchecked( - self.internal, - self.get, - ReturnType::Object, - &[JValue::from(idx).to_jni()], - ); + pub fn get<'other_local_2>( + &self, + env: &mut JNIEnv<'other_local_2>, + idx: jint, + ) -> Result<Option<JObject<'other_local_2>>> { + // SAFETY: We keep the class loaded, and fetched the method ID for this function. + // Provided argument is statically known as a JObject/null, rather than another primitive type. + let result = unsafe { + env.call_method_unchecked( + self.internal, + self.get, + ReturnType::Object, + &[JValue::from(idx).as_jni()], + ) + }; match result { Ok(val) => Ok(Some(val.l()?)), @@ -79,39 +94,55 @@ impl<'a: 'b, 'b> JList<'a, 'b> { } /// Append an element to the list - pub fn add(&self, value: JObject<'a>) -> Result<()> { - let result = self.env.call_method_unchecked( - self.internal, - self.add, - ReturnType::Primitive(Primitive::Boolean), - &[JValue::from(value).to_jni()], - ); + pub fn add(&self, env: &mut JNIEnv, value: &JObject) -> Result<()> { + // SAFETY: We keep the class loaded, and fetched the method ID for this function. + // Provided argument is statically known as a JObject/null, rather than another primitive type. + let result = unsafe { + env.call_method_unchecked( + self.internal, + self.add, + ReturnType::Primitive(Primitive::Boolean), + &[JValue::from(value).as_jni()], + ) + }; let _ = result?; Ok(()) } /// Insert an element at a specific index - pub fn insert(&self, idx: jint, value: JObject<'a>) -> Result<()> { - let result = self.env.call_method_unchecked( - self.internal, - self.add_idx, - ReturnType::Primitive(Primitive::Void), - &[JValue::from(idx).to_jni(), JValue::from(value).to_jni()], - ); + pub fn insert(&self, env: &mut JNIEnv, idx: jint, value: &JObject) -> Result<()> { + // SAFETY: We keep the class loaded, and fetched the method ID for this function. + // Provided argument is statically known as a JObject/null, rather than another primitive type. + let result = unsafe { + env.call_method_unchecked( + self.internal, + self.add_idx, + ReturnType::Primitive(Primitive::Void), + &[JValue::from(idx).as_jni(), JValue::from(value).as_jni()], + ) + }; let _ = result?; Ok(()) } /// Remove an element from the list by index - pub fn remove(&self, idx: jint) -> Result<Option<JObject<'a>>> { - let result = self.env.call_method_unchecked( - self.internal, - self.remove, - ReturnType::Object, - &[JValue::from(idx).to_jni()], - ); + pub fn remove<'other_local_2>( + &self, + env: &mut JNIEnv<'other_local_2>, + idx: jint, + ) -> Result<Option<JObject<'other_local_2>>> { + // SAFETY: We keep the class loaded, and fetched the method ID for this function. + // Provided argument is statically known as a int, rather than any other java type. + let result = unsafe { + env.call_method_unchecked( + self.internal, + self.remove, + ReturnType::Object, + &[JValue::from(idx).as_jni()], + ) + }; match result { Ok(val) => Ok(Some(val.l()?)), @@ -123,13 +154,16 @@ impl<'a: 'b, 'b> JList<'a, 'b> { } /// Get the size of the list - pub fn size(&self) -> Result<jint> { - let result = self.env.call_method_unchecked( - self.internal, - self.size, - ReturnType::Primitive(Primitive::Int), - &[], - ); + pub fn size(&self, env: &mut JNIEnv) -> Result<jint> { + // SAFETY: We keep the class loaded, and fetched the method ID for this function. + let result = unsafe { + env.call_method_unchecked( + self.internal, + self.size, + ReturnType::Primitive(Primitive::Int), + &[], + ) + }; result.and_then(|v| v.i()) } @@ -137,18 +171,25 @@ impl<'a: 'b, 'b> JList<'a, 'b> { /// Pop the last element from the list /// /// Note that this calls `size()` to determine the last index. - pub fn pop(&self) -> Result<Option<JObject<'a>>> { - let size = self.size()?; + pub fn pop<'other_local_2>( + &self, + env: &mut JNIEnv<'other_local_2>, + ) -> Result<Option<JObject<'other_local_2>>> { + let size = self.size(env)?; if size == 0 { return Ok(None); } - let result = self.env.call_method_unchecked( - self.internal, - self.remove, - ReturnType::Object, - &[JValue::from(size - 1).to_jni()], - ); + // SAFETY: We keep the class loaded, and fetched the method ID for this function. + // Provided argument is statically known as a int. + let result = unsafe { + env.call_method_unchecked( + self.internal, + self.remove, + ReturnType::Object, + &[JValue::from(size - 1).as_jni()], + ) + }; match result { Ok(val) => Ok(Some(val.l()?)), @@ -161,42 +202,98 @@ impl<'a: 'b, 'b> JList<'a, 'b> { /// Get key/value iterator for the map. This is done by getting the /// `EntrySet` from java and iterating over it. - pub fn iter(&self) -> Result<JListIter<'a, 'b, '_>> { + /// + /// The returned iterator does not implement [`std::iter::Iterator`] and + /// cannot be used with a `for` loop. This is because its `next` method + /// uses a `&mut JNIEnv` to call the Java iterator. Use a `while let` loop + /// instead: + /// + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv, objects::{AutoLocal, JList, JObject}}; + /// # + /// # fn example(env: &mut JNIEnv, list: JList) -> Result<()> { + /// let mut iterator = list.iter(env)?; + /// + /// while let Some(obj) = iterator.next(env)? { + /// let obj: AutoLocal<JObject> = env.auto_local(obj); + /// + /// // Do something with `obj` here. + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// Each call to `next` creates a new local reference. To prevent excessive + /// memory usage or overflow error, the local reference should be deleted + /// using [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`] before the + /// next loop iteration. Alternatively, if the list is known to have a + /// small, predictable size, the loop could be wrapped in + /// [`JNIEnv::with_local_frame`] to delete all of the local references at + /// once. + pub fn iter<'list>( + &'list self, + env: &mut JNIEnv, + ) -> Result<JListIter<'list, 'local, 'obj_ref, 'other_local_1>> { Ok(JListIter { list: self, current: 0, - size: self.size()?, + size: self.size(env)?, }) } } -/// An iterator over the keys and values in a map. +/// An iterator over the keys and values in a `java.util.List`. See +/// [`JList::iter`] for more information. /// /// TODO: make the iterator implementation for java iterators its own thing /// and generic enough to use elsewhere. -pub struct JListIter<'a: 'b, 'b: 'c, 'c> { - list: &'c JList<'a, 'b>, +pub struct JListIter<'list, 'local, 'other_local_1: 'obj_ref, 'obj_ref> { + list: &'list JList<'local, 'other_local_1, 'obj_ref>, current: jint, size: jint, } -impl<'a: 'b, 'b: 'c, 'c> Iterator for JListIter<'a, 'b, 'c> { - type Item = JObject<'a>; - - fn next(&mut self) -> Option<Self::Item> { +impl<'list, 'local, 'other_local_1: 'obj_ref, 'obj_ref> + JListIter<'list, 'local, 'other_local_1, 'obj_ref> +{ + /// Advances the iterator and returns the next object in the + /// `java.util.List`, or `None` if there are no more objects. + /// + /// See [`JList::iter`] for more information. + /// + /// This method creates a new local reference. To prevent excessive memory + /// usage or overflow error, the local reference should be deleted using + /// [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`] before the next + /// loop iteration. Alternatively, if the list is known to have a small, + /// predictable size, the loop could be wrapped in + /// [`JNIEnv::with_local_frame`] to delete all of the local references at + /// once. + /// + /// This method returns: + /// + /// * `Ok(Some(_))`: if there was another object in the list. + /// * `Ok(None)`: if there are no more objects in the list. + /// * `Err(_)`: if there was an error calling the Java method to + /// get the next object. + /// + /// This is like [`std::iter::Iterator::next`], but requires a parameter of + /// type `&mut JNIEnv` in order to call into Java. + pub fn next<'other_local_2>( + &mut self, + env: &mut JNIEnv<'other_local_2>, + ) -> Result<Option<JObject<'other_local_2>>> { if self.current == self.size { - return None; - } - let res = self.list.get(self.current); - match res { - Ok(elem) => { - self.current += 1; - elem - } - Err(_) => { - self.current = self.size; - None - } + return Ok(None); } + + let res = self.list.get(env, self.current); + + self.current = match &res { + Ok(Some(_)) => self.current + 1, + Ok(None) => self.current, + Err(_) => self.size, + }; + + res } } diff --git a/src/wrapper/objects/jmap.rs b/src/wrapper/objects/jmap.rs index bc391e3..278b91f 100644 --- a/src/wrapper/objects/jmap.rs +++ b/src/wrapper/objects/jmap.rs @@ -1,44 +1,50 @@ use crate::{ errors::*, - objects::{AutoLocal, JMethodID, JObject, JValue}, + objects::{AutoLocal, JClass, JMethodID, JObject, JValue}, signature::{Primitive, ReturnType}, JNIEnv, }; +use std::marker::PhantomData; + /// Wrapper for JObjects that implement `java/util/Map`. Provides methods to get /// and set entries and a way to iterate over key/value pairs. /// /// Looks up the class and method ids on creation rather than for every method /// call. -pub struct JMap<'a: 'b, 'b> { - internal: JObject<'a>, - class: AutoLocal<'a, 'b>, +pub struct JMap<'local, 'other_local_1: 'obj_ref, 'obj_ref> { + internal: &'obj_ref JObject<'other_local_1>, + class: AutoLocal<'local, JClass<'local>>, get: JMethodID, put: JMethodID, remove: JMethodID, - env: &'b JNIEnv<'a>, } -impl<'a: 'b, 'b> ::std::ops::Deref for JMap<'a, 'b> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal +impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef<JMap<'local, 'other_local_1, 'obj_ref>> + for JMap<'local, 'other_local_1, 'obj_ref> +{ + fn as_ref(&self) -> &JMap<'local, 'other_local_1, 'obj_ref> { + self } } -impl<'a: 'b, 'b> From<JMap<'a, 'b>> for JObject<'a> { - fn from(other: JMap<'a, 'b>) -> JObject<'a> { - other.internal +impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef<JObject<'other_local_1>> + for JMap<'local, 'other_local_1, 'obj_ref> +{ + fn as_ref(&self) -> &JObject<'other_local_1> { + self.internal } } -impl<'a: 'b, 'b> JMap<'a, 'b> { +impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> JMap<'local, 'other_local_1, 'obj_ref> { /// Create a map from the environment and an object. This looks up the /// necessary class and method ids to call all of the methods on it so that /// exra work doesn't need to be done on every method call. - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result<JMap<'a, 'b>> { - let class = env.auto_local(env.find_class("java/util/Map")?); + pub fn from_env( + env: &mut JNIEnv<'local>, + obj: &'obj_ref JObject<'other_local_1>, + ) -> Result<JMap<'local, 'other_local_1, 'obj_ref>> { + let class = AutoLocal::new(env.find_class("java/util/Map")?, env); let get = env.get_method_id(&class, "get", "(Ljava/lang/Object;)Ljava/lang/Object;")?; let put = env.get_method_id( @@ -57,19 +63,26 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { get, put, remove, - env, }) } /// Look up the value for a key. Returns `Some` if it's found and `None` if /// a null pointer would be returned. - pub fn get(&self, key: JObject<'a>) -> Result<Option<JObject<'a>>> { - let result = self.env.call_method_unchecked( - self.internal, - self.get, - ReturnType::Object, - &[JValue::from(key).to_jni()], - ); + pub fn get<'other_local_2>( + &self, + env: &mut JNIEnv<'other_local_2>, + key: &JObject, + ) -> Result<Option<JObject<'other_local_2>>> { + // SAFETY: We keep the class loaded, and fetched the method ID for this function. + // Provided argument is statically known as a JObject/null, rather than another primitive type. + let result = unsafe { + env.call_method_unchecked( + self.internal, + self.get, + ReturnType::Object, + &[JValue::from(key).as_jni()], + ) + }; match result { Ok(val) => Ok(Some(val.l()?)), @@ -82,13 +95,22 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { /// Look up the value for a key. Returns `Some` with the old value if the /// key already existed and `None` if it's a new key. - pub fn put(&self, key: JObject<'a>, value: JObject<'a>) -> Result<Option<JObject<'a>>> { - let result = self.env.call_method_unchecked( - self.internal, - self.put, - ReturnType::Object, - &[JValue::from(key).to_jni(), JValue::from(value).to_jni()], - ); + pub fn put<'other_local_2>( + &self, + env: &mut JNIEnv<'other_local_2>, + key: &JObject, + value: &JObject, + ) -> Result<Option<JObject<'other_local_2>>> { + // SAFETY: We keep the class loaded, and fetched the method ID for this function. + // Provided argument is statically known as a JObject/null, rather than another primitive type. + let result = unsafe { + env.call_method_unchecked( + self.internal, + self.put, + ReturnType::Object, + &[JValue::from(key).as_jni(), JValue::from(value).as_jni()], + ) + }; match result { Ok(val) => Ok(Some(val.l()?)), @@ -101,13 +123,21 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { /// Remove a value from the map. Returns `Some` with the removed value and /// `None` if there was no value for the key. - pub fn remove(&self, key: JObject<'a>) -> Result<Option<JObject<'a>>> { - let result = self.env.call_method_unchecked( - self.internal, - self.remove, - ReturnType::Object, - &[JValue::from(key).to_jni()], - ); + pub fn remove<'other_local_2>( + &self, + env: &mut JNIEnv<'other_local_2>, + key: &JObject, + ) -> Result<Option<JObject<'other_local_2>>> { + // SAFETY: We keep the class loaded, and fetched the method ID for this function. + // Provided argument is statically known as a JObject/null, rather than another primitive type. + let result = unsafe { + env.call_method_unchecked( + self.internal, + self.remove, + ReturnType::Object, + &[JValue::from(key).as_jni()], + ) + }; match result { Ok(val) => Ok(Some(val.l()?)), @@ -120,59 +150,83 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { /// Get key/value iterator for the map. This is done by getting the /// `EntrySet` from java and iterating over it. - pub fn iter(&self) -> Result<JMapIter<'a, 'b, '_>> { - let iter_class = self - .env - .auto_local(self.env.find_class("java/util/Iterator")?); - - let has_next = self.env.get_method_id(&iter_class, "hasNext", "()Z")?; - - let next = self - .env - .get_method_id(&iter_class, "next", "()Ljava/lang/Object;")?; - - let entry_class = self - .env - .auto_local(self.env.find_class("java/util/Map$Entry")?); - - let get_key = self - .env - .get_method_id(&entry_class, "getKey", "()Ljava/lang/Object;")?; - - let get_value = self - .env - .get_method_id(&entry_class, "getValue", "()Ljava/lang/Object;")?; + /// + /// The returned iterator does not implement [`std::iter::Iterator`] and + /// cannot be used with a `for` loop. This is because its `next` method + /// uses a `&mut JNIEnv` to call the Java iterator. Use a `while let` loop + /// instead: + /// + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv, objects::{AutoLocal, JMap, JObject}}; + /// # + /// # fn example(env: &mut JNIEnv, map: JMap) -> Result<()> { + /// let mut iterator = map.iter(env)?; + /// + /// while let Some((key, value)) = iterator.next(env)? { + /// let key: AutoLocal<JObject> = env.auto_local(key); + /// let value: AutoLocal<JObject> = env.auto_local(value); + /// + /// // Do something with `key` and `value` here. + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// Each call to `next` creates two new local references. To prevent + /// excessive memory usage or overflow error, the local references should + /// be deleted using [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`] + /// before the next loop iteration. Alternatively, if the map is known to + /// have a small, predictable size, the loop could be wrapped in + /// [`JNIEnv::with_local_frame`] to delete all of the local references at + /// once. + pub fn iter<'map, 'iter_local>( + &'map self, + env: &mut JNIEnv<'iter_local>, + ) -> Result<JMapIter<'map, 'local, 'other_local_1, 'obj_ref, 'iter_local>> { + let iter_class = AutoLocal::new(env.find_class("java/util/Iterator")?, env); + + let has_next = env.get_method_id(&iter_class, "hasNext", "()Z")?; + + let next = env.get_method_id(&iter_class, "next", "()Ljava/lang/Object;")?; + + let entry_class = AutoLocal::new(env.find_class("java/util/Map$Entry")?, env); + + let get_key = env.get_method_id(&entry_class, "getKey", "()Ljava/lang/Object;")?; + + let get_value = env.get_method_id(&entry_class, "getValue", "()Ljava/lang/Object;")?; // Get the iterator over Map entries. - // Use the local frame till #109 is resolved, so that implicitly looked-up - // classes are freed promptly. - let iter = self.env.with_local_frame(16, || { - let entry_set = self - .env - .call_method_unchecked( + + // SAFETY: We keep the class loaded, and fetched the method ID for this function. Arg list is known empty. + let entry_set = AutoLocal::new( + unsafe { + env.call_method_unchecked( self.internal, (&self.class, "entrySet", "()Ljava/util/Set;"), ReturnType::Object, &[], - )? - .l()?; + ) + }? + .l()?, + env, + ); - let iter = self - .env - .call_method_unchecked( + // SAFETY: We keep the class loaded, and fetched the method ID for this function. Arg list is known empty. + let iter = AutoLocal::new( + unsafe { + env.call_method_unchecked( entry_set, ("java/util/Set", "iterator", "()Ljava/util/Iterator;"), ReturnType::Object, &[], - )? - .l()?; - - Ok(iter) - })?; - let iter = self.env.auto_local(iter); + ) + }? + .l()?, + env, + ); Ok(JMapIter { - map: self, + _phantom_map: PhantomData, has_next, next, get_key, @@ -182,65 +236,77 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { } } -/// An iterator over the keys and values in a map. +/// An iterator over the keys and values in a map. See [`JMap::iter`] for more +/// information. /// /// TODO: make the iterator implementation for java iterators its own thing /// and generic enough to use elsewhere. -pub struct JMapIter<'a, 'b, 'c> { - map: &'c JMap<'a, 'b>, +pub struct JMapIter<'map, 'local, 'other_local_1: 'obj_ref, 'obj_ref, 'iter_local> { + _phantom_map: PhantomData<&'map JMap<'local, 'other_local_1, 'obj_ref>>, has_next: JMethodID, next: JMethodID, get_key: JMethodID, get_value: JMethodID, - iter: AutoLocal<'a, 'b>, + iter: AutoLocal<'iter_local, JObject<'iter_local>>, } -impl<'a: 'b, 'b: 'c, 'c> JMapIter<'a, 'b, 'c> { - fn get_next(&self) -> Result<Option<(JObject<'a>, JObject<'a>)>> { - let iter = self.iter.as_obj(); - let has_next = self - .map - .env - .call_method_unchecked( - iter, +impl<'map, 'local, 'other_local_1: 'obj_ref, 'obj_ref, 'iter_local> + JMapIter<'map, 'local, 'other_local_1, 'obj_ref, 'iter_local> +{ + /// Advances the iterator and returns the next key-value pair in the + /// `java.util.Map`, or `None` if there are no more objects. + /// + /// See [`JMap::iter`] for more information. + /// + /// This method creates two new local references. To prevent excessive + /// memory usage or overflow error, the local references should be deleted + /// using [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`] before the + /// next loop iteration. Alternatively, if the map is known to have a + /// small, predictable size, the loop could be wrapped in + /// [`JNIEnv::with_local_frame`] to delete all of the local references at + /// once. + /// + /// This method returns: + /// + /// * `Ok(Some(_))`: if there was another key-value pair in the map. + /// * `Ok(None)`: if there are no more key-value pairs in the map. + /// * `Err(_)`: if there was an error calling the Java method to + /// get the next key-value pair. + /// + /// This is like [`std::iter::Iterator::next`], but requires a parameter of + /// type `&mut JNIEnv` in order to call into Java. + pub fn next<'other_local_2>( + &mut self, + env: &mut JNIEnv<'other_local_2>, + ) -> Result<Option<(JObject<'other_local_2>, JObject<'other_local_2>)>> { + // SAFETY: We keep the class loaded, and fetched the method ID for these functions. We know none expect args. + + let has_next = unsafe { + env.call_method_unchecked( + &self.iter, self.has_next, ReturnType::Primitive(Primitive::Boolean), &[], - )? - .z()?; + ) + }? + .z()?; if !has_next { return Ok(None); } - let next = self - .map - .env - .call_method_unchecked(iter, self.next, ReturnType::Object, &[])? - .l()?; - - let key = self - .map - .env - .call_method_unchecked(next, self.get_key, ReturnType::Object, &[])? - .l()?; - - let value = self - .map - .env - .call_method_unchecked(next, self.get_value, ReturnType::Object, &[])? - .l()?; + let next = + unsafe { env.call_method_unchecked(&self.iter, self.next, ReturnType::Object, &[]) }? + .l()?; + let next = env.auto_local(next); - Ok(Some((key, value))) - } -} + let key = + unsafe { env.call_method_unchecked(&next, self.get_key, ReturnType::Object, &[]) }? + .l()?; -impl<'a: 'b, 'b: 'c, 'c> Iterator for JMapIter<'a, 'b, 'c> { - type Item = (JObject<'a>, JObject<'a>); + let value = + unsafe { env.call_method_unchecked(&next, self.get_value, ReturnType::Object, &[]) }? + .l()?; - fn next(&mut self) -> Option<Self::Item> { - match self.get_next() { - Ok(Some(n)) => Some(n), - _ => None, - } + Ok(Some((key, value))) } } diff --git a/src/wrapper/objects/jmethodid.rs b/src/wrapper/objects/jmethodid.rs index 892fe43..eb945a1 100644 --- a/src/wrapper/objects/jmethodid.rs +++ b/src/wrapper/objects/jmethodid.rs @@ -46,3 +46,15 @@ impl JMethodID { self.internal } } + +impl AsRef<JMethodID> for JMethodID { + fn as_ref(&self) -> &JMethodID { + self + } +} + +impl AsMut<JMethodID> for JMethodID { + fn as_mut(&mut self) -> &mut JMethodID { + self + } +} diff --git a/src/wrapper/objects/jobject.rs b/src/wrapper/objects/jobject.rs index 1b55dae..e58e6f7 100644 --- a/src/wrapper/objects/jobject.rs +++ b/src/wrapper/objects/jobject.rs @@ -2,22 +2,56 @@ use std::marker::PhantomData; use crate::sys::jobject; -/// Wrapper around `sys::jobject` that adds a lifetime. This prevents it from -/// outliving the context in which it was acquired and getting GC'd out from -/// under us. It matches C's representation of the raw pointer, so it can be -/// used in any of the extern function argument positions that would take a -/// `jobject`. +#[cfg(doc)] +use crate::{objects::GlobalRef, JNIEnv}; + +/// Wrapper around [`sys::jobject`] that adds a lifetime to ensure that +/// the underlying JNI pointer won't be accessible to safe Rust code if the +/// object reference is released. +/// +/// It matches C's representation of the raw pointer, so it can be used in any +/// of the extern function argument positions that would take a `jobject`. /// /// Most other types in the `objects` module deref to this, as they do in the C /// representation. +/// +/// The lifetime `'local` represents the local reference frame that this +/// reference belongs to. See the [`JNIEnv`] documentation for more information +/// about local reference frames. If `'local` is `'static`, then this reference +/// does not belong to a local reference frame, that is, it is either null or a +/// [global reference][GlobalRef]. +/// +/// Note that an *owned* `JObject` is always a local reference and will never +/// have the `'static` lifetime. [`GlobalRef`] does implement +/// <code>[AsRef]<JObject<'static>></code>, but this only yields a +/// *borrowed* `&JObject<'static>`, never an owned `JObject<'static>`. +/// +/// Local references belong to a single thread and are not safe to share across +/// threads. This type implements [`Send`] and [`Sync`] if and only if the +/// lifetime `'local` is `'static`. #[repr(transparent)] -#[derive(Clone, Copy, Debug)] -pub struct JObject<'a> { +#[derive(Debug)] +pub struct JObject<'local> { internal: jobject, - lifetime: PhantomData<&'a ()>, + lifetime: PhantomData<&'local ()>, +} + +unsafe impl Send for JObject<'static> {} +unsafe impl Sync for JObject<'static> {} + +impl<'local> AsRef<JObject<'local>> for JObject<'local> { + fn as_ref(&self) -> &JObject<'local> { + self + } } -impl<'a> ::std::ops::Deref for JObject<'a> { +impl<'local> AsMut<JObject<'local>> for JObject<'local> { + fn as_mut(&mut self) -> &mut JObject<'local> { + self + } +} + +impl<'local> ::std::ops::Deref for JObject<'local> { type Target = jobject; fn deref(&self) -> &Self::Target { @@ -25,12 +59,17 @@ impl<'a> ::std::ops::Deref for JObject<'a> { } } -impl<'a> JObject<'a> { +impl<'local> JObject<'local> { /// Creates a [`JObject`] that wraps the given `raw` [`jobject`] /// /// # Safety /// - /// Expects a valid pointer or `null` + /// `raw` may be a null pointer. If `raw` is not a null pointer, then: + /// + /// * `raw` must be a valid raw JNI local reference. + /// * There must not be any other `JObject` representing the same local reference. + /// * The lifetime `'local` must not outlive the local reference frame that the local reference + /// was created in. pub unsafe fn from_raw(raw: jobject) -> Self { Self { internal: raw, @@ -38,18 +77,26 @@ impl<'a> JObject<'a> { } } + /// Returns the raw JNI pointer. + pub fn as_raw(&self) -> jobject { + self.internal + } + /// Unwrap to the internal jni type. pub fn into_raw(self) -> jobject { self.internal } - /// Creates a new null object - pub fn null() -> JObject<'a> { - unsafe { Self::from_raw(std::ptr::null_mut() as jobject) } + /// Creates a new null reference. + /// + /// Null references are always valid and do not belong to a local reference frame. Therefore, + /// the returned `JObject` always has the `'static` lifetime. + pub fn null() -> JObject<'static> { + unsafe { JObject::from_raw(std::ptr::null_mut() as jobject) } } } -impl<'a> std::default::Default for JObject<'a> { +impl<'local> std::default::Default for JObject<'local> { fn default() -> Self { Self::null() } diff --git a/src/wrapper/objects/jobject_array.rs b/src/wrapper/objects/jobject_array.rs new file mode 100644 index 0000000..55c77b0 --- /dev/null +++ b/src/wrapper/objects/jobject_array.rs @@ -0,0 +1,81 @@ +use crate::{ + objects::JObject, + sys::{jobject, jobjectArray}, +}; + +use super::AsJArrayRaw; + +/// Lifetime'd representation of a [`jobjectArray`] which wraps a [`JObject`] reference +#[repr(transparent)] +#[derive(Debug)] +pub struct JObjectArray<'local>(JObject<'local>); + +impl<'local> AsRef<JObjectArray<'local>> for JObjectArray<'local> { + fn as_ref(&self) -> &JObjectArray<'local> { + self + } +} + +impl<'local> AsRef<JObject<'local>> for JObjectArray<'local> { + fn as_ref(&self) -> &JObject<'local> { + self + } +} + +impl<'local> ::std::ops::Deref for JObjectArray<'local> { + type Target = JObject<'local>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'local> From<JObjectArray<'local>> for JObject<'local> { + fn from(other: JObjectArray) -> JObject { + other.0 + } +} + +/// This conversion assumes that the `JObject` is a pointer to a class object. +impl<'local> From<JObject<'local>> for JObjectArray<'local> { + fn from(other: JObject) -> Self { + unsafe { Self::from_raw(other.into_raw()) } + } +} + +/// This conversion assumes that the `JObject` is a pointer to a class object. +impl<'local, 'obj_ref> From<&'obj_ref JObject<'local>> for &'obj_ref JObjectArray<'local> { + fn from(other: &'obj_ref JObject<'local>) -> Self { + // Safety: `JObjectArray` is `repr(transparent)` around `JObject`. + unsafe { &*(other as *const JObject<'local> as *const JObjectArray<'local>) } + } +} + +impl<'local> std::default::Default for JObjectArray<'local> { + fn default() -> Self { + Self(JObject::null()) + } +} + +unsafe impl<'local> AsJArrayRaw<'local> for JObjectArray<'local> {} + +impl<'local> JObjectArray<'local> { + /// Creates a [`JObjectArray`] that wraps the given `raw` [`jobjectArray`] + /// + /// # Safety + /// + /// `raw` may be a null pointer. If `raw` is not a null pointer, then: + /// + /// * `raw` must be a valid raw JNI local reference. + /// * There must not be any other `JObject` representing the same local reference. + /// * The lifetime `'local` must not outlive the local reference frame that the local reference + /// was created in. + pub unsafe fn from_raw(raw: jobjectArray) -> Self { + Self(JObject::from_raw(raw as jobject)) + } + + /// Unwrap to the raw jni type. + pub fn into_raw(self) -> jobjectArray { + self.0.into_raw() as jobjectArray + } +} diff --git a/src/wrapper/objects/jprimitive_array.rs b/src/wrapper/objects/jprimitive_array.rs new file mode 100644 index 0000000..f6e74ad --- /dev/null +++ b/src/wrapper/objects/jprimitive_array.rs @@ -0,0 +1,144 @@ +use std::marker::PhantomData; + +use crate::{ + objects::JObject, + sys::{jarray, jobject}, +}; + +use super::TypeArray; + +#[cfg(doc)] +use crate::JNIEnv; + +/// Lifetime'd representation of a [`jarray`] which wraps a [`JObject`] reference +/// +/// This is a wrapper type for a [`JObject`] local reference that's used to +/// differentiate JVM array types. +#[repr(transparent)] +#[derive(Debug)] +pub struct JPrimitiveArray<'local, T: TypeArray> { + obj: JObject<'local>, + lifetime: PhantomData<&'local T>, +} + +impl<'local, T: TypeArray> AsRef<JPrimitiveArray<'local, T>> for JPrimitiveArray<'local, T> { + fn as_ref(&self) -> &JPrimitiveArray<'local, T> { + self + } +} + +impl<'local, T: TypeArray> AsMut<JPrimitiveArray<'local, T>> for JPrimitiveArray<'local, T> { + fn as_mut(&mut self) -> &mut JPrimitiveArray<'local, T> { + self + } +} + +impl<'local, T: TypeArray> AsRef<JObject<'local>> for JPrimitiveArray<'local, T> { + fn as_ref(&self) -> &JObject<'local> { + &self.obj + } +} + +impl<'local, T: TypeArray> ::std::ops::Deref for JPrimitiveArray<'local, T> { + type Target = JObject<'local>; + + fn deref(&self) -> &Self::Target { + &self.obj + } +} + +impl<'local, T: TypeArray> From<JPrimitiveArray<'local, T>> for JObject<'local> { + fn from(other: JPrimitiveArray<'local, T>) -> JObject { + other.obj + } +} + +/// This conversion assumes that the `JObject` is a pointer to a class object. +impl<'local, T: TypeArray> From<JObject<'local>> for JPrimitiveArray<'local, T> { + fn from(other: JObject) -> Self { + unsafe { Self::from_raw(other.into_raw()) } + } +} + +/// This conversion assumes that the `JObject` is a pointer to a class object. +impl<'local, 'obj_ref, T: TypeArray> From<&'obj_ref JObject<'local>> + for &'obj_ref JPrimitiveArray<'local, T> +{ + fn from(other: &'obj_ref JObject<'local>) -> Self { + // Safety: `JPrimitiveArray` is `repr(transparent)` around `JObject`. + unsafe { &*(other as *const JObject<'local> as *const JPrimitiveArray<'local, T>) } + } +} + +impl<'local, T: TypeArray> std::default::Default for JPrimitiveArray<'local, T> { + fn default() -> Self { + Self { + obj: JObject::null(), + lifetime: PhantomData, + } + } +} + +impl<'local, T: TypeArray> JPrimitiveArray<'local, T> { + /// Creates a [`JPrimitiveArray`] that wraps the given `raw` [`jarray`] + /// + /// # Safety + /// + /// `raw` may be a null pointer. If `raw` is not a null pointer, then: + /// + /// * `raw` must be a valid raw JNI local reference. + /// * There must not be any other `JObject` representing the same local reference. + /// * The lifetime `'local` must not outlive the local reference frame that the local reference + /// was created in. + pub unsafe fn from_raw(raw: jarray) -> Self { + Self { + obj: JObject::from_raw(raw as jobject), + lifetime: PhantomData, + } + } + + /// Unwrap to the raw jni type. + pub fn into_raw(self) -> jarray { + self.obj.into_raw() as jarray + } +} + +/// Lifetime'd representation of a [`crate::sys::jbooleanArray`] which wraps a [`JObject`] reference +pub type JBooleanArray<'local> = JPrimitiveArray<'local, crate::sys::jboolean>; + +/// Lifetime'd representation of a [`crate::sys::jbyteArray`] which wraps a [`JObject`] reference +pub type JByteArray<'local> = JPrimitiveArray<'local, crate::sys::jbyte>; + +/// Lifetime'd representation of a [`crate::sys::jcharArray`] which wraps a [`JObject`] reference +pub type JCharArray<'local> = JPrimitiveArray<'local, crate::sys::jchar>; + +/// Lifetime'd representation of a [`crate::sys::jshortArray`] which wraps a [`JObject`] reference +pub type JShortArray<'local> = JPrimitiveArray<'local, crate::sys::jshort>; + +/// Lifetime'd representation of a [`crate::sys::jintArray`] which wraps a [`JObject`] reference +pub type JIntArray<'local> = JPrimitiveArray<'local, crate::sys::jint>; + +/// Lifetime'd representation of a [`crate::sys::jlongArray`] which wraps a [`JObject`] reference +pub type JLongArray<'local> = JPrimitiveArray<'local, crate::sys::jlong>; + +/// Lifetime'd representation of a [`crate::sys::jfloatArray`] which wraps a [`JObject`] reference +pub type JFloatArray<'local> = JPrimitiveArray<'local, crate::sys::jfloat>; + +/// Lifetime'd representation of a [`crate::sys::jdoubleArray`] which wraps a [`JObject`] reference +pub type JDoubleArray<'local> = JPrimitiveArray<'local, crate::sys::jdouble>; + +/// Trait to access the raw `jarray` pointer for types that wrap an array reference +/// +/// # Safety +/// +/// Implementing this trait will allow a type to be passed to [`JNIEnv::get_array_length()`] +/// or other JNI APIs that only work with a valid reference to an array (or `null`) +/// +pub unsafe trait AsJArrayRaw<'local>: AsRef<JObject<'local>> { + /// Returns the raw JNI pointer as a `jarray` + fn as_jarray_raw(&self) -> jarray { + self.as_ref().as_raw() as jarray + } +} + +unsafe impl<'local, T: TypeArray> AsJArrayRaw<'local> for JPrimitiveArray<'local, T> {} diff --git a/src/wrapper/objects/jstaticfieldid.rs b/src/wrapper/objects/jstaticfieldid.rs index 4579b44..c9f1820 100644 --- a/src/wrapper/objects/jstaticfieldid.rs +++ b/src/wrapper/objects/jstaticfieldid.rs @@ -46,3 +46,15 @@ impl JStaticFieldID { self.internal } } + +impl AsRef<JStaticFieldID> for JStaticFieldID { + fn as_ref(&self) -> &JStaticFieldID { + self + } +} + +impl AsMut<JStaticFieldID> for JStaticFieldID { + fn as_mut(&mut self) -> &mut JStaticFieldID { + self + } +} diff --git a/src/wrapper/objects/jstaticmethodid.rs b/src/wrapper/objects/jstaticmethodid.rs index ada3fb9..a8a9986 100644 --- a/src/wrapper/objects/jstaticmethodid.rs +++ b/src/wrapper/objects/jstaticmethodid.rs @@ -46,3 +46,15 @@ impl JStaticMethodID { self.internal } } + +impl AsRef<JStaticMethodID> for JStaticMethodID { + fn as_ref(&self) -> &JStaticMethodID { + self + } +} + +impl AsMut<JStaticMethodID> for JStaticMethodID { + fn as_mut(&mut self) -> &mut JStaticMethodID { + self + } +} diff --git a/src/wrapper/objects/jstring.rs b/src/wrapper/objects/jstring.rs index a5df7aa..428e1fe 100644 --- a/src/wrapper/objects/jstring.rs +++ b/src/wrapper/objects/jstring.rs @@ -6,41 +6,64 @@ use crate::{ /// Lifetime'd representation of a `jstring`. Just a `JObject` wrapped in a new /// class. #[repr(transparent)] -#[derive(Clone, Copy)] -pub struct JString<'a>(JObject<'a>); +pub struct JString<'local>(JObject<'local>); -impl<'a> ::std::ops::Deref for JString<'a> { - type Target = JObject<'a>; +impl<'local> AsRef<JString<'local>> for JString<'local> { + fn as_ref(&self) -> &JString<'local> { + self + } +} + +impl<'local> AsRef<JObject<'local>> for JString<'local> { + fn as_ref(&self) -> &JObject<'local> { + self + } +} + +impl<'local> ::std::ops::Deref for JString<'local> { + type Target = JObject<'local>; fn deref(&self) -> &Self::Target { &self.0 } } -impl<'a> From<JString<'a>> for JObject<'a> { +impl<'local> From<JString<'local>> for JObject<'local> { fn from(other: JString) -> JObject { other.0 } } -impl<'a> From<JObject<'a>> for JString<'a> { +impl<'local> From<JObject<'local>> for JString<'local> { fn from(other: JObject) -> Self { unsafe { Self::from_raw(other.into_raw()) } } } -impl<'a> std::default::Default for JString<'a> { +impl<'local, 'obj_ref> From<&'obj_ref JObject<'local>> for &'obj_ref JString<'local> { + fn from(other: &'obj_ref JObject<'local>) -> Self { + // Safety: `JString` is `repr(transparent)` around `JObject`. + unsafe { &*(other as *const JObject<'local> as *const JString<'local>) } + } +} + +impl<'local> std::default::Default for JString<'local> { fn default() -> Self { Self(JObject::null()) } } -impl<'a> JString<'a> { +impl<'local> JString<'local> { /// Creates a [`JString`] that wraps the given `raw` [`jstring`] /// /// # Safety /// - /// Expects a valid pointer or `null` + /// `raw` may be a null pointer. If `raw` is not a null pointer, then: + /// + /// * `raw` must be a valid raw JNI local reference. + /// * There must not be any other `JObject` representing the same local reference. + /// * The lifetime `'local` must not outlive the local reference frame that the local reference + /// was created in. pub unsafe fn from_raw(raw: jstring) -> Self { Self(JObject::from_raw(raw as jobject)) } diff --git a/src/wrapper/objects/jthrowable.rs b/src/wrapper/objects/jthrowable.rs index f77f6e3..3616605 100644 --- a/src/wrapper/objects/jthrowable.rs +++ b/src/wrapper/objects/jthrowable.rs @@ -6,41 +6,64 @@ use crate::{ /// Lifetime'd representation of a `jthrowable`. Just a `JObject` wrapped in a /// new class. #[repr(transparent)] -#[derive(Clone, Copy)] -pub struct JThrowable<'a>(JObject<'a>); +pub struct JThrowable<'local>(JObject<'local>); -impl<'a> ::std::ops::Deref for JThrowable<'a> { - type Target = JObject<'a>; +impl<'local> AsRef<JThrowable<'local>> for JThrowable<'local> { + fn as_ref(&self) -> &JThrowable<'local> { + self + } +} + +impl<'local> AsRef<JObject<'local>> for JThrowable<'local> { + fn as_ref(&self) -> &JObject<'local> { + self + } +} + +impl<'local> ::std::ops::Deref for JThrowable<'local> { + type Target = JObject<'local>; fn deref(&self) -> &Self::Target { &self.0 } } -impl<'a> From<JThrowable<'a>> for JObject<'a> { +impl<'local> From<JThrowable<'local>> for JObject<'local> { fn from(other: JThrowable) -> JObject { other.0 } } -impl<'a> From<JObject<'a>> for JThrowable<'a> { +impl<'local> From<JObject<'local>> for JThrowable<'local> { fn from(other: JObject) -> Self { unsafe { Self::from_raw(other.into_raw()) } } } -impl<'a> std::default::Default for JThrowable<'a> { +impl<'local, 'obj_ref> From<&'obj_ref JObject<'local>> for &'obj_ref JThrowable<'local> { + fn from(other: &'obj_ref JObject<'local>) -> Self { + // Safety: `JThrowable` is `repr(transparent)` around `JObject`. + unsafe { &*(other as *const JObject<'local> as *const JThrowable<'local>) } + } +} + +impl<'local> std::default::Default for JThrowable<'local> { fn default() -> Self { Self(JObject::null()) } } -impl<'a> JThrowable<'a> { +impl<'local> JThrowable<'local> { /// Creates a [`JThrowable`] that wraps the given `raw` [`jthrowable`] /// /// # Safety /// - /// Expects a valid pointer or `null` + /// `raw` may be a null pointer. If `raw` is not a null pointer, then: + /// + /// * `raw` must be a valid raw JNI local reference. + /// * There must not be any other `JObject` representing the same local reference. + /// * The lifetime `'local` must not outlive the local reference frame that the local reference + /// was created in. pub unsafe fn from_raw(raw: jthrowable) -> Self { Self(JObject::from_raw(raw as jobject)) } diff --git a/src/wrapper/objects/jvalue.rs b/src/wrapper/objects/jvalue.rs index 54829c0..1f91a41 100644 --- a/src/wrapper/objects/jvalue.rs +++ b/src/wrapper/objects/jvalue.rs @@ -1,5 +1,5 @@ use std::convert::TryFrom; -use std::mem::transmute; +use std::fmt::Debug; use log::trace; @@ -7,10 +7,21 @@ use crate::{errors::*, objects::JObject, signature::Primitive, sys::*}; /// Rusty version of the JNI C `jvalue` enum. Used in Java method call arguments /// and returns. +/// +/// `JValueGen` is a generic type, meant to represent both owned and borrowed +/// JNI values. The type parameter `O` refers to what kind of object reference +/// the `JValueGen` can hold, which is either: +/// +/// * an owned [`JObject`], used for values returned from a Java method call, +/// or +/// * a borrowed `&JObject`, used for parameters passed to a Java method call. +/// +/// These two cases are represented by the type aliases [`JValueOwned`] and +/// [`JValue`], respectively. #[allow(missing_docs)] #[derive(Clone, Copy, Debug)] -pub enum JValue<'a> { - Object(JObject<'a>), +pub enum JValueGen<O> { + Object(O), Byte(jbyte), Char(jchar), Short(jshort), @@ -22,28 +33,39 @@ pub enum JValue<'a> { Void, } -impl<'a> From<JValue<'a>> for jvalue { - fn from(other: JValue) -> jvalue { - other.to_jni() - } -} - -impl<'a> JValue<'a> { +/// An <dfn>owned</dfn> [`JValueGen`]. +/// +/// This type is used for values returned from Java method calls. If the Java +/// method returns an object reference, it will take the form of an owned +/// [`JObject`]. +pub type JValueOwned<'local> = JValueGen<JObject<'local>>; + +/// A <dfn>reference</dfn> [`JValueGen`]. +/// +/// This type is used for parameters passed to Java method calls. If the Java +/// method is to be passed an object reference, it takes the form of a borrowed +/// <code>&[JObject]</code>. +pub type JValue<'local, 'obj_ref> = JValueGen<&'obj_ref JObject<'local>>; + +impl<O> JValueGen<O> { /// Convert the enum to its jni-compatible equivalent. - pub fn to_jni(self) -> jvalue { + pub fn as_jni<'local>(&self) -> jvalue + where + O: AsRef<JObject<'local>> + Debug, + { let val: jvalue = match self { - JValue::Object(obj) => jvalue { - l: unsafe { transmute(obj) }, + JValueGen::Object(obj) => jvalue { + l: obj.as_ref().as_raw(), }, - JValue::Byte(byte) => jvalue { b: byte }, - JValue::Char(char) => jvalue { c: char }, - JValue::Short(short) => jvalue { s: short }, - JValue::Int(int) => jvalue { i: int }, - JValue::Long(long) => jvalue { j: long }, - JValue::Bool(boolean) => jvalue { b: boolean as i8 }, - JValue::Float(float) => jvalue { f: float }, - JValue::Double(double) => jvalue { d: double }, - JValue::Void => jvalue { + JValueGen::Byte(byte) => jvalue { b: *byte }, + JValueGen::Char(char) => jvalue { c: *char }, + JValueGen::Short(short) => jvalue { s: *short }, + JValueGen::Int(int) => jvalue { i: *int }, + JValueGen::Long(long) => jvalue { j: *long }, + JValueGen::Bool(boolean) => jvalue { b: *boolean as i8 }, + JValueGen::Float(float) => jvalue { f: *float }, + JValueGen::Double(double) => jvalue { d: *double }, + JValueGen::Void => jvalue { l: ::std::ptr::null_mut(), }, }; @@ -53,19 +75,28 @@ impl<'a> JValue<'a> { val } + /// Convert the enum to its jni-compatible equivalent. + #[deprecated = "Use `as_jni` instead."] + pub fn to_jni<'local>(self) -> jvalue + where + O: AsRef<JObject<'local>> + Debug, + { + self.as_jni() + } + /// Get the type name for the enum variant. pub fn type_name(&self) -> &'static str { match *self { - JValue::Void => "void", - JValue::Object(_) => "object", - JValue::Byte(_) => "byte", - JValue::Char(_) => "char", - JValue::Short(_) => "short", - JValue::Int(_) => "int", - JValue::Long(_) => "long", - JValue::Bool(_) => "bool", - JValue::Float(_) => "float", - JValue::Double(_) => "double", + JValueGen::Void => "void", + JValueGen::Object(_) => "object", + JValueGen::Byte(_) => "byte", + JValueGen::Char(_) => "char", + JValueGen::Short(_) => "short", + JValueGen::Int(_) => "int", + JValueGen::Long(_) => "long", + JValueGen::Bool(_) => "bool", + JValueGen::Float(_) => "float", + JValueGen::Double(_) => "double", } } @@ -73,23 +104,23 @@ impl<'a> JValue<'a> { /// (i.e. an Object), returns None. pub fn primitive_type(&self) -> Option<Primitive> { Some(match *self { - JValue::Object(_) => return None, - JValue::Void => Primitive::Void, - JValue::Byte(_) => Primitive::Byte, - JValue::Char(_) => Primitive::Char, - JValue::Short(_) => Primitive::Short, - JValue::Int(_) => Primitive::Int, - JValue::Long(_) => Primitive::Long, - JValue::Bool(_) => Primitive::Boolean, - JValue::Float(_) => Primitive::Float, - JValue::Double(_) => Primitive::Double, + JValueGen::Object(_) => return None, + JValueGen::Void => Primitive::Void, + JValueGen::Byte(_) => Primitive::Byte, + JValueGen::Char(_) => Primitive::Char, + JValueGen::Short(_) => Primitive::Short, + JValueGen::Int(_) => Primitive::Int, + JValueGen::Long(_) => Primitive::Long, + JValueGen::Bool(_) => Primitive::Boolean, + JValueGen::Float(_) => Primitive::Float, + JValueGen::Double(_) => Primitive::Double, }) } /// Try to unwrap to an Object. - pub fn l(self) -> Result<JObject<'a>> { + pub fn l(self) -> Result<O> { match self { - JValue::Object(obj) => Ok(obj), + JValueGen::Object(obj) => Ok(obj), _ => Err(Error::WrongJValueType("object", self.type_name())), } } @@ -97,7 +128,7 @@ impl<'a> JValue<'a> { /// Try to unwrap to a boolean. pub fn z(self) -> Result<bool> { match self { - JValue::Bool(b) => Ok(b == JNI_TRUE), + JValueGen::Bool(b) => Ok(b == JNI_TRUE), _ => Err(Error::WrongJValueType("bool", self.type_name())), } } @@ -105,7 +136,7 @@ impl<'a> JValue<'a> { /// Try to unwrap to a byte. pub fn b(self) -> Result<jbyte> { match self { - JValue::Byte(b) => Ok(b), + JValueGen::Byte(b) => Ok(b), _ => Err(Error::WrongJValueType("jbyte", self.type_name())), } } @@ -113,7 +144,7 @@ impl<'a> JValue<'a> { /// Try to unwrap to a char. pub fn c(self) -> Result<jchar> { match self { - JValue::Char(b) => Ok(b), + JValueGen::Char(b) => Ok(b), _ => Err(Error::WrongJValueType("jchar", self.type_name())), } } @@ -121,7 +152,7 @@ impl<'a> JValue<'a> { /// Try to unwrap to a double. pub fn d(self) -> Result<jdouble> { match self { - JValue::Double(b) => Ok(b), + JValueGen::Double(b) => Ok(b), _ => Err(Error::WrongJValueType("jdouble", self.type_name())), } } @@ -129,7 +160,7 @@ impl<'a> JValue<'a> { /// Try to unwrap to a float. pub fn f(self) -> Result<jfloat> { match self { - JValue::Float(b) => Ok(b), + JValueGen::Float(b) => Ok(b), _ => Err(Error::WrongJValueType("jfloat", self.type_name())), } } @@ -137,7 +168,7 @@ impl<'a> JValue<'a> { /// Try to unwrap to an int. pub fn i(self) -> Result<jint> { match self { - JValue::Int(b) => Ok(b), + JValueGen::Int(b) => Ok(b), _ => Err(Error::WrongJValueType("jint", self.type_name())), } } @@ -145,7 +176,7 @@ impl<'a> JValue<'a> { /// Try to unwrap to a long. pub fn j(self) -> Result<jlong> { match self { - JValue::Long(b) => Ok(b), + JValueGen::Long(b) => Ok(b), _ => Err(Error::WrongJValueType("jlong", self.type_name())), } } @@ -153,7 +184,7 @@ impl<'a> JValue<'a> { /// Try to unwrap to a short. pub fn s(self) -> Result<jshort> { match self { - JValue::Short(b) => Ok(b), + JValueGen::Short(b) => Ok(b), _ => Err(Error::WrongJValueType("jshort", self.type_name())), } } @@ -161,192 +192,225 @@ impl<'a> JValue<'a> { /// Try to unwrap to a void. pub fn v(self) -> Result<()> { match self { - JValue::Void => Ok(()), + JValueGen::Void => Ok(()), _ => Err(Error::WrongJValueType("void", self.type_name())), } } + + /// Copies or borrows the value in this `JValue`. + /// + /// If the value is a primitive type, it is copied. If the value is an + /// object reference, it is borrowed. + pub fn borrow(&self) -> JValueGen<&O> { + match self { + JValueGen::Object(o) => JValueGen::Object(o), + JValueGen::Byte(v) => JValueGen::Byte(*v), + JValueGen::Char(v) => JValueGen::Char(*v), + JValueGen::Short(v) => JValueGen::Short(*v), + JValueGen::Int(v) => JValueGen::Int(*v), + JValueGen::Long(v) => JValueGen::Long(*v), + JValueGen::Bool(v) => JValueGen::Bool(*v), + JValueGen::Float(v) => JValueGen::Float(*v), + JValueGen::Double(v) => JValueGen::Double(*v), + JValueGen::Void => JValueGen::Void, + } + } +} + +impl<'obj_ref, O> From<&'obj_ref JValueGen<O>> for JValueGen<&'obj_ref O> { + fn from(other: &'obj_ref JValueGen<O>) -> Self { + other.borrow() + } } -impl<'a, T: Into<JObject<'a>>> From<T> for JValue<'a> { +impl<'local, T: Into<JObject<'local>>> From<T> for JValueOwned<'local> { fn from(other: T) -> Self { - JValue::Object(other.into()) + Self::Object(other.into()) + } +} + +impl<'local: 'obj_ref, 'obj_ref, T: AsRef<JObject<'local>>> From<&'obj_ref T> + for JValue<'local, 'obj_ref> +{ + fn from(other: &'obj_ref T) -> Self { + Self::Object(other.as_ref()) } } -impl<'a> TryFrom<JValue<'a>> for JObject<'a> { +impl<'local> TryFrom<JValueOwned<'local>> for JObject<'local> { type Error = Error; - fn try_from(value: JValue<'a>) -> Result<Self> { + fn try_from(value: JValueOwned<'local>) -> Result<Self> { match value { - JValue::Object(o) => Ok(o), + JValueGen::Object(o) => Ok(o), _ => Err(Error::WrongJValueType("object", value.type_name())), } } } -impl<'a> From<bool> for JValue<'a> { +impl<O> From<bool> for JValueGen<O> { fn from(other: bool) -> Self { - JValue::Bool(if other { JNI_TRUE } else { JNI_FALSE }) + JValueGen::Bool(if other { JNI_TRUE } else { JNI_FALSE }) } } // jbool -impl<'a> From<jboolean> for JValue<'a> { +impl<O> From<jboolean> for JValueGen<O> { fn from(other: jboolean) -> Self { - JValue::Bool(other) + JValueGen::Bool(other) } } -impl<'a> TryFrom<JValue<'a>> for jboolean { +impl<O> TryFrom<JValueGen<O>> for jboolean { type Error = Error; - fn try_from(value: JValue<'a>) -> Result<Self> { + fn try_from(value: JValueGen<O>) -> Result<Self> { match value { - JValue::Bool(b) => Ok(b), + JValueGen::Bool(b) => Ok(b), _ => Err(Error::WrongJValueType("bool", value.type_name())), } } } // jchar -impl<'a> From<jchar> for JValue<'a> { +impl<O> From<jchar> for JValueGen<O> { fn from(other: jchar) -> Self { - JValue::Char(other) + JValueGen::Char(other) } } -impl<'a> TryFrom<JValue<'a>> for jchar { +impl<O> TryFrom<JValueGen<O>> for jchar { type Error = Error; - fn try_from(value: JValue<'a>) -> Result<Self> { + fn try_from(value: JValueGen<O>) -> Result<Self> { match value { - JValue::Char(c) => Ok(c), + JValueGen::Char(c) => Ok(c), _ => Err(Error::WrongJValueType("char", value.type_name())), } } } // jshort -impl<'a> From<jshort> for JValue<'a> { +impl<O> From<jshort> for JValueGen<O> { fn from(other: jshort) -> Self { - JValue::Short(other) + JValueGen::Short(other) } } -impl<'a> TryFrom<JValue<'a>> for jshort { +impl<O> TryFrom<JValueGen<O>> for jshort { type Error = Error; - fn try_from(value: JValue<'a>) -> Result<Self> { + fn try_from(value: JValueGen<O>) -> Result<Self> { match value { - JValue::Short(s) => Ok(s), + JValueGen::Short(s) => Ok(s), _ => Err(Error::WrongJValueType("short", value.type_name())), } } } // jfloat -impl<'a> From<jfloat> for JValue<'a> { +impl<O> From<jfloat> for JValueGen<O> { fn from(other: jfloat) -> Self { - JValue::Float(other) + JValueGen::Float(other) } } -impl<'a> TryFrom<JValue<'a>> for jfloat { +impl<O> TryFrom<JValueGen<O>> for jfloat { type Error = Error; - fn try_from(value: JValue<'a>) -> Result<Self> { + fn try_from(value: JValueGen<O>) -> Result<Self> { match value { - JValue::Float(f) => Ok(f), + JValueGen::Float(f) => Ok(f), _ => Err(Error::WrongJValueType("float", value.type_name())), } } } // jdouble -impl<'a> From<jdouble> for JValue<'a> { +impl<O> From<jdouble> for JValueGen<O> { fn from(other: jdouble) -> Self { - JValue::Double(other) + JValueGen::Double(other) } } -impl<'a> TryFrom<JValue<'a>> for jdouble { +impl<O> TryFrom<JValueGen<O>> for jdouble { type Error = Error; - fn try_from(value: JValue<'a>) -> Result<Self> { + fn try_from(value: JValueGen<O>) -> Result<Self> { match value { - JValue::Double(d) => Ok(d), + JValueGen::Double(d) => Ok(d), _ => Err(Error::WrongJValueType("double", value.type_name())), } } } // jint -impl<'a> From<jint> for JValue<'a> { +impl<O> From<jint> for JValueGen<O> { fn from(other: jint) -> Self { - JValue::Int(other) + JValueGen::Int(other) } } -impl<'a> TryFrom<JValue<'a>> for jint { +impl<O> TryFrom<JValueGen<O>> for jint { type Error = Error; - fn try_from(value: JValue<'a>) -> Result<Self> { + fn try_from(value: JValueGen<O>) -> Result<Self> { match value { - JValue::Int(i) => Ok(i), + JValueGen::Int(i) => Ok(i), _ => Err(Error::WrongJValueType("int", value.type_name())), } } } // jlong -impl<'a> From<jlong> for JValue<'a> { +impl<O> From<jlong> for JValueGen<O> { fn from(other: jlong) -> Self { - JValue::Long(other) + JValueGen::Long(other) } } -impl<'a> TryFrom<JValue<'a>> for jlong { +impl<O> TryFrom<JValueGen<O>> for jlong { type Error = Error; - fn try_from(value: JValue<'a>) -> Result<Self> { + fn try_from(value: JValueGen<O>) -> Result<Self> { match value { - JValue::Long(l) => Ok(l), + JValueGen::Long(l) => Ok(l), _ => Err(Error::WrongJValueType("long", value.type_name())), } } } // jbyte -impl<'a> From<jbyte> for JValue<'a> { +impl<O> From<jbyte> for JValueGen<O> { fn from(other: jbyte) -> Self { - JValue::Byte(other) + JValueGen::Byte(other) } } -impl<'a> TryFrom<JValue<'a>> for jbyte { +impl<O> TryFrom<JValueGen<O>> for jbyte { type Error = Error; - fn try_from(value: JValue<'a>) -> Result<Self> { + fn try_from(value: JValueGen<O>) -> Result<Self> { match value { - JValue::Byte(b) => Ok(b), + JValueGen::Byte(b) => Ok(b), _ => Err(Error::WrongJValueType("byte", value.type_name())), } } } // jvoid -impl<'a> From<()> for JValue<'a> { +impl<O> From<()> for JValueGen<O> { fn from(_: ()) -> Self { - JValue::Void + JValueGen::Void } } -impl<'a> TryFrom<JValue<'a>> for () { +impl<O> TryFrom<JValueGen<O>> for () { type Error = Error; - fn try_from(value: JValue<'a>) -> Result<Self> { + fn try_from(value: JValueGen<O>) -> Result<Self> { match value { - JValue::Void => Ok(()), + JValueGen::Void => Ok(()), _ => Err(Error::WrongJValueType("void", value.type_name())), } } diff --git a/src/wrapper/objects/mod.rs b/src/wrapper/objects/mod.rs index 3209140..c01cf52 100644 --- a/src/wrapper/objects/mod.rs +++ b/src/wrapper/objects/mod.rs @@ -39,6 +39,9 @@ pub use self::jbytebuffer::*; mod global_ref; pub use self::global_ref::*; +mod weak_ref; +pub use self::weak_ref::*; + // For automatic local ref deletion mod auto_local; pub use self::auto_local::*; @@ -46,11 +49,18 @@ pub use self::auto_local::*; mod release_mode; pub use self::release_mode::*; +/// Primitive Array types +mod jobject_array; +pub use self::jobject_array::*; + +/// Primitive Array types +mod jprimitive_array; +pub use self::jprimitive_array::*; + // For automatic pointer-based generic array release -mod auto_array; -pub use self::auto_array::*; +mod auto_elements; +pub use self::auto_elements::*; // For automatic pointer-based primitive array release -mod auto_primitive_array; - -pub use self::auto_primitive_array::*; +mod auto_elements_critical; +pub use self::auto_elements_critical::*; diff --git a/src/wrapper/objects/release_mode.rs b/src/wrapper/objects/release_mode.rs index ff81df7..7df9689 100644 --- a/src/wrapper/objects/release_mode.rs +++ b/src/wrapper/objects/release_mode.rs @@ -1,8 +1,11 @@ use crate::sys::JNI_ABORT; +#[cfg(doc)] +use super::{AutoElements, AutoElementsCritical}; + /// ReleaseMode /// -/// This defines the release mode of AutoArray (and AutoPrimitiveArray) resources, and +/// This defines the release mode of [`AutoElements`] (and [`AutoElementsCritical`]) resources, and /// related release array functions. #[derive(Clone, Copy, Debug)] #[repr(i32)] diff --git a/src/wrapper/objects/weak_ref.rs b/src/wrapper/objects/weak_ref.rs new file mode 100644 index 0000000..71b7336 --- /dev/null +++ b/src/wrapper/objects/weak_ref.rs @@ -0,0 +1,176 @@ +use std::sync::Arc; + +use log::{debug, warn}; + +use crate::{ + errors::Result, + objects::{GlobalRef, JObject}, + sys, JNIEnv, JavaVM, +}; + +// Note: `WeakRef` must not implement `Into<JObject>`! If it did, then it would be possible to +// wrap it in `AutoLocal`, which would cause undefined behavior upon drop as a result of calling +// the wrong JNI function to delete the reference. + +/// A *weak* global JVM reference. These are global in scope like +/// [`GlobalRef`], and may outlive the `JNIEnv` they came from, but are +/// *not* guaranteed to not get collected until released. +/// +/// `WeakRef` can be cloned to use _the same_ weak reference in different +/// contexts. If you want to create yet another weak ref to the same java object, call +/// [`WeakRef::clone_in_jvm`]. +/// +/// Underlying weak reference will be dropped, when the last instance +/// of `WeakRef` leaves its scope. +/// +/// It is _recommended_ that a native thread that drops the weak reference is attached +/// to the Java thread (i.e., has an instance of `JNIEnv`). If the native thread is *not* attached, +/// the `WeakRef#drop` will print a warning and implicitly `attach` and `detach` it, which +/// significantly affects performance. + +#[derive(Clone)] +pub struct WeakRef { + inner: Arc<WeakRefGuard>, +} + +struct WeakRefGuard { + raw: sys::jweak, + vm: JavaVM, +} + +unsafe impl Send for WeakRef {} +unsafe impl Sync for WeakRef {} + +impl WeakRef { + /// Creates a new wrapper for a global reference. + /// + /// # Safety + /// + /// Expects a valid raw weak global reference that should be created with `NewWeakGlobalRef` + /// JNI function. + pub(crate) unsafe fn from_raw(vm: JavaVM, raw: sys::jweak) -> Self { + WeakRef { + inner: Arc::new(WeakRefGuard { raw, vm }), + } + } + + /// Returns the raw JNI weak reference. + pub fn as_raw(&self) -> sys::jweak { + self.inner.raw + } + + /// Creates a new local reference to this object. + /// + /// This object may have already been garbage collected by the time this method is called. If + /// so, this method returns `Ok(None)`. Otherwise, it returns `Ok(Some(r))` where `r` is the + /// new local reference. + /// + /// If this method returns `Ok(Some(r))`, it is guaranteed that the object will not be garbage + /// collected at least until `r` is deleted or becomes invalid. + pub fn upgrade_local<'local>(&self, env: &JNIEnv<'local>) -> Result<Option<JObject<'local>>> { + let r = env.new_local_ref(unsafe { JObject::from_raw(self.as_raw()) })?; + + // Per JNI spec, `NewLocalRef` will return a null pointer if the object was GC'd. + if r.is_null() { + Ok(None) + } else { + Ok(Some(r)) + } + } + + /// Creates a new strong global reference to this object. + /// + /// This object may have already been garbage collected by the time this method is called. If + /// so, this method returns `Ok(None)`. Otherwise, it returns `Ok(Some(r))` where `r` is the + /// new strong global reference. + /// + /// If this method returns `Ok(Some(r))`, it is guaranteed that the object will not be garbage + /// collected at least until `r` is dropped. + pub fn upgrade_global(&self, env: &JNIEnv) -> Result<Option<GlobalRef>> { + let r = env.new_global_ref(unsafe { JObject::from_raw(self.as_raw()) })?; + + // Unlike `NewLocalRef`, the JNI spec does *not* guarantee that `NewGlobalRef` will return a + // null pointer if the object was GC'd, so we'll have to check. + if env.is_same_object(&r, JObject::null())? { + Ok(None) + } else { + Ok(Some(r)) + } + } + + /// Checks if the object referred to by this `WeakRef` has been garbage collected. + /// + /// Note that garbage collection can happen at any moment, so a return of `Ok(true)` from this + /// method does not guarantee that [`WeakRef::upgrade_local`] or [`WeakRef::upgrade_global`] + /// will succeed. + /// + /// This is equivalent to + /// <code>self.[is_same_object][WeakRef::is_same_object](env, [JObject::null]\())</code>. + pub fn is_garbage_collected(&self, env: &JNIEnv) -> Result<bool> { + self.is_same_object(env, JObject::null()) + } + + // The following methods are wrappers around those `JNIEnv` methods that make sense for a weak + // reference. These methods exist because they use `JObject::from_raw` on the raw pointer of a + // weak reference. Although this usage is sound, it is `unsafe`. It's also confusing because + // `JObject` normally represents a strong reference. + + /// Returns true if this weak reference refers to the given object. Otherwise returns false. + /// + /// If `object` is [null][JObject::null], then this method is equivalent to + /// [`WeakRef::is_garbage_collected`]: it returns true if the object referred to by this + /// `WeakRef` has been garbage collected, or false if the object has not yet been garbage + /// collected. + pub fn is_same_object<'local, O>(&self, env: &JNIEnv<'local>, object: O) -> Result<bool> + where + O: AsRef<JObject<'local>>, + { + env.is_same_object(unsafe { JObject::from_raw(self.as_raw()) }, object) + } + + /// Returns true if this weak reference refers to the same object as another weak reference. + /// Otherwise returns false. + /// + /// This method will also return true if both weak references refer to an object that has been + /// garbage collected. + pub fn is_weak_ref_to_same_object(&self, env: &JNIEnv, other: &WeakRef) -> Result<bool> { + self.is_same_object(env, unsafe { JObject::from_raw(other.as_raw()) }) + } + + /// Creates a new weak reference to the same object that this one refers to. + /// + /// `WeakRef` implements [`Clone`], which should normally be used whenever a new `WeakRef` to + /// the same object is needed. However, that only increments an internal reference count and + /// does not actually create a new weak reference in the JVM. If you specifically need to have + /// the JVM create a new weak reference, use this method instead of `Clone`. + /// + /// This method returns `Ok(None)` if the object has already been garbage collected. + pub fn clone_in_jvm(&self, env: &JNIEnv) -> Result<Option<WeakRef>> { + env.new_weak_ref(unsafe { JObject::from_raw(self.as_raw()) }) + } +} + +impl Drop for WeakRefGuard { + fn drop(&mut self) { + fn drop_impl(env: &JNIEnv, raw: sys::jweak) -> Result<()> { + let internal = env.get_native_interface(); + // This method is safe to call in case of pending exceptions (see chapter 2 of the spec) + jni_unchecked!(internal, DeleteWeakGlobalRef, raw); + Ok(()) + } + + let res = match self.vm.get_env() { + Ok(env) => drop_impl(&env, self.raw), + Err(_) => { + warn!("Dropping a WeakRef in a detached thread. Fix your code if this message appears frequently (see the WeakRef docs)."); + self.vm + .attach_current_thread() + .and_then(|env| drop_impl(&env, self.raw)) + } + }; + + if let Err(err) = res { + debug!("error dropping weak ref: {:#?}", err); + } + } +} diff --git a/src/wrapper/signature.rs b/src/wrapper/signature.rs index 46444cc..6f86f1b 100644 --- a/src/wrapper/signature.rs +++ b/src/wrapper/signature.rs @@ -63,8 +63,8 @@ impl fmt::Display for JavaType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { JavaType::Primitive(ref ty) => ty.fmt(f), - JavaType::Object(ref name) => write!(f, "L{};", name), - JavaType::Array(ref ty) => write!(f, "[{}", ty), + JavaType::Object(ref name) => write!(f, "L{name};"), + JavaType::Array(ref ty) => write!(f, "[{ty}"), JavaType::Method(ref m) => m.fmt(f), } } @@ -131,7 +131,7 @@ impl fmt::Display for TypeSignature { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "(")?; for a in &self.args { - write!(f, "{}", a)?; + write!(f, "{a}")?; } write!(f, ")")?; write!(f, "{}", self.ret)?; @@ -247,12 +247,12 @@ mod test { ]; for each in inputs.iter() { - let res = JavaType::from_str(*each).unwrap(); - println!("{:#?}", res); - let s = format!("{}", res); + let res = JavaType::from_str(each).unwrap(); + println!("{res:#?}"); + let s = format!("{res}"); assert_eq!(s, *each); - let res2 = JavaType::from_str(*each).unwrap(); - println!("{:#?}", res2); + let res2 = JavaType::from_str(each).unwrap(); + println!("{res2:#?}"); assert_eq!(res2, res); } } diff --git a/src/wrapper/strings/ffi_str.rs b/src/wrapper/strings/ffi_str.rs index 7af5e48..a822f21 100644 --- a/src/wrapper/strings/ffi_str.rs +++ b/src/wrapper/strings/ffi_str.rs @@ -50,8 +50,8 @@ where } } -impl<'a> From<&'a JNIStr> for Cow<'a, str> { - fn from(other: &'a JNIStr) -> Cow<'a, str> { +impl<'str_ref> From<&'str_ref JNIStr> for Cow<'str_ref, str> { + fn from(other: &'str_ref JNIStr) -> Cow<'str_ref, str> { let bytes = other.to_bytes(); match from_java_cesu8(bytes) { Ok(s) => s, @@ -84,7 +84,7 @@ impl JNIStr { /// /// Expects a valid pointer to a null-terminated C string and does not perform any lifetime /// checks for the resulting value. - pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a JNIStr { + pub unsafe fn from_ptr<'jni_str>(ptr: *const c_char) -> &'jni_str JNIStr { &*(ffi::CStr::from_ptr(ptr) as *const ffi::CStr as *const ffi_str::JNIStr) } } diff --git a/src/wrapper/strings/java_str.rs b/src/wrapper/strings/java_str.rs index f964df2..07153a9 100644 --- a/src/wrapper/strings/java_str.rs +++ b/src/wrapper/strings/java_str.rs @@ -1,3 +1,4 @@ +use jni_sys::{jboolean, JNI_TRUE}; use std::{borrow::Cow, os::raw::c_char}; use log::warn; @@ -5,65 +6,198 @@ use log::warn; use crate::{errors::*, objects::JString, strings::JNIStr, JNIEnv}; /// Reference to a string in the JVM. Holds a pointer to the array -/// returned by GetStringUTFChars. Calls ReleaseStringUTFChars on Drop. +/// returned by `GetStringUTFChars`. Calls `ReleaseStringUTFChars` on Drop. /// Can be converted to a `&JNIStr` with the same cost as the `&CStr.from_ptr` /// conversion. -pub struct JavaStr<'a: 'b, 'b> { +pub struct JavaStr<'local, 'other_local: 'obj_ref, 'obj_ref> { internal: *const c_char, - obj: JString<'a>, - env: &'b JNIEnv<'a>, + obj: &'obj_ref JString<'other_local>, + env: JNIEnv<'local>, } -impl<'a: 'b, 'b> JavaStr<'a, 'b> { - /// Build a `JavaStr` from an object and a reference to the environment. You - /// probably want to use `JNIEnv::get_string` instead. - pub fn from_env(env: &'b JNIEnv<'a>, obj: JString<'a>) -> Result<Self> { - let ptr = env.get_string_utf_chars(obj)?; - let java_str = JavaStr { - internal: ptr, - env, - obj, - }; - Ok(java_str) +impl<'local, 'other_local: 'obj_ref, 'obj_ref> JavaStr<'local, 'other_local, 'obj_ref> { + /// Get a pointer to the character array beneath a [JString] + /// + /// The string will be `NULL` terminated and encoded as + /// [Modified UTF-8](https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8) / + /// [CESU-8](https://en.wikipedia.org/wiki/CESU-8). + /// + /// The implementation may either create a copy of the character array for + /// the given `String` or it may pin it to avoid it being collected by the + /// garbage collector. + /// + /// Returns a tuple with the pointer and the status of whether the implementation + /// created a copy of the underlying character array. + /// + /// # Warning + /// + /// The caller must release the array when they are done with it via + /// [Self::release_string_utf_chars] + /// + /// # Safety + /// + /// The caller must guarantee that the Object passed in is an instance of `java.lang.String`, + /// passing in anything else will lead to undefined behaviour (The JNI implementation + /// is likely to crash or abort the process). + unsafe fn get_string_utf_chars( + env: &JNIEnv<'_>, + obj: &JString<'_>, + ) -> Result<(*const c_char, bool)> { + non_null!(obj, "get_string_utf_chars obj argument"); + let mut is_copy: jboolean = 0; + let ptr: *const c_char = jni_non_null_call!( + env.get_raw(), + GetStringUTFChars, + obj.as_raw(), + &mut is_copy as *mut _ + ); + + let is_copy = is_copy == JNI_TRUE; + Ok((ptr, is_copy)) + } + + /// Release the backing string + /// + /// This will either free the copy that was made by `GetStringUTFChars` or unpin it so it + /// may be released by the garbage collector once there are no further references to the string. + /// + /// # Safety + /// + /// The caller must guarantee that [Self::internal] was constructed from a valid pointer obtained from [Self::get_string_utf_chars] + unsafe fn release_string_utf_chars(&mut self) -> Result<()> { + non_null!(self.obj, "release_string_utf_chars obj argument"); + // This method is safe to call in case of pending exceptions (see the chapter 2 of the spec) + jni_unchecked!( + self.env.get_raw(), + ReleaseStringUTFChars, + self.obj.as_raw(), + self.internal + ); + + Ok(()) } - /// Extract the raw C string pointer from the JavaStr. This will be - /// encoded using the JVM internal `CESU-8`-style. + /// Get a [JavaStr] from a [JNIEnv] and a [JString]. + /// You probably want [JNIEnv::get_string] instead of this method. + pub fn from_env(env: &JNIEnv<'local>, obj: &'obj_ref JString<'other_local>) -> Result<Self> { + Ok(unsafe { + let (ptr, _) = Self::get_string_utf_chars(env, obj)?; + + Self::from_raw(env, obj, ptr) + }) + } + + /// Get the raw string pointer from the JavaStr. + /// + /// The string will be `NULL` terminated and encoded as + /// [Modified UTF-8](https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8) / + /// [CESU-8](https://en.wikipedia.org/wiki/CESU-8). pub fn get_raw(&self) -> *const c_char { self.internal } + + /// Consumes the `JavaStr`, returning the raw string pointer + /// + /// The string will be `NULL` terminated and encoded as + /// [Modified UTF-8](https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8) / + /// [CESU-8](https://en.wikipedia.org/wiki/CESU-8). + /// + /// # Warning + /// The programmer is responsible for making sure the backing string gets + /// released when they are done with it, for example by reconstructing a + /// [JavaStr] with [`Self::from_raw`], which will release the backing string + /// when it is dropped. + pub fn into_raw(self) -> *const c_char { + let mut _dont_call_drop = std::mem::ManuallyDrop::new(self); + + // Drop the `JNIEnv` in place. As of this writing, that's a no-op, but if `JNIEnv` + // gains any drop code in the future, this will run it. + // + // Safety: The `&mut` proves that `self.env` is valid and not aliased. It is not + // accessed again after this point. Because `self` has been moved into `ManuallyDrop`, + // the `JNIEnv` will not be dropped twice. + unsafe { + std::ptr::drop_in_place(&mut _dont_call_drop.env); + } + + _dont_call_drop.internal + } + + /// Get a [JavaStr] from it's raw components + /// + /// # Safety + /// + /// The caller must guarantee that `ptr` is a valid, non-null pointer returned by [`Self::into_raw`], + /// and that `obj` is the same `String` object originally used to create the [JavaStr] + /// + /// # Example + /// ```rust,no_run + /// # use jni::{errors::Result, JNIEnv, strings::JavaStr}; + /// # + /// # fn example(env: &mut JNIEnv) -> Result<()> { + /// let jstring = env.new_string("foo")?; + /// let java_str = env.get_string(&jstring)?; + /// + /// let ptr = java_str.into_raw(); + /// // Do whatever you need with the pointer + /// let java_str = unsafe { JavaStr::from_raw(env, &jstring, ptr) }; + /// # Ok(()) + /// # } + /// ``` + pub unsafe fn from_raw( + env: &JNIEnv<'local>, + obj: &'obj_ref JString<'other_local>, + ptr: *const c_char, + ) -> Self { + Self { + internal: ptr, + obj, + + // Safety: The cloned `JNIEnv` will not be used to create any local references, only to + // release `ptr`. + env: env.unsafe_clone(), + } + } } -impl<'a: 'b, 'b> ::std::ops::Deref for JavaStr<'a, 'b> { +impl<'local, 'other_local: 'obj_ref, 'obj_ref> ::std::ops::Deref + for JavaStr<'local, 'other_local, 'obj_ref> +{ type Target = JNIStr; fn deref(&self) -> &Self::Target { self.into() } } -impl<'a: 'b, 'b: 'c, 'c> From<&'c JavaStr<'a, 'b>> for &'c JNIStr { - fn from(other: &'c JavaStr) -> &'c JNIStr { +impl<'local, 'other_local: 'obj_ref, 'obj_ref: 'java_str, 'java_str> + From<&'java_str JavaStr<'local, 'other_local, 'obj_ref>> for &'java_str JNIStr +{ + fn from(other: &'java_str JavaStr) -> &'java_str JNIStr { unsafe { JNIStr::from_ptr(other.internal) } } } -impl<'a: 'b, 'b: 'c, 'c> From<&'c JavaStr<'a, 'b>> for Cow<'c, str> { - fn from(other: &'c JavaStr) -> Cow<'c, str> { +impl<'local, 'other_local: 'obj_ref, 'obj_ref: 'java_str, 'java_str> + From<&'java_str JavaStr<'local, 'other_local, 'obj_ref>> for Cow<'java_str, str> +{ + fn from(other: &'java_str JavaStr) -> Cow<'java_str, str> { let jni_str: &JNIStr = other; jni_str.into() } } -impl<'a: 'b, 'b> From<JavaStr<'a, 'b>> for String { +impl<'local, 'other_local: 'obj_ref, 'obj_ref> From<JavaStr<'local, 'other_local, 'obj_ref>> + for String +{ fn from(other: JavaStr) -> String { let cow: Cow<str> = (&other).into(); cow.into_owned() } } -impl<'a: 'b, 'b> Drop for JavaStr<'a, 'b> { +impl<'local, 'other_local: 'obj_ref, 'obj_ref> Drop for JavaStr<'local, 'other_local, 'obj_ref> { fn drop(&mut self) { - match unsafe { self.env.release_string_utf_chars(self.obj, self.internal) } { + match unsafe { self.release_string_utf_chars() } { Ok(()) => {} Err(e) => warn!("error dropping java str: {}", e), } diff --git a/tests/executor.rs b/tests/executor.rs index fbd02d4..daaa632 100644 --- a/tests/executor.rs +++ b/tests/executor.rs @@ -1,11 +1,17 @@ #![cfg(feature = "invocation")] use std::{ - sync::{Arc, Barrier}, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Barrier, + }, thread::spawn, + time::Duration, }; -use jni::{sys::jint, Executor}; +use jni::{objects::AutoLocal, sys::jint, Executor}; + +use rusty_fork::rusty_fork_test; mod util; use util::{jvm, AtomicIntegerProxy}; @@ -73,3 +79,133 @@ fn test_concurrent_threads(executor: Executor, thread_num: usize) { let expected = (ITERS_PER_THREAD * thread_num) as jint; assert_eq!(expected, atomic.get().unwrap()); } + +// We need to test `JavaVM::destroy()` in a separate process otherwise it will break +// all the other tests +rusty_fork_test! { +#[test] +fn test_destroy() { + const THREAD_NUM: usize = 2; + const DAEMON_THREAD_NUM: usize = 2; + static MATH_CLASS: &str = "java/lang/Math"; + + // We don't test this using an `Executor` because we don't want to + // attach all the threads as daemon threads. + + let jvm = jvm().clone(); + + let atomic = Arc::new(AtomicUsize::new(0)); + + let attach_barrier = Arc::new(Barrier::new(THREAD_NUM + DAEMON_THREAD_NUM + 1)); + let daemons_detached_barrier = Arc::new(Barrier::new(DAEMON_THREAD_NUM + 1)); + let mut threads = Vec::new(); + + for _ in 0..THREAD_NUM { + let attach_barrier = Arc::clone(&attach_barrier); + let jvm = jvm.clone(); + let atomic = atomic.clone(); + let jh = spawn(move || { + let mut env = jvm.attach_current_thread().unwrap(); + println!("java thread attach"); + attach_barrier.wait(); + println!("java thread run"); + std::thread::sleep(Duration::from_millis(250)); + + println!("use before destroy..."); + // Make some token JNI call + let _class = AutoLocal::new(env.find_class(MATH_CLASS).unwrap(), &env); + + atomic.fetch_add(1, Ordering::SeqCst); + + println!("java thread finished"); + }); + threads.push(jh); + } + + for _ in 0..DAEMON_THREAD_NUM { + let attach_barrier = Arc::clone(&attach_barrier); + let daemons_detached_barrier = Arc::clone(&daemons_detached_barrier); + let jvm = jvm.clone(); + let atomic = atomic.clone(); + let jh = spawn(move || { + // We have to be _very_ careful to ensure we have finished accessing the + // JavaVM before it gets destroyed, including dropping the AutoLocal + // for the `MATH_CLASS` + { + let mut env = jvm.attach_current_thread_as_daemon().unwrap(); + println!("daemon thread attach"); + attach_barrier.wait(); + println!("daemon thread run"); + + println!("daemon JVM use before destroy..."); + + let _class = AutoLocal::new(env.find_class(MATH_CLASS).unwrap(), &env); + } + + // For it to be safe to call `JavaVM::destroy()` we need to ensure that + // daemon threads are detached from the JavaVM ahead of time because + // `JavaVM::destroy()` does not synchronize and wait for them to exit + // which means we would effectively trigger a use-after-free when daemon + // threads exit and they try to automatically detach from the `JavaVM` + // + // # Safety + // We won't be accessing any (invalid) `JNIEnv` once we have detached this + // thread + unsafe { + jvm.detach_current_thread(); + } + + daemons_detached_barrier.wait(); + + for _ in 0..10 { + std::thread::sleep(Duration::from_millis(100)); + println!("daemon thread running"); + } + + atomic.fetch_add(1, Ordering::SeqCst); + + println!("daemon thread finished"); + }); + threads.push(jh); + } + + // At this point we at least know that all threads have been attached + // to the JVM + println!("MAIN: waiting for threads attached barrier"); + attach_barrier.wait(); + + // Before we try and destroy the JavaVM we need to be sure that the daemon + // threads have finished using the VM since `jvm.destroy()` won't wait + // for daemon threads to exit. + println!("MAIN: waiting for daemon threads detached barrier"); + daemons_detached_barrier.wait(); + + // # Safety + // + // We drop the `jvm` variable immediately after `destroy()` returns to avoid + // any use-after-free. + unsafe { + println!("MAIN: calling DestroyJavaVM()..."); + jvm.destroy().unwrap(); + drop(jvm); + println!("MAIN: jvm destroyed"); + } + + println!("MAIN: joining (waiting for) all threads"); + let mut joined = 0; + for jh in threads { + jh.join().unwrap(); + joined += 1; + println!( + "joined {joined} threads, atomic = {}", + atomic.load(Ordering::SeqCst) + ); + } + + assert_eq!( + atomic.load(Ordering::SeqCst), + THREAD_NUM + DAEMON_THREAD_NUM + ); +} + +} diff --git a/tests/executor_nested_attach.rs b/tests/executor_nested_attach.rs index b0ef6ae..47f7a57 100644 --- a/tests/executor_nested_attach.rs +++ b/tests/executor_nested_attach.rs @@ -31,9 +31,9 @@ fn nested_attach() { fn check_nested_attach(vm: &Arc<JavaVM>, executor: Executor) { check_detached(vm); executor - .with_attached(|_| { + .with_attached::<_, _, Error>(|_| { check_attached(vm); - executor.with_attached(|_| { + executor.with_attached::<_, _, Error>(|_| { check_attached(vm); Ok(()) })?; diff --git a/tests/invocation_character_encoding.rs b/tests/invocation_character_encoding.rs new file mode 100644 index 0000000..4008177 --- /dev/null +++ b/tests/invocation_character_encoding.rs @@ -0,0 +1,43 @@ +// This is a separate test program because it has to start a JVM with a specific option. + +#![cfg(feature = "invocation")] + +use jni::{objects::JString, InitArgsBuilder, JavaVM}; + +#[test] +fn invocation_character_encoding() { + let jvm_args = InitArgsBuilder::new() + .version(jni::JNIVersion::V8) + .option("-Xcheck:jni") + // U+00A0 NO-BREAK SPACE is the only non-ASCII character that's present in all parts of + // ISO 8859. This minimizes the chance of this test failing as a result of the character + // not being present in the platform default character encoding. This test will still fail + // on platforms where the default character encoding cannot represent a no-break space, + // such as GBK. + .option("-Dnbsp=\u{00a0}") + .build() + .unwrap_or_else(|e| panic!("{:#?}", e)); + + let jvm = JavaVM::new(jvm_args).unwrap_or_else(|e| panic!("{:#?}", e)); + + let mut env = jvm.attach_current_thread().unwrap(); + + let prop_name = env.new_string("nbsp").unwrap(); + + let prop_value: JString = env + .call_static_method( + "java/lang/System", + "getProperty", + "(Ljava/lang/String;)Ljava/lang/String;", + &[(&prop_name).into()], + ) + .unwrap() + .l() + .unwrap() + .into(); + + let prop_value_str = env.get_string(&prop_value).unwrap(); + let prop_value_str: &str = prop_value_str.to_str().unwrap(); + + assert_eq!("\u{00a0}", prop_value_str); +} diff --git a/tests/java_integers.rs b/tests/java_integers.rs index 0d7c6fb..6a1cdf2 100644 --- a/tests/java_integers.rs +++ b/tests/java_integers.rs @@ -1,41 +1,43 @@ #![cfg(feature = "invocation")] -use jni::{objects::JObject, objects::JValue}; +use jni::{errors::Error, objects::JValue}; mod util; use util::{attach_current_thread, print_exception}; #[test] fn test_java_integers() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let array_length = 50; for value in -10..10 { - env.with_local_frame(16, || { + env.with_local_frame(16, |env| -> Result<_, Error> { let integer_value = env.new_object("java/lang/Integer", "(I)V", &[JValue::Int(value)])?; let values_array = - env.new_object_array(array_length, "java/lang/Integer", integer_value)?; - let values_array = unsafe { JObject::from_raw(values_array) }; + env.new_object_array(array_length, "java/lang/Integer", &integer_value)?; let result = env .call_static_method( "java/util/Arrays", "binarySearch", "([Ljava/lang/Object;Ljava/lang/Object;)I", - &[JValue::Object(values_array), JValue::Object(integer_value)], + &[ + JValue::Object(&values_array), + JValue::Object(&integer_value), + ], )? .i()?; assert!(0 <= result && result < array_length); - Ok(JObject::null()) + Ok(()) }) .unwrap_or_else(|e| { print_exception(&env); panic!("{:#?}", e); - }); + }) } } diff --git a/tests/jmap.rs b/tests/jmap.rs index f19005c..9945455 100644 --- a/tests/jmap.rs +++ b/tests/jmap.rs @@ -1,39 +1,41 @@ #![cfg(feature = "invocation")] -use jni::objects::{JMap, JObject}; +use jni::objects::{JMap, JObject, JString}; mod util; use util::{attach_current_thread, unwrap}; #[test] pub fn jmap_push_and_iterate() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let data = &["hello", "world", "from", "test"]; // Create a new map. Use LinkedHashMap to have predictable iteration order - let map_object = unwrap(&env, env.new_object("java/util/LinkedHashMap", "()V", &[])); - let map = unwrap(&env, JMap::from_env(&env, map_object)); + let map_object = unwrap(env.new_object("java/util/LinkedHashMap", "()V", &[]), &env); + let map = unwrap(JMap::from_env(&mut env, &map_object), &env); // Push all strings unwrap( - &env, data.iter().try_for_each(|s| { env.new_string(s) .map(JObject::from) - .and_then(|s| map.put(s, s).map(|_| ())) + .and_then(|s| map.put(&mut env, &s, &s).map(|_| ())) }), + &env, ); // Collect the keys using the JMap iterator let mut collected = Vec::new(); unwrap( - &env, - map.iter().and_then(|mut iter| { - iter.try_for_each(|e| { - env.get_string(e.0.into()) - .map(|s| collected.push(String::from(s))) - }) + map.iter(&mut env).and_then(|mut iter| { + while let Some(e) = iter.next(&mut env)? { + let s = JString::from(e.0); + let s = env.get_string(&s)?; + collected.push(String::from(s)); + } + Ok(()) }), + &env, ); let orig = data.to_vec(); diff --git a/tests/jni_api.rs b/tests/jni_api.rs index 050f8ec..2aebbbb 100644 --- a/tests/jni_api.rs +++ b/tests/jni_api.rs @@ -1,14 +1,14 @@ #![cfg(feature = "invocation")] - -use std::str::FromStr; +use std::{convert::TryFrom, str::FromStr}; use jni::{ descriptors::Desc, errors::Error, objects::{ - AutoArray, AutoLocal, JByteBuffer, JList, JObject, JString, JThrowable, JValue, ReleaseMode, + AutoElements, AutoLocal, JByteBuffer, JList, JObject, JString, JThrowable, JValue, + ReleaseMode, }, - signature::JavaType, + signature::{JavaType, Primitive, ReturnType}, strings::JNIString, sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jobject, jshort, jsize}, JNIEnv, @@ -33,74 +33,74 @@ static TESTING_OBJECT_STR: &str = "TESTING OBJECT"; #[test] pub fn call_method_returning_null() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); // Create an Exception with no message let obj = AutoLocal::new( + unwrap(env.new_object(EXCEPTION_CLASS, "()V", &[]), &env), &env, - unwrap(&env, env.new_object(EXCEPTION_CLASS, "()V", &[])), ); // Call Throwable#getMessage must return null let message = unwrap( - &env, env.call_method(&obj, "getMessage", "()Ljava/lang/String;", &[]), + &env, ); - let message_ref = env.auto_local(unwrap(&env, message.l())); + let message_ref = env.auto_local(unwrap(message.l(), &env)); - assert!(message_ref.as_obj().is_null()); + assert!(message_ref.is_null()); } #[test] pub fn is_instance_of_same_class() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let obj = AutoLocal::new( + unwrap(env.new_object(EXCEPTION_CLASS, "()V", &[]), &env), &env, - unwrap(&env, env.new_object(EXCEPTION_CLASS, "()V", &[])), ); - assert!(unwrap(&env, env.is_instance_of(&obj, EXCEPTION_CLASS))); + assert!(unwrap(env.is_instance_of(&obj, EXCEPTION_CLASS), &env)); } #[test] pub fn is_instance_of_superclass() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let obj = AutoLocal::new( + unwrap(env.new_object(ARITHMETIC_EXCEPTION_CLASS, "()V", &[]), &env), &env, - unwrap(&env, env.new_object(ARITHMETIC_EXCEPTION_CLASS, "()V", &[])), ); - assert!(unwrap(&env, env.is_instance_of(&obj, EXCEPTION_CLASS))); + assert!(unwrap(env.is_instance_of(&obj, EXCEPTION_CLASS), &env)); } #[test] pub fn is_instance_of_subclass() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let obj = AutoLocal::new( + unwrap(env.new_object(EXCEPTION_CLASS, "()V", &[]), &env), &env, - unwrap(&env, env.new_object(EXCEPTION_CLASS, "()V", &[])), ); assert!(!unwrap( + env.is_instance_of(&obj, ARITHMETIC_EXCEPTION_CLASS), &env, - env.is_instance_of(&obj, ARITHMETIC_EXCEPTION_CLASS) )); } #[test] pub fn is_instance_of_not_superclass() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let obj = AutoLocal::new( + unwrap(env.new_object(ARITHMETIC_EXCEPTION_CLASS, "()V", &[]), &env), &env, - unwrap(&env, env.new_object(ARITHMETIC_EXCEPTION_CLASS, "()V", &[])), ); - assert!(!unwrap(&env, env.is_instance_of(&obj, ARRAYLIST_CLASS))); + assert!(!unwrap(env.is_instance_of(&obj, ARRAYLIST_CLASS), &env)); } #[test] pub fn is_instance_of_null() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let obj = JObject::null(); - assert!(unwrap(&env, env.is_instance_of(obj, ARRAYLIST_CLASS))); - assert!(unwrap(&env, env.is_instance_of(obj, EXCEPTION_CLASS))); + assert!(unwrap(env.is_instance_of(&obj, ARRAYLIST_CLASS), &env)); + assert!(unwrap(env.is_instance_of(&obj, EXCEPTION_CLASS), &env)); assert!(unwrap( + env.is_instance_of(&obj, ARITHMETIC_EXCEPTION_CLASS), &env, - env.is_instance_of(obj, ARITHMETIC_EXCEPTION_CLASS) )); } @@ -108,16 +108,16 @@ pub fn is_instance_of_null() { pub fn is_same_object_diff_references() { let env = attach_current_thread(); let string = env.new_string(TESTING_OBJECT_STR).unwrap(); - let ref_from_string = unwrap(&env, env.new_local_ref::<JObject>(string.into())); - assert!(unwrap(&env, env.is_same_object(string, ref_from_string))); - unwrap(&env, env.delete_local_ref(ref_from_string)); + let ref_from_string = unwrap(env.new_local_ref(&string), &env); + assert!(unwrap(env.is_same_object(&string, &ref_from_string), &env)); + unwrap(env.delete_local_ref(ref_from_string), &env); } #[test] pub fn is_same_object_same_reference() { let env = attach_current_thread(); let string = env.new_string(TESTING_OBJECT_STR).unwrap(); - assert!(unwrap(&env, env.is_same_object(string, string))); + assert!(unwrap(env.is_same_object(&string, &string), &env)); } #[test] @@ -125,21 +125,21 @@ pub fn is_not_same_object() { let env = attach_current_thread(); let string = env.new_string(TESTING_OBJECT_STR).unwrap(); let same_src_str = env.new_string(TESTING_OBJECT_STR).unwrap(); - assert!(!unwrap(&env, env.is_same_object(string, same_src_str))); + assert!(!unwrap(env.is_same_object(string, same_src_str), &env)); } #[test] pub fn is_not_same_object_null() { let env = attach_current_thread(); assert!(unwrap( + env.is_same_object(JObject::null(), JObject::null()), &env, - env.is_same_object(JObject::null(), JObject::null()) )); } #[test] pub fn get_static_public_field() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let min_int_value = env .get_static_field(INTEGER_CLASS, "MIN_VALUE", "I") @@ -152,7 +152,7 @@ pub fn get_static_public_field() { #[test] pub fn get_static_public_field_by_id() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); // One can't pass a JavaType::Primitive(Primitive::Int) to // `get_static_field_id` unfortunately: #137 @@ -173,7 +173,7 @@ pub fn get_static_public_field_by_id() { #[test] pub fn pop_local_frame_pending_exception() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); env.push_local_frame(16).unwrap(); @@ -181,7 +181,7 @@ pub fn pop_local_frame_pending_exception() { .unwrap(); // Pop the local frame with a pending exception - env.pop_local_frame(JObject::null()) + unsafe { env.pop_local_frame(&JObject::null()) } .expect("JNIEnv#pop_local_frame must work in case of pending exception"); env.exception_clear().unwrap(); @@ -189,7 +189,7 @@ pub fn pop_local_frame_pending_exception() { #[test] pub fn push_local_frame_pending_exception() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception") .unwrap(); @@ -200,7 +200,7 @@ pub fn push_local_frame_pending_exception() { env.exception_clear().unwrap(); - env.pop_local_frame(JObject::null()).unwrap(); + unsafe { env.pop_local_frame(&JObject::null()) }.unwrap(); } #[test] @@ -212,48 +212,101 @@ pub fn push_local_frame_too_many_refs() { env.push_local_frame(frame_size) .expect_err("push_local_frame(2B) must Err"); - env.pop_local_frame(JObject::null()).unwrap(); + unsafe { env.pop_local_frame(&JObject::null()) }.unwrap(); } #[test] pub fn with_local_frame() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let s = env - .with_local_frame(16, || { - let res = env.new_string("Test").unwrap(); + .with_local_frame_returning_local::<_, jni::errors::Error>(16, |env| { + let res = env.new_string("Test")?; Ok(res.into()) }) - .unwrap(); + .unwrap() + .into(); let s = env - .get_string(s.into()) + .get_string(&s) .expect("The object returned from the local frame must remain valid"); assert_eq!(s.to_str().unwrap(), "Test"); } #[test] pub fn with_local_frame_pending_exception() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception") .unwrap(); // Try to allocate a frame of locals - env.with_local_frame(16, || Ok(JObject::null())) + env.with_local_frame(16, |_| -> Result<_, Error> { Ok(()) }) .expect("JNIEnv#with_local_frame must work in case of pending exception"); env.exception_clear().unwrap(); } #[test] +pub fn call_method_ok() { + let mut env = attach_current_thread(); + + let s = env.new_string(TESTING_OBJECT_STR).unwrap(); + + let v: jint = env + .call_method(s, "indexOf", "(I)I", &[JValue::Int('S' as i32)]) + .expect("JNIEnv#call_method should return JValue") + .i() + .unwrap(); + + assert_eq!(v, 2); +} + +#[test] +pub fn call_method_with_bad_args_errs() { + let mut env = attach_current_thread(); + + let s = env.new_string(TESTING_OBJECT_STR).unwrap(); + + let is_bad_typ = env + .call_method( + &s, + "indexOf", + "(I)I", + &[JValue::Float(std::f32::consts::PI)], + ) + .map_err(|error| matches!(error, Error::InvalidArgList(_))) + .expect_err("JNIEnv#callmethod with bad arg type should err"); + + assert!( + is_bad_typ, + "ErrorKind::InvalidArgList expected when passing bad value type" + ); + + let is_bad_len = env + .call_method( + &s, + "indexOf", + "(I)I", + &[JValue::Int('S' as i32), JValue::Long(3)], + ) + .map_err(|error| matches!(error, Error::InvalidArgList(_))) + .expect_err("JNIEnv#call_method with bad arg lengths should err"); + + assert!( + is_bad_len, + "ErrorKind::InvalidArgList expected when passing bad argument lengths" + ); +} + +#[test] pub fn call_static_method_ok() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let x = JValue::from(-10); let val: jint = env .call_static_method(MATH_CLASS, MATH_ABS_METHOD_NAME, MATH_ABS_SIGNATURE, &[x]) - .expect("JNIEnv#call_static_method_unsafe should return JValue") + .expect("JNIEnv#call_static_method should return JValue") .i() .unwrap(); @@ -261,8 +314,110 @@ pub fn call_static_method_ok() { } #[test] +pub fn call_static_method_unchecked_ok() { + let mut env = attach_current_thread(); + + let x = JValue::from(-10); + let math_class = env.find_class(MATH_CLASS).unwrap(); + let abs_method_id = env + .get_static_method_id(&math_class, MATH_ABS_METHOD_NAME, MATH_ABS_SIGNATURE) + .unwrap(); + let val: jint = unsafe { + env.call_static_method_unchecked( + &math_class, + abs_method_id, + ReturnType::Primitive(Primitive::Int), + &[x.as_jni()], + ) + } + .expect("JNIEnv#call_static_method_unchecked should return JValue") + .i() + .unwrap(); + + assert_eq!(val, 10); +} + +#[test] +pub fn call_new_object_unchecked_ok() { + let mut env = attach_current_thread(); + + let test_str = env.new_string(TESTING_OBJECT_STR).unwrap(); + let string_class = env.find_class(STRING_CLASS).unwrap(); + + let ctor_method_id = env + .get_method_id(&string_class, "<init>", "(Ljava/lang/String;)V") + .unwrap(); + let val: JObject = unsafe { + env.new_object_unchecked( + &string_class, + ctor_method_id, + &[JValue::from(&test_str).as_jni()], + ) + } + .expect("JNIEnv#new_object_unchecked should return JValue"); + + let jstr = JString::try_from(val).expect("asd"); + let javastr = env.get_string(&jstr).unwrap(); + let rstr = javastr.to_str().unwrap(); + assert_eq!(rstr, TESTING_OBJECT_STR); +} + +#[test] +pub fn call_new_object_with_bad_args_errs() { + let mut env = attach_current_thread(); + + let string_class = env.find_class(STRING_CLASS).unwrap(); + + let is_bad_typ = env + .new_object(&string_class, "(Ljava/lang/String;)V", &[JValue::Int(2)]) + .map_err(|error| matches!(error, Error::InvalidArgList(_))) + .expect_err("JNIEnv#new_object with bad arg type should err"); + + assert!( + is_bad_typ, + "ErrorKind::InvalidArgList expected when passing bad value type" + ); + + let s = env.new_string(TESTING_OBJECT_STR).unwrap(); + + let is_bad_len = env + .new_object( + &string_class, + "(Ljava/lang/String;)V", + &[JValue::from(&s), JValue::Int(2)], + ) + .map_err(|error| matches!(error, Error::InvalidArgList(_))) + .expect_err("JNIEnv#new_object with bad arg type should err"); + + assert!( + is_bad_len, + "ErrorKind::InvalidArgList expected when passing bad argument lengths" + ); +} + +/// Check that we get a runtime error if trying to instantiate with an array class. +/// +/// Although the JNI spec for `NewObjectA` states that the class "must not refer to an array class" +/// (and could therefor potentially trigger undefined behaviour if that rule is violated) we +/// expect that `JNIEnv::new_object()` shouldn't ever get as far as calling `NewObjectA` since +/// it will first fail (with a safe, runtime error) to lookup a method ID for any constructor. +/// (consistent with how [getConstructors()](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getConstructors()) +/// doesn't expose constructors for array classes) +#[test] +pub fn call_new_object_with_array_class() { + let mut env = attach_current_thread(); + + let byte_array = env.new_byte_array(16).unwrap(); + let array_class = env.get_object_class(byte_array).unwrap(); + // We just make up a plausible constructor signature + let result = env.new_object(&array_class, "(I)[B", &[JValue::Int(16)]); + + assert!(result.is_err()) +} + +#[test] pub fn call_static_method_throws() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let x = JValue::Long(4_000_000_000); let is_java_exception = env @@ -275,50 +430,72 @@ pub fn call_static_method_throws() { .map_err(|error| matches!(error, Error::JavaException)) .expect_err("JNIEnv#call_static_method_unsafe should return error"); + // Throws a java.lang.ArithmeticException: integer overflow assert!( is_java_exception, "ErrorKind::JavaException expected as error" ); - assert_pending_java_exception(&env); + assert_pending_java_exception(&mut env); } #[test] -pub fn call_static_method_wrong_arg() { - let env = attach_current_thread(); +pub fn call_static_method_with_bad_args_errs() { + let mut env = attach_current_thread(); let x = JValue::Double(4.567_891_23); - env.call_static_method( - MATH_CLASS, - MATH_TO_INT_METHOD_NAME, - MATH_TO_INT_SIGNATURE, - &[x], - ) - .expect_err("JNIEnv#call_static_method_unsafe should return error"); + let is_bad_typ = env + .call_static_method( + MATH_CLASS, + MATH_TO_INT_METHOD_NAME, + MATH_TO_INT_SIGNATURE, + &[x], + ) + .map_err(|error| matches!(error, Error::InvalidArgList(_))) + .expect_err("JNIEnv#call_static_method with bad arg type should err"); + + assert!( + is_bad_typ, + "ErrorKind::InvalidArgList expected when passing bad value type" + ); + + let is_bad_len = env + .call_static_method( + MATH_CLASS, + MATH_TO_INT_METHOD_NAME, + MATH_TO_INT_SIGNATURE, + &[JValue::Int(2), JValue::Int(3)], + ) + .map_err(|error| matches!(error, Error::InvalidArgList(_))) + .expect_err("JNIEnv#call_static_method with bad arg lengths should err"); - assert_pending_java_exception(&env); + assert!( + is_bad_len, + "ErrorKind::InvalidArgList expected when passing bad argument lengths" + ); } #[test] pub fn java_byte_array_from_slice() { let env = attach_current_thread(); let buf: &[u8] = &[1, 2, 3]; - let java_array = env - .byte_array_from_slice(buf) - .expect("JNIEnv#byte_array_from_slice must create a java array from slice"); - let obj = AutoLocal::new(&env, unsafe { JObject::from_raw(java_array) }); + let java_array = AutoLocal::new( + env.byte_array_from_slice(buf) + .expect("JNIEnv#byte_array_from_slice must create a java array from slice"), + &env, + ); - assert!(!obj.as_obj().is_null()); + assert!(!java_array.is_null()); let mut res: [i8; 3] = [0; 3]; - env.get_byte_array_region(java_array, 0, &mut res).unwrap(); + env.get_byte_array_region(&java_array, 0, &mut res).unwrap(); assert_eq!(res[0], 1); assert_eq!(res[1], 2); assert_eq!(res[2], 3); } -macro_rules! test_get_array_elements { - ( $jni_get:tt, $jni_type:ty, $new_array:tt, $get_array:tt, $set_array:tt ) => { +macro_rules! test_auto_array_read_write { + ( $test_name:tt, $jni_type:ty, $new_array:tt, $get_array:tt, $set_array:tt ) => { #[test] - pub fn $jni_get() { + pub fn $test_name() { let env = attach_current_thread(); // Create original Java array @@ -328,20 +505,20 @@ macro_rules! test_get_array_elements { .expect(stringify!(JNIEnv#$new_array must create a Java $jni_type array with given size)); // Insert array elements - let _ = env.$set_array(java_array, 0, buf); + let _ = env.$set_array(&java_array, 0, buf); // Use a scope to test Drop { // Get byte array elements auto wrapper - let auto_ptr: AutoArray<$jni_type> = { + let mut auto_ptr: AutoElements<$jni_type> = unsafe { // Make sure the lifetime is tied to the environment, // not the particular JNIEnv reference - let temporary_env: JNIEnv = *env; - temporary_env.$jni_get(java_array, ReleaseMode::CopyBack).unwrap() + let mut temporary_env: JNIEnv = env.unsafe_clone(); + temporary_env.get_array_elements(&java_array, ReleaseMode::CopyBack).unwrap() }; // Check array size - assert_eq!(auto_ptr.size().unwrap(), 2); + assert_eq!(auto_ptr.len(), 2); // Check pointer access let ptr = auto_ptr.as_ptr(); @@ -358,19 +535,34 @@ macro_rules! test_get_array_elements { assert_eq!(unsafe { *ptr.offset(0) } as i32, 0); assert_eq!(unsafe { *ptr.offset(1) } as i32, 1); - // Modify + // Check slice access + // + // # Safety + // + // We make sure that the slice is dropped before also testing access via `Deref` + // (to ensure we don't have aliased references) unsafe { - *ptr.offset(0) += 1 as $jni_type; - *ptr.offset(1) -= 1 as $jni_type; + let slice = std::slice::from_raw_parts(auto_ptr.as_ptr(), auto_ptr.len()); + assert_eq!(slice[0] as i32, 0); + assert_eq!(slice[1] as i32, 1); } + // Check access via Deref + assert_eq!(auto_ptr[0] as i32, 0); + assert_eq!(auto_ptr[1] as i32, 1); + + // Modify via DerefMut + let tmp = auto_ptr[1]; + auto_ptr[1] = auto_ptr[0]; + auto_ptr[0] = tmp; + // Commit would be necessary here, if there were no closure //auto_ptr.commit().unwrap(); } // Confirm modification of original Java array let mut res: [$jni_type; 2] = [0 as $jni_type; 2]; - env.$get_array(java_array, 0, &mut res).unwrap(); + env.$get_array(&java_array, 0, &mut res).unwrap(); assert_eq!(res[0] as i32, 1); assert_eq!(res[1] as i32, 0); } @@ -378,7 +570,7 @@ macro_rules! test_get_array_elements { } // Test generic get_array_elements -test_get_array_elements!( +test_auto_array_read_write!( get_array_elements, jint, new_int_array, @@ -387,7 +579,7 @@ test_get_array_elements!( ); // Test type-specific array accessors -test_get_array_elements!( +test_auto_array_read_write!( get_int_array_elements, jint, new_int_array, @@ -395,7 +587,7 @@ test_get_array_elements!( set_int_array_region ); -test_get_array_elements!( +test_auto_array_read_write!( get_long_array_elements, jlong, new_long_array, @@ -403,7 +595,7 @@ test_get_array_elements!( set_long_array_region ); -test_get_array_elements!( +test_auto_array_read_write!( get_byte_array_elements, jbyte, new_byte_array, @@ -411,7 +603,7 @@ test_get_array_elements!( set_byte_array_region ); -test_get_array_elements!( +test_auto_array_read_write!( get_boolean_array_elements, jboolean, new_boolean_array, @@ -419,7 +611,7 @@ test_get_array_elements!( set_boolean_array_region ); -test_get_array_elements!( +test_auto_array_read_write!( get_char_array_elements, jchar, new_char_array, @@ -427,7 +619,7 @@ test_get_array_elements!( set_char_array_region ); -test_get_array_elements!( +test_auto_array_read_write!( get_short_array_elements, jshort, new_short_array, @@ -435,7 +627,7 @@ test_get_array_elements!( set_short_array_region ); -test_get_array_elements!( +test_auto_array_read_write!( get_float_array_elements, jfloat, new_float_array, @@ -443,7 +635,7 @@ test_get_array_elements!( set_float_array_region ); -test_get_array_elements!( +test_auto_array_read_write!( get_double_array_elements, jdouble, new_double_array, @@ -454,7 +646,7 @@ test_get_array_elements!( #[test] #[ignore] // Disabled until issue #283 is resolved pub fn get_long_array_elements_commit() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); // Create original Java array let buf: &[i64] = &[1, 2, 3]; @@ -463,12 +655,13 @@ pub fn get_long_array_elements_commit() { .expect("JNIEnv#new_long_array must create a java array with given size"); // Insert array elements - let _ = env.set_long_array_region(java_array, 0, buf); + let _ = env.set_long_array_region(&java_array, 0, buf); // Get long array elements auto wrapper - let auto_ptr = env - .get_long_array_elements(java_array, ReleaseMode::CopyBack) - .unwrap(); + let mut auto_ptr = unsafe { + env.get_array_elements(&java_array, ReleaseMode::CopyBack) + .unwrap() + }; // Copying the array depends on the VM vendor/version/GC combinations. // If the wrapped array is not being copied, we can skip the test. @@ -488,7 +681,7 @@ pub fn get_long_array_elements_commit() { // Check that original Java array is unmodified let mut res: [i64; 3] = [0; 3]; - env.get_long_array_region(java_array, 0, &mut res).unwrap(); + env.get_long_array_region(&java_array, 0, &mut res).unwrap(); assert_eq!(res[0], 1); assert_eq!(res[1], 2); assert_eq!(res[2], 3); @@ -496,15 +689,15 @@ pub fn get_long_array_elements_commit() { auto_ptr.commit().unwrap(); // Confirm modification of original Java array - env.get_long_array_region(java_array, 0, &mut res).unwrap(); + env.get_long_array_region(&java_array, 0, &mut res).unwrap(); assert_eq!(res[0], 2); assert_eq!(res[1], 3); assert_eq!(res[2], 4); } #[test] -pub fn get_primitive_array_critical() { - let env = attach_current_thread(); +pub fn get_array_elements_critical() { + let mut env = attach_current_thread(); // Create original Java array let buf: &[u8] = &[1, 2, 3]; @@ -515,39 +708,41 @@ pub fn get_primitive_array_critical() { // Use a scope to test Drop { // Get primitive array elements auto wrapper - let auto_ptr = env - .get_primitive_array_critical(java_array, ReleaseMode::CopyBack) - .unwrap(); + let mut auto_ptr = unsafe { + env.get_array_elements_critical(&java_array, ReleaseMode::CopyBack) + .unwrap() + }; // Check array size - assert_eq!(auto_ptr.size().unwrap(), 3); - - // Get pointer - let ptr = auto_ptr.as_ptr(); - - // Convert void pointer to an unsigned byte array, without copy - let mut vec; - unsafe { vec = Vec::from_raw_parts(ptr as *mut u8, 3, 3) } - - // Check - assert_eq!(vec[0], 1); - assert_eq!(vec[1], 2); - assert_eq!(vec[2], 3); + assert_eq!(auto_ptr.len(), 3); + + // Convert void pointer to a &[i8] slice, without copy + // + // # Safety + // + // We make sure that the slice is dropped before also testing access via `Deref` + // (to ensure we don't have aliased references) + unsafe { + let slice = std::slice::from_raw_parts(auto_ptr.as_ptr(), auto_ptr.len()); + assert_eq!(slice[0], 1); + assert_eq!(slice[1], 2); + assert_eq!(slice[2], 3); + } - // Modify - vec[0] += 1; - vec[1] += 1; - vec[2] += 1; + // Also check access via `Deref` + assert_eq!(auto_ptr[0], 1); + assert_eq!(auto_ptr[1], 2); + assert_eq!(auto_ptr[2], 3); - // Release - // Make sure vec's destructor doesn't free the data it thinks it owns when it goes out - // of scope (avoid double free) - std::mem::forget(vec); + // Modify via `DerefMut` + auto_ptr[0] += 1; + auto_ptr[1] += 1; + auto_ptr[2] += 1; } // Confirm modification of original Java array let mut res: [i8; 3] = [0; 3]; - env.get_byte_array_region(java_array, 0, &mut res).unwrap(); + env.get_byte_array_region(&java_array, 0, &mut res).unwrap(); assert_eq!(res[0], 2); assert_eq!(res[1], 3); assert_eq!(res[2], 4); @@ -575,7 +770,7 @@ pub fn get_object_class_null_arg() { #[test] pub fn new_direct_byte_buffer() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let vec: Vec<u8> = vec![0, 1, 2, 3]; let (addr, len) = { // (would use buf.into_raw_parts() on nightly) @@ -589,14 +784,14 @@ pub fn new_direct_byte_buffer() { #[test] pub fn new_direct_byte_buffer_invalid_addr() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let result = unsafe { env.new_direct_byte_buffer(std::ptr::null_mut(), 5) }; assert!(result.is_err()); } #[test] pub fn get_direct_buffer_capacity_ok() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let vec: Vec<u8> = vec![0, 1, 2, 3]; let (addr, len) = { // (would use buf.into_raw_parts() on nightly) @@ -606,7 +801,7 @@ pub fn get_direct_buffer_capacity_ok() { let result = unsafe { env.new_direct_byte_buffer(addr, len) }.unwrap(); assert!(!result.is_null()); - let capacity = env.get_direct_buffer_capacity(result).unwrap(); + let capacity = env.get_direct_buffer_capacity(&result).unwrap(); assert_eq!(capacity, 4); } @@ -614,20 +809,20 @@ pub fn get_direct_buffer_capacity_ok() { pub fn get_direct_buffer_capacity_wrong_arg() { let env = attach_current_thread(); let wrong_obj = unsafe { JByteBuffer::from_raw(env.new_string("wrong").unwrap().into_raw()) }; - let capacity = env.get_direct_buffer_capacity(wrong_obj); + let capacity = env.get_direct_buffer_capacity(&wrong_obj); assert!(capacity.is_err()); } #[test] pub fn get_direct_buffer_capacity_null_arg() { let env = attach_current_thread(); - let result = env.get_direct_buffer_capacity(JObject::null().into()); + let result = env.get_direct_buffer_capacity(&JObject::null().into()); assert!(result.is_err()); } #[test] pub fn get_direct_buffer_address_ok() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let vec: Vec<u8> = vec![0, 1, 2, 3]; let (addr, len) = { // (would use buf.into_raw_parts() on nightly) @@ -637,7 +832,7 @@ pub fn get_direct_buffer_address_ok() { let result = unsafe { env.new_direct_byte_buffer(addr, len) }.unwrap(); assert!(!result.is_null()); - let dest_buffer = env.get_direct_buffer_address(result).unwrap(); + let dest_buffer = env.get_direct_buffer_address(&result).unwrap(); assert_eq!(addr, dest_buffer); } @@ -645,14 +840,14 @@ pub fn get_direct_buffer_address_ok() { pub fn get_direct_buffer_address_wrong_arg() { let env = attach_current_thread(); let wrong_obj: JObject = env.new_string("wrong").unwrap().into(); - let result = env.get_direct_buffer_address(wrong_obj.into()); + let result = env.get_direct_buffer_address(&wrong_obj.into()); assert!(result.is_err()); } #[test] pub fn get_direct_buffer_address_null_arg() { let env = attach_current_thread(); - let result = env.get_direct_buffer_address(JObject::null().into()); + let result = env.get_direct_buffer_address(&JObject::null().into()); assert!(result.is_err()); } @@ -698,56 +893,56 @@ pub fn new_primitive_array_ok() { // Group test for testing the family of new_PRIMITIVE_array functions with wrong arguments #[test] pub fn new_primitive_array_wrong() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); const WRONG_SIZE: jsize = -1; - let result = env.new_boolean_array(WRONG_SIZE); + let result = env.new_boolean_array(WRONG_SIZE).map(|arr| arr.as_raw()); assert_exception(&result, "JNIEnv#new_boolean_array should throw exception"); - assert_pending_java_exception(&env); + assert_pending_java_exception(&mut env); - let result = env.new_byte_array(WRONG_SIZE); + let result = env.new_byte_array(WRONG_SIZE).map(|arr| arr.as_raw()); assert_exception(&result, "JNIEnv#new_byte_array should throw exception"); - assert_pending_java_exception(&env); + assert_pending_java_exception(&mut env); - let result = env.new_char_array(WRONG_SIZE); + let result = env.new_char_array(WRONG_SIZE).map(|arr| arr.as_raw()); assert_exception(&result, "JNIEnv#new_char_array should throw exception"); - assert_pending_java_exception(&env); + assert_pending_java_exception(&mut env); - let result = env.new_short_array(WRONG_SIZE); + let result = env.new_short_array(WRONG_SIZE).map(|arr| arr.as_raw()); assert_exception(&result, "JNIEnv#new_short_array should throw exception"); - assert_pending_java_exception(&env); + assert_pending_java_exception(&mut env); - let result = env.new_int_array(WRONG_SIZE); + let result = env.new_int_array(WRONG_SIZE).map(|arr| arr.as_raw()); assert_exception(&result, "JNIEnv#new_int_array should throw exception"); - assert_pending_java_exception(&env); + assert_pending_java_exception(&mut env); - let result = env.new_long_array(WRONG_SIZE); + let result = env.new_long_array(WRONG_SIZE).map(|arr| arr.as_raw()); assert_exception(&result, "JNIEnv#new_long_array should throw exception"); - assert_pending_java_exception(&env); + assert_pending_java_exception(&mut env); - let result = env.new_float_array(WRONG_SIZE); + let result = env.new_float_array(WRONG_SIZE).map(|arr| arr.as_raw()); assert_exception(&result, "JNIEnv#new_float_array should throw exception"); - assert_pending_java_exception(&env); + assert_pending_java_exception(&mut env); - let result = env.new_double_array(WRONG_SIZE); + let result = env.new_double_array(WRONG_SIZE).map(|arr| arr.as_raw()); assert_exception(&result, "JNIEnv#new_double_array should throw exception"); - assert_pending_java_exception(&env); + assert_pending_java_exception(&mut env); } #[test] fn get_super_class_ok() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let result = env.get_superclass(ARRAYLIST_CLASS); assert!(result.is_ok()); - assert!(!result.unwrap().is_null()); + assert!(result.unwrap().is_some()); } #[test] fn get_super_class_null() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let result = env.get_superclass("java/lang/Object"); assert!(result.is_ok()); - assert!(result.unwrap().is_null()); + assert!(result.unwrap().is_none()); } #[test] @@ -766,7 +961,7 @@ fn local_ref_null() { let env = attach_current_thread(); let null_obj = JObject::null(); - let result = env.new_local_ref::<JObject>(null_obj); + let result = env.new_local_ref::<&JObject>(&null_obj); assert!(result.is_ok()); assert!(result.unwrap().is_null()); @@ -781,7 +976,15 @@ fn new_global_ref_null() { let null_obj = JObject::null(); let result = env.new_global_ref(null_obj); assert!(result.is_ok()); - assert!(result.unwrap().as_obj().is_null()); + assert!(result.unwrap().is_null()); +} + +#[test] +fn new_weak_ref_null() { + let env = attach_current_thread(); + let null_obj = JObject::null(); + let result = unwrap(env.new_weak_ref(null_obj), &env); + assert!(result.is_none()); } #[test] @@ -789,69 +992,73 @@ fn auto_local_null() { let env = attach_current_thread(); let null_obj = JObject::null(); { - let auto_ref = AutoLocal::new(&env, null_obj); - assert!(auto_ref.as_obj().is_null()); + let auto_ref = AutoLocal::new(null_obj, &env); + assert!(auto_ref.is_null()); } - assert!(null_obj.is_null()); } #[test] fn short_lifetime_with_local_frame() { - let env = attach_current_thread(); - let object = short_lifetime_with_local_frame_sub_fn(&env); + let mut env = attach_current_thread(); + let object = short_lifetime_with_local_frame_sub_fn(&mut env); assert!(object.is_ok()); } -fn short_lifetime_with_local_frame_sub_fn<'a>(env: &'_ JNIEnv<'a>) -> Result<JObject<'a>, Error> { - env.with_local_frame(16, || { +fn short_lifetime_with_local_frame_sub_fn<'local>( + env: &'_ mut JNIEnv<'local>, +) -> Result<JObject<'local>, Error> { + env.with_local_frame_returning_local(16, |env| { env.new_object(INTEGER_CLASS, "(I)V", &[JValue::from(5)]) }) } #[test] fn short_lifetime_list() { - let env = attach_current_thread(); - let first_list_object = short_lifetime_list_sub_fn(&env).unwrap(); + let mut env = attach_current_thread(); + let first_list_object = short_lifetime_list_sub_fn(&mut env).unwrap(); let value = env.call_method(first_list_object, "intValue", "()I", &[]); assert_eq!(value.unwrap().i().unwrap(), 1); } -fn short_lifetime_list_sub_fn<'a>(env: &'_ JNIEnv<'a>) -> Result<JObject<'a>, Error> { +fn short_lifetime_list_sub_fn<'local>( + env: &'_ mut JNIEnv<'local>, +) -> Result<JObject<'local>, Error> { let list_object = env.new_object(ARRAYLIST_CLASS, "()V", &[])?; - let list = JList::from_env(env, list_object)?; + let list = JList::from_env(env, &list_object)?; let element = env.new_object(INTEGER_CLASS, "(I)V", &[JValue::from(1)])?; - list.add(element)?; - short_lifetime_list_sub_fn_get_first_element(&list) + list.add(env, &element)?; + short_lifetime_list_sub_fn_get_first_element(env, &list) } -fn short_lifetime_list_sub_fn_get_first_element<'a>( - list: &'_ JList<'a, '_>, -) -> Result<JObject<'a>, Error> { - let mut iterator = list.iter()?; - Ok(iterator.next().unwrap()) +fn short_lifetime_list_sub_fn_get_first_element<'local>( + env: &'_ mut JNIEnv<'local>, + list: &'_ JList<'local, '_, '_>, +) -> Result<JObject<'local>, Error> { + let mut iterator = list.iter(env)?; + Ok(iterator.next(env)?.unwrap()) } #[test] fn get_object_array_element() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let array = env .new_object_array(1, STRING_CLASS, JObject::null()) .unwrap(); assert!(!array.is_null()); - assert!(env.get_object_array_element(array, 0).unwrap().is_null()); + assert!(env.get_object_array_element(&array, 0).unwrap().is_null()); let test_str = env.new_string("test").unwrap(); - env.set_object_array_element(array, 0, test_str).unwrap(); - assert!(!env.get_object_array_element(array, 0).unwrap().is_null()); + env.set_object_array_element(&array, 0, test_str).unwrap(); + assert!(!env.get_object_array_element(&array, 0).unwrap().is_null()); } #[test] pub fn throw_new() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let result = env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception"); assert!(result.is_ok()); assert_pending_java_exception_detailed( - &env, + &mut env, Some(RUNTIME_EXCEPTION_CLASS), Some("Test Exception"), ); @@ -859,21 +1066,21 @@ pub fn throw_new() { #[test] pub fn throw_new_fail() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); let result = env.throw_new("java/lang/NonexistentException", "Test Exception"); assert!(result.is_err()); // Just to clear the java.lang.NoClassDefFoundError - assert_pending_java_exception(&env); + assert_pending_java_exception(&mut env); } #[test] pub fn throw_defaults() { - let env = attach_current_thread(); + let mut env = attach_current_thread(); - test_throwable_descriptor_with_default_type(&env, TEST_EXCEPTION_MESSAGE); - test_throwable_descriptor_with_default_type(&env, TEST_EXCEPTION_MESSAGE.to_owned()); - test_throwable_descriptor_with_default_type(&env, JNIString::from(TEST_EXCEPTION_MESSAGE)); + test_throwable_descriptor_with_default_type(&mut env, TEST_EXCEPTION_MESSAGE); + test_throwable_descriptor_with_default_type(&mut env, TEST_EXCEPTION_MESSAGE.to_owned()); + test_throwable_descriptor_with_default_type(&mut env, JNIString::from(TEST_EXCEPTION_MESSAGE)); } #[test] @@ -881,26 +1088,52 @@ pub fn test_conversion() { let env = attach_current_thread(); let orig_obj: JObject = env.new_string("Hello, world!").unwrap().into(); - let string = JString::from(orig_obj); + let obj: JObject = unwrap(env.new_local_ref(&orig_obj), &env); + let string = JString::from(obj); let actual = JObject::from(string); - assert!(unwrap(&env, env.is_same_object(orig_obj, actual))); + assert!(unwrap(env.is_same_object(&orig_obj, actual), &env)); + + let global_ref = env.new_global_ref(&orig_obj).unwrap(); + assert!(unwrap(env.is_same_object(&orig_obj, global_ref), &env)); + + let weak_ref = unwrap(env.new_weak_ref(&orig_obj), &env).expect("weak ref should not be null"); + let actual = + unwrap(weak_ref.upgrade_local(&env), &env).expect("weak ref should not have been GC'd"); + assert!(unwrap(env.is_same_object(&orig_obj, actual), &env)); + + let obj: JObject = unwrap(env.new_local_ref(&orig_obj), &env); + let auto_local = env.auto_local(obj); + assert!(unwrap(env.is_same_object(&orig_obj, auto_local), &env)); +} + +#[test] +pub fn test_null_get_string() { + let mut env = attach_current_thread(); + let s = unsafe { JString::from_raw(std::ptr::null_mut() as _) }; + let ret = env.get_string(&s); + assert!(ret.is_err()); +} + +#[test] +pub fn test_invalid_list_get_string() { + let mut env = attach_current_thread(); - let global_ref = env.new_global_ref(orig_obj).unwrap(); - let actual = JObject::from(&global_ref); - assert!(unwrap(&env, env.is_same_object(orig_obj, actual))); + let class = env.find_class("java/util/List").unwrap(); + let class = JString::from(JObject::from(class)); + let class = env.auto_local(class); - let auto_local = env.auto_local(orig_obj); - let actual = JObject::from(&auto_local); - assert!(unwrap(&env, env.is_same_object(orig_obj, actual))); + let ret = env.get_string(&class); + assert!(ret.is_err()); } -fn test_throwable_descriptor_with_default_type<'a, D>(env: &JNIEnv<'a>, descriptor: D) +fn test_throwable_descriptor_with_default_type<'local, D>(env: &mut JNIEnv<'local>, descriptor: D) where - D: Desc<'a, JThrowable<'a>>, + D: Desc<'local, JThrowable<'local>>, { let result = descriptor.lookup(env); assert!(result.is_ok()); let exception = result.unwrap(); + let exception = exception.as_ref(); assert_exception_type(env, exception, RUNTIME_EXCEPTION_CLASS); assert_exception_message(env, exception, TEST_EXCEPTION_MESSAGE); @@ -917,14 +1150,14 @@ fn assert_exception(res: &Result<jobject, Error>, expect_message: &str) { // Shortcut to `assert_pending_java_exception_detailed()` without checking for expected type and // message of exception. -fn assert_pending_java_exception(env: &JNIEnv) { +fn assert_pending_java_exception(env: &mut JNIEnv) { assert_pending_java_exception_detailed(env, None, None) } // Helper method that asserts there is a pending Java exception of `expected_type` with // `expected_message` and clears it if any. fn assert_pending_java_exception_detailed( - env: &JNIEnv, + env: &mut JNIEnv, expected_type: Option<&str>, expected_message: Option<&str>, ) { @@ -933,26 +1166,26 @@ fn assert_pending_java_exception_detailed( env.exception_clear().unwrap(); if let Some(expected_type) = expected_type { - assert_exception_type(env, exception, expected_type); + assert_exception_type(env, &exception, expected_type); } if let Some(expected_message) = expected_message { - assert_exception_message(env, exception, expected_message); + assert_exception_message(env, &exception, expected_message); } } // Asserts that exception is of `expected_type` type. -fn assert_exception_type(env: &JNIEnv, exception: JThrowable, expected_type: &str) { +fn assert_exception_type(env: &mut JNIEnv, exception: &JThrowable, expected_type: &str) { assert!(env.is_instance_of(exception, expected_type).unwrap()); } // Asserts that exception's message is `expected_message`. -fn assert_exception_message(env: &JNIEnv, exception: JThrowable, expected_message: &str) { +fn assert_exception_message(env: &mut JNIEnv, exception: &JThrowable, expected_message: &str) { let message = env .call_method(exception, "getMessage", "()Ljava/lang/String;", &[]) .unwrap() .l() .unwrap(); - let msg_rust: String = env.get_string(message.into()).unwrap().into(); + let msg_rust: String = env.get_string(&message.into()).unwrap().into(); assert_eq!(msg_rust, expected_message); } diff --git a/tests/jni_global_refs.rs b/tests/jni_global_refs.rs index 2adc628..54a0831 100644 --- a/tests/jni_global_refs.rs +++ b/tests/jni_global_refs.rs @@ -17,22 +17,22 @@ use util::{attach_current_thread, unwrap}; pub fn global_ref_works_in_other_threads() { const ITERS_PER_THREAD: usize = 10_000; - let env = attach_current_thread(); + let mut env = attach_current_thread(); let mut join_handlers = Vec::new(); let atomic_integer = { let local_ref = AutoLocal::new( - &env, unwrap( - &env, env.new_object( "java/util/concurrent/atomic/AtomicInteger", "(I)V", &[JValue::from(0)], ), + &env, ), + &env, ); - unwrap(&env, env.new_global_ref(&local_ref)) + unwrap(env.new_global_ref(&local_ref), &env) }; // Test with a different number of threads (from 2 to 8) @@ -44,16 +44,16 @@ pub fn global_ref_works_in_other_threads() { let atomic_integer = atomic_integer.clone(); let jh = spawn(move || { - let env = attach_current_thread(); + let mut env = attach_current_thread(); barrier.wait(); for _ in 0..ITERS_PER_THREAD { unwrap( - &env, unwrap( - &env, env.call_method(&atomic_integer, "incrementAndGet", "()I", &[]), + &env, ) .i(), + &env, ); } }); @@ -68,12 +68,12 @@ pub fn global_ref_works_in_other_threads() { assert_eq!( expected, unwrap( - &env, unwrap( + env.call_method(&atomic_integer, "getAndSet", "(I)I", &[JValue::from(0)]), &env, - env.call_method(&atomic_integer, "getAndSet", "(I)I", &[JValue::from(0)]) ) - .i() + .i(), + &env, ) ); } diff --git a/tests/jni_weak_refs.rs b/tests/jni_weak_refs.rs new file mode 100644 index 0000000..6829507 --- /dev/null +++ b/tests/jni_weak_refs.rs @@ -0,0 +1,195 @@ +#![cfg(feature = "invocation")] + +use std::{ + sync::{Arc, Barrier}, + thread::spawn, +}; + +use jni::{ + objects::{AutoLocal, JValue}, + sys::jint, + JNIEnv, +}; + +mod util; +use util::{attach_current_thread, unwrap}; + +#[test] +pub fn weak_ref_works_in_other_threads() { + const ITERS_PER_THREAD: usize = 10_000; + + let mut env = attach_current_thread(); + let mut join_handlers = Vec::new(); + + let atomic_integer_local = AutoLocal::new( + unwrap( + env.new_object( + "java/util/concurrent/atomic/AtomicInteger", + "(I)V", + &[JValue::from(0)], + ), + &env, + ), + &env, + ); + let atomic_integer = + unwrap(env.new_weak_ref(&atomic_integer_local), &env).expect("weak ref should not be null"); + + // Test with a different number of threads (from 2 to 8) + for thread_num in 2..9 { + let barrier = Arc::new(Barrier::new(thread_num)); + + for _ in 0..thread_num { + let barrier = barrier.clone(); + let atomic_integer = atomic_integer.clone(); + + let jh = spawn(move || { + let mut env = attach_current_thread(); + barrier.wait(); + for _ in 0..ITERS_PER_THREAD { + let atomic_integer = env.auto_local( + unwrap(atomic_integer.upgrade_local(&env), &env) + .expect("AtomicInteger shouldn't have been GC'd yet"), + ); + unwrap( + unwrap( + env.call_method(&atomic_integer, "incrementAndGet", "()I", &[]), + &env, + ) + .i(), + &env, + ); + } + }); + join_handlers.push(jh); + } + + for jh in join_handlers.drain(..) { + jh.join().unwrap(); + } + + let expected = (ITERS_PER_THREAD * thread_num) as jint; + assert_eq!( + expected, + unwrap( + unwrap( + env.call_method( + &atomic_integer_local, + "getAndSet", + "(I)I", + &[JValue::from(0)] + ), + &env, + ) + .i(), + &env, + ) + ); + } +} + +#[test] +fn weak_ref_is_actually_weak() { + let mut env = attach_current_thread(); + + // This test uses `with_local_frame` to work around issue #109. + + fn run_gc(env: &mut JNIEnv) { + unwrap( + env.with_local_frame(1, |env| { + env.call_static_method("java/lang/System", "gc", "()V", &[])?; + Ok(()) + }), + env, + ); + } + + for _ in 0..100 { + let obj_local = unwrap( + env.with_local_frame_returning_local(2, |env| { + env.new_object("java/lang/Object", "()V", &[]) + }), + &env, + ); + let obj_local = env.auto_local(obj_local); + + let obj_weak = + unwrap(env.new_weak_ref(&obj_local), &env).expect("weak ref should not be null"); + + let obj_weak2 = + unwrap(obj_weak.clone_in_jvm(&env), &env).expect("weak ref should not be null"); + + run_gc(&mut env); + + for obj_weak in &[&obj_weak, &obj_weak2] { + { + let obj_local_from_weak = env.auto_local( + unwrap(obj_weak.upgrade_local(&env), &env) + .expect("object shouldn't have been GC'd yet"), + ); + + assert!(!obj_local_from_weak.is_null()); + assert!(unwrap( + env.is_same_object(&obj_local_from_weak, &obj_local), + &env, + )); + } + + { + let obj_global_from_weak = unwrap(obj_weak.upgrade_global(&env), &env) + .expect("object shouldn't have been GC'd yet"); + + assert!(!obj_global_from_weak.is_null()); + assert!(unwrap( + env.is_same_object(&obj_global_from_weak, &obj_local), + &env, + )); + } + + assert!(unwrap(obj_weak.is_same_object(&env, &obj_local), &env)); + + assert!( + !unwrap(obj_weak.is_garbage_collected(&env), &env), + "`is_garbage_collected` returned incorrect value" + ); + } + + assert!(unwrap( + obj_weak.is_weak_ref_to_same_object(&env, &obj_weak2), + &env, + )); + + drop(obj_local); + run_gc(&mut env); + + for obj_weak in &[&obj_weak, &obj_weak2] { + { + let obj_local_from_weak = unwrap(obj_weak.upgrade_local(&env), &env); + + assert!( + obj_local_from_weak.is_none(), + "object should have been GC'd" + ); + } + + { + let obj_global_from_weak = unwrap(obj_weak.upgrade_global(&env), &env); + + assert!( + obj_global_from_weak.is_none(), + "object should have been GC'd" + ); + } + + assert!( + unwrap(obj_weak.is_garbage_collected(&env), &env), + "`is_garbage_collected` returned incorrect value" + ); + } + + assert!(unwrap( + obj_weak.is_weak_ref_to_same_object(&env, &obj_weak2), + &env, + )); + } +} diff --git a/tests/threads_attach_guard.rs b/tests/threads_attach_guard.rs index d43f807..a6a1b0a 100644 --- a/tests/threads_attach_guard.rs +++ b/tests/threads_attach_guard.rs @@ -7,9 +7,9 @@ use util::{attach_current_thread, call_java_abs, jvm}; fn thread_attach_guard_detaches_on_drop() { assert_eq!(jvm().threads_attached(), 0); { - let guard = attach_current_thread(); + let mut guard = attach_current_thread(); assert_eq!(jvm().threads_attached(), 1); - let val = call_java_abs(&guard, -1); + let val = call_java_abs(&mut guard, -1); assert_eq!(val, 1); } assert_eq!(jvm().threads_attached(), 0); diff --git a/tests/threads_detach.rs b/tests/threads_detach.rs index 922bca7..98e8b08 100644 --- a/tests/threads_detach.rs +++ b/tests/threads_detach.rs @@ -8,8 +8,8 @@ use util::{attach_current_thread_permanently, call_java_abs, jvm}; #[test] fn thread_detaches_when_finished() { let thread = spawn(|| { - let env = attach_current_thread_permanently(); - let val = call_java_abs(&env, -2); + let mut env = attach_current_thread_permanently(); + let val = call_java_abs(&mut env, -2); assert_eq!(val, 2); assert_eq!(jvm().threads_attached(), 1); }); diff --git a/tests/threads_detach_daemon.rs b/tests/threads_detach_daemon.rs index 9531a35..2ef5c58 100644 --- a/tests/threads_detach_daemon.rs +++ b/tests/threads_detach_daemon.rs @@ -8,8 +8,8 @@ use util::{attach_current_thread_as_daemon, call_java_abs, jvm}; #[test] fn daemon_thread_detaches_when_finished() { let thread = spawn(|| { - let env = attach_current_thread_as_daemon(); - let val = call_java_abs(&env, -3); + let mut env = attach_current_thread_as_daemon(); + let val = call_java_abs(&mut env, -3); assert_eq!(val, 3); assert_eq!(jvm().threads_attached(), 1); }); diff --git a/tests/threads_explicit_detach.rs b/tests/threads_explicit_detach.rs index 3010ca3..f1e0b5b 100644 --- a/tests/threads_explicit_detach.rs +++ b/tests/threads_explicit_detach.rs @@ -6,12 +6,16 @@ use util::{attach_current_thread, call_java_abs, detach_current_thread, jvm}; #[test] pub fn explicit_detach_detaches_thread_attached_locally() { assert_eq!(jvm().threads_attached(), 0); - let guard = attach_current_thread(); - let val = call_java_abs(&guard, -1); + let mut guard = attach_current_thread(); + let val = call_java_abs(&mut guard, -1); assert_eq!(val, 1); assert_eq!(jvm().threads_attached(), 1); - detach_current_thread(); + // # Safety + // we won't be trying to use a pre-existing (invalid) `JNIEnv` after detaching + unsafe { + detach_current_thread(); + } assert_eq!(jvm().threads_attached(), 0); assert!(jvm().get_env().is_err()); } diff --git a/tests/threads_explicit_detach_daemon.rs b/tests/threads_explicit_detach_daemon.rs index 76d4f86..0253565 100644 --- a/tests/threads_explicit_detach_daemon.rs +++ b/tests/threads_explicit_detach_daemon.rs @@ -6,12 +6,16 @@ use util::{attach_current_thread_as_daemon, call_java_abs, detach_current_thread #[test] pub fn explicit_detach_detaches_thread_attached_as_daemon() { assert_eq!(jvm().threads_attached(), 0); - let guard = attach_current_thread_as_daemon(); - let val = call_java_abs(&guard, -1); + let mut guard = attach_current_thread_as_daemon(); + let val = call_java_abs(&mut guard, -1); assert_eq!(val, 1); assert_eq!(jvm().threads_attached(), 1); - detach_current_thread(); + // # Safety + // we won't be trying to use a pre-existing (invalid) `JNIEnv` after detaching + unsafe { + detach_current_thread(); + } assert_eq!(jvm().threads_attached(), 0); assert!(jvm().get_env().is_err()); } diff --git a/tests/threads_explicit_detach_permanent.rs b/tests/threads_explicit_detach_permanent.rs index a58937f..16eb849 100644 --- a/tests/threads_explicit_detach_permanent.rs +++ b/tests/threads_explicit_detach_permanent.rs @@ -6,12 +6,16 @@ use util::{attach_current_thread_permanently, call_java_abs, detach_current_thre #[test] pub fn explicit_detach_detaches_thread_attached_permanently() { assert_eq!(jvm().threads_attached(), 0); - let guard = attach_current_thread_permanently(); - let val = call_java_abs(&guard, -1); + let mut guard = attach_current_thread_permanently(); + let val = call_java_abs(&mut guard, -1); assert_eq!(val, 1); assert_eq!(jvm().threads_attached(), 1); - detach_current_thread(); + // # Safety + // we won't be trying to use a pre-existing (invalid) `JNIEnv` after detaching + unsafe { + detach_current_thread(); + } assert_eq!(jvm().threads_attached(), 0); assert!(jvm().get_env().is_err()); } diff --git a/tests/threads_nested_attach_daemon.rs b/tests/threads_nested_attach_daemon.rs index 2515f61..30ee919 100644 --- a/tests/threads_nested_attach_daemon.rs +++ b/tests/threads_nested_attach_daemon.rs @@ -9,29 +9,29 @@ use util::{ #[test] pub fn nested_attaches_should_not_detach_daemon_thread() { assert_eq!(jvm().threads_attached(), 0); - let env = attach_current_thread_as_daemon(); - let val = call_java_abs(&env, -1); + let mut env = attach_current_thread_as_daemon(); + let val = call_java_abs(&mut env, -1); assert_eq!(val, 1); assert_eq!(jvm().threads_attached(), 1); // Create nested AttachGuard. { - let env_nested = attach_current_thread(); - let val = call_java_abs(&env_nested, -2); + let mut env_nested = attach_current_thread(); + let val = call_java_abs(&mut env_nested, -2); assert_eq!(val, 2); assert_eq!(jvm().threads_attached(), 1); } // Call a Java method after nested guard has been dropped to check that // this thread has not been detached. - let val = call_java_abs(&env, -3); + let val = call_java_abs(&mut env, -3); assert_eq!(val, 3); assert_eq!(jvm().threads_attached(), 1); // Nested attach_permanently is a no-op. { - let env_nested = attach_current_thread_permanently(); - let val = call_java_abs(&env_nested, -4); + let mut env_nested = attach_current_thread_permanently(); + let val = call_java_abs(&mut env_nested, -4); assert_eq!(val, 4); assert_eq!(jvm().threads_attached(), 1); } @@ -39,8 +39,8 @@ pub fn nested_attaches_should_not_detach_daemon_thread() { // Nested attach_as_daemon is a no-op. { - let env_nested = attach_current_thread_as_daemon(); - let val = call_java_abs(&env_nested, -5); + let mut env_nested = attach_current_thread_as_daemon(); + let val = call_java_abs(&mut env_nested, -5); assert_eq!(val, 5); assert_eq!(jvm().threads_attached(), 1); } diff --git a/tests/threads_nested_attach_guard.rs b/tests/threads_nested_attach_guard.rs index c9d4aca..0e09880 100644 --- a/tests/threads_nested_attach_guard.rs +++ b/tests/threads_nested_attach_guard.rs @@ -9,29 +9,29 @@ use util::{ #[test] pub fn nested_attaches_should_not_detach_guarded_thread() { assert_eq!(jvm().threads_attached(), 0); - let env = attach_current_thread(); - let val = call_java_abs(&env, -1); + let mut env = attach_current_thread(); + let val = call_java_abs(&mut env, -1); assert_eq!(val, 1); assert_eq!(jvm().threads_attached(), 1); // Create nested AttachGuard. { - let env_nested = attach_current_thread(); - let val = call_java_abs(&env_nested, -2); + let mut env_nested = attach_current_thread(); + let val = call_java_abs(&mut env_nested, -2); assert_eq!(val, 2); assert_eq!(jvm().threads_attached(), 1); } // Call a Java method after nested guard has been dropped to check that // this thread has not been detached. - let val = call_java_abs(&env, -3); + let val = call_java_abs(&mut env, -3); assert_eq!(val, 3); assert_eq!(jvm().threads_attached(), 1); // Nested attach_permanently is a no-op. { - let env_nested = attach_current_thread_permanently(); - let val = call_java_abs(&env_nested, -4); + let mut env_nested = attach_current_thread_permanently(); + let val = call_java_abs(&mut env_nested, -4); assert_eq!(val, 4); assert_eq!(jvm().threads_attached(), 1); } @@ -39,8 +39,8 @@ pub fn nested_attaches_should_not_detach_guarded_thread() { // Nested attach_as_daemon is a no-op. { - let env_nested = attach_current_thread_as_daemon(); - let val = call_java_abs(&env_nested, -5); + let mut env_nested = attach_current_thread_as_daemon(); + let val = call_java_abs(&mut env_nested, -5); assert_eq!(val, 5); assert_eq!(jvm().threads_attached(), 1); } diff --git a/tests/threads_nested_attach_permanently.rs b/tests/threads_nested_attach_permanently.rs index 42ad9ec..7bd0e52 100644 --- a/tests/threads_nested_attach_permanently.rs +++ b/tests/threads_nested_attach_permanently.rs @@ -9,29 +9,29 @@ use util::{ #[test] pub fn nested_attaches_should_not_detach_permanent_thread() { assert_eq!(jvm().threads_attached(), 0); - let env = attach_current_thread_permanently(); - let val = call_java_abs(&env, -1); + let mut env = attach_current_thread_permanently(); + let val = call_java_abs(&mut env, -1); assert_eq!(val, 1); assert_eq!(jvm().threads_attached(), 1); // Create nested AttachGuard. { - let env_nested = attach_current_thread(); - let val = call_java_abs(&env_nested, -2); + let mut env_nested = attach_current_thread(); + let val = call_java_abs(&mut env_nested, -2); assert_eq!(val, 2); assert_eq!(jvm().threads_attached(), 1); } // Call a Java method after nested guard has been dropped to check that // this thread has not been detached. - let val = call_java_abs(&env, -3); + let val = call_java_abs(&mut env, -3); assert_eq!(val, 3); assert_eq!(jvm().threads_attached(), 1); // Nested attach_permanently is a no-op. { - let env_nested = attach_current_thread_permanently(); - let val = call_java_abs(&env_nested, -4); + let mut env_nested = attach_current_thread_permanently(); + let val = call_java_abs(&mut env_nested, -4); assert_eq!(val, 4); assert_eq!(jvm().threads_attached(), 1); } @@ -39,8 +39,8 @@ pub fn nested_attaches_should_not_detach_permanent_thread() { // Nested attach_as_daemon is a no-op. { - let env_nested = attach_current_thread_as_daemon(); - let val = call_java_abs(&env_nested, -5); + let mut env_nested = attach_current_thread_as_daemon(); + let val = call_java_abs(&mut env_nested, -5); assert_eq!(val, 5); assert_eq!(jvm().threads_attached(), 1); } diff --git a/tests/util/example_proxy.rs b/tests/util/example_proxy.rs index 05f7d6a..cea7f2a 100644 --- a/tests/util/example_proxy.rs +++ b/tests/util/example_proxy.rs @@ -17,12 +17,13 @@ pub struct AtomicIntegerProxy { impl AtomicIntegerProxy { /// Creates a new instance of `AtomicIntegerProxy` pub fn new(exec: Executor, init_value: jint) -> Result<Self> { - let obj = exec.with_attached(|env: &JNIEnv| { - env.new_global_ref(env.new_object( + let obj = exec.with_attached(|env: &mut JNIEnv| { + let i = env.new_object( "java/util/concurrent/atomic/AtomicInteger", "(I)V", &[JValue::from(init_value)], - )?) + )?; + env.new_global_ref(i) })?; Ok(AtomicIntegerProxy { exec, obj }) } diff --git a/tests/util/mod.rs b/tests/util/mod.rs index fb07386..e5dd70a 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -30,7 +30,7 @@ pub fn jvm() -> &'static Arc<JavaVM> { } #[allow(dead_code)] -pub fn call_java_abs(env: &JNIEnv, value: i32) -> i32 { +pub fn call_java_abs(env: &mut JNIEnv, value: i32) -> i32 { env.call_static_method( "java/lang/Math", "abs", @@ -64,7 +64,7 @@ pub fn attach_current_thread_permanently() -> JNIEnv<'static> { } #[allow(dead_code)] -pub fn detach_current_thread() { +pub unsafe fn detach_current_thread() { jvm().detach_current_thread() } @@ -77,7 +77,7 @@ pub fn print_exception(env: &JNIEnv) { } #[allow(dead_code)] -pub fn unwrap<T>(env: &JNIEnv, res: Result<T>) -> T { +pub fn unwrap<T>(res: Result<T>, env: &JNIEnv) -> T { res.unwrap_or_else(|e| { print_exception(env); panic!("{:#?}", e); |