aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Maurer <mmaurer@google.com>2024-05-22 14:16:45 +0000
committerMatthew Maurer <mmaurer@google.com>2024-05-22 15:52:44 +0000
commit293fd53532cc59fd55f3947a6ec3a653349ea00f (patch)
treef184ec389bc5a9b415fa85b8430e8a87e3c6d9e7
parentd42c2ec45a5bb40774a7decae5b3e16339202742 (diff)
downloadjni-main.tar.gz
Upgrade jni to 0.21.1HEADmastermain
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
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--.vscode/settings.json5
-rw-r--r--Android.bp6
-rw-r--r--CHANGELOG.md71
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Cargo.toml31
-rw-r--r--Cargo.toml.orig14
-rw-r--r--METADATA25
-rw-r--r--benches/api_calls.rs198
-rw-r--r--build.rs109
-rw-r--r--docs/0.21-MIGRATION.md326
-rw-r--r--src/lib.rs16
-rw-r--r--src/wrapper/descriptors/class_desc.rs44
-rw-r--r--src/wrapper/descriptors/desc.rs127
-rw-r--r--src/wrapper/descriptors/exception_desc.rs48
-rw-r--r--src/wrapper/descriptors/field_desc.rs16
-rw-r--r--src/wrapper/descriptors/method_desc.rs26
-rw-r--r--src/wrapper/errors.rs42
-rw-r--r--src/wrapper/executor.rs35
-rw-r--r--src/wrapper/java_vm/init_args.rs310
-rw-r--r--src/wrapper/java_vm/init_args/char_encoding_generic.rs117
-rw-r--r--src/wrapper/java_vm/init_args/char_encoding_windows.rs443
-rw-r--r--src/wrapper/java_vm/vm.rs272
-rw-r--r--src/wrapper/jnienv.rs1883
-rw-r--r--src/wrapper/objects/auto_array.rs137
-rw-r--r--src/wrapper/objects/auto_elements.rs264
-rw-r--r--src/wrapper/objects/auto_elements_critical.rs168
-rw-r--r--src/wrapper/objects/auto_local.rs136
-rw-r--r--src/wrapper/objects/auto_primitive_array.rs90
-rw-r--r--src/wrapper/objects/global_ref.rs51
-rw-r--r--src/wrapper/objects/jbytebuffer.rs35
-rw-r--r--src/wrapper/objects/jclass.rs48
-rw-r--r--src/wrapper/objects/jfieldid.rs12
-rw-r--r--src/wrapper/objects/jlist.rs257
-rw-r--r--src/wrapper/objects/jmap.rs304
-rw-r--r--src/wrapper/objects/jmethodid.rs12
-rw-r--r--src/wrapper/objects/jobject.rs77
-rw-r--r--src/wrapper/objects/jobject_array.rs81
-rw-r--r--src/wrapper/objects/jprimitive_array.rs144
-rw-r--r--src/wrapper/objects/jstaticfieldid.rs12
-rw-r--r--src/wrapper/objects/jstaticmethodid.rs12
-rw-r--r--src/wrapper/objects/jstring.rs41
-rw-r--r--src/wrapper/objects/jthrowable.rs41
-rw-r--r--src/wrapper/objects/jvalue.rs274
-rw-r--r--src/wrapper/objects/mod.rs20
-rw-r--r--src/wrapper/objects/release_mode.rs5
-rw-r--r--src/wrapper/objects/weak_ref.rs176
-rw-r--r--src/wrapper/signature.rs16
-rw-r--r--src/wrapper/strings/ffi_str.rs6
-rw-r--r--src/wrapper/strings/java_str.rs184
-rw-r--r--tests/executor.rs140
-rw-r--r--tests/executor_nested_attach.rs4
-rw-r--r--tests/invocation_character_encoding.rs43
-rw-r--r--tests/java_integers.rs18
-rw-r--r--tests/jmap.rs26
-rw-r--r--tests/jni_api.rs633
-rw-r--r--tests/jni_global_refs.rs20
-rw-r--r--tests/jni_weak_refs.rs195
-rw-r--r--tests/threads_attach_guard.rs4
-rw-r--r--tests/threads_detach.rs4
-rw-r--r--tests/threads_detach_daemon.rs4
-rw-r--r--tests/threads_explicit_detach.rs10
-rw-r--r--tests/threads_explicit_detach_daemon.rs10
-rw-r--r--tests/threads_explicit_detach_permanent.rs10
-rw-r--r--tests/threads_nested_attach_daemon.rs18
-rw-r--r--tests/threads_nested_attach_guard.rs18
-rw-r--r--tests/threads_nested_attach_permanently.rs18
-rw-r--r--tests/util/example_proxy.rs7
-rw-r--r--tests/util/mod.rs6
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
diff --git a/Android.bp b/Android.bp
index 5b13d41..445994b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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
diff --git a/Cargo.toml b/Cargo.toml
index 2441b4a..5e7a2fd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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]
diff --git a/METADATA b/METADATA
index f891cf7..1c614f8 100644
--- a/METADATA
+++ b/METADATA
@@ -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.
diff --git a/src/lib.rs b/src/lib.rs
index dff0c20..8476b9d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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>&amp;[JObject]</code>
+/// or <code>&amp;[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]&lt;JObject&lt;'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);