diff options
author | Ivan Lozano <ivanlozano@google.com> | 2023-01-19 17:21:40 +0000 |
---|---|---|
committer | Ivan Lozano <ivanlozano@google.com> | 2023-01-24 16:35:23 +0000 |
commit | 05c80856628da27713cf29f6f4bbd73fbc2f1078 (patch) | |
tree | af62ef3b254156b04b01b4998d81b79144b2ce72 | |
parent | ff9d1c5e44fa55a18b0e5bec12529e8558d4f40c (diff) | |
download | jni-05c80856628da27713cf29f6f4bbd73fbc2f1078.tar.gz |
Upgrade jni to 0.20.0
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
Bug: 266055509
Test: TreeHugger
Change-Id: I2751a74cd2f28013d8f993b6ee96bebab2bc1b19
44 files changed, 1151 insertions, 468 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6f1d375 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ +## Overview + +<!-- Please describe your changes here and list any open questions you might have. --> + +### Definition of Done + +- [ ] There are no TODOs left in the code +- [ ] Change is covered by [automated tests](https://github.com/jni-rs/jni-rs/blob/master/CONTRIBUTING.md#tests) +- [ ] The [coding guidelines](https://github.com/jni-rs/jni-rs/blob/master/CONTRIBUTING.md#the-code-style) are followed +- [ ] Public API has documentation +- [ ] User-visible changes are mentioned in the Changelog +- [ ] The continuous integration build passes diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0514937 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: monthly + open-pull-requests-limit: 10 +- package-ecosystem: cargo + directory: "/example/mylib" + schedule: + interval: monthly + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ee0ff26 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,99 @@ +name: CI + +on: [pull_request, push] + +jobs: + # Check code formatting. + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt + profile: minimal + override: true + - name: Run rustfmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + # Static analyzer. + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + profile: minimal + override: true + - name: Run clippy + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all --tests --all-features -- -D warnings + + # Security audit. + audit: + name: Security audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + # Tests. + test: + name: ${{ matrix.build }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - build: Linux + os: ubuntu-latest + - build: macOS + os: macOS-latest + - build: Windows + os: windows-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + - name: Install java + uses: actions/setup-java@v1 + with: + java-version: '1.8.0' + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --examples --all + - name: Test default features + uses: actions-rs/cargo@v1 + with: + command: test + - name: Shellcheck + if: runner.os == 'Linux' + run: .github/workflows/shellcheck.sh + - name: Test invocation + if: runner.os != 'Windows' + run: .github/workflows/run_invocation_tests.sh + - name: Test invocation + if: runner.os == 'Windows' + run: .github/workflows/run_windows_invocation_tests.ps1 diff --git a/.github/workflows/run_invocation_tests.sh b/.github/workflows/run_invocation_tests.sh new file mode 100644 index 0000000..6d40d3c --- /dev/null +++ b/.github/workflows/run_invocation_tests.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Setup LD_LIBRARY_PATH for ITs +# shellcheck source=/dev/null +source test_profile + +# Run all tests with invocation feature (enables JavaVM ITs) +cargo test --features=invocation diff --git a/.github/workflows/run_windows_invocation_tests.ps1 b/.github/workflows/run_windows_invocation_tests.ps1 new file mode 100644 index 0000000..1162703 --- /dev/null +++ b/.github/workflows/run_windows_invocation_tests.ps1 @@ -0,0 +1,3 @@ +$env:Path += ";$(Split-Path -Path (Get-Childitem –Path $Env:JAVA_HOME -Filter jvm.dll -Recurse) -Parent)" + +cargo test --features=invocation diff --git a/.github/workflows/shellcheck.sh b/.github/workflows/shellcheck.sh new file mode 100644 index 0000000..38744b1 --- /dev/null +++ b/.github/workflows/shellcheck.sh @@ -0,0 +1,9 @@ +# Fail immediately in case of errors and/or unset variables +set -eu -o pipefail + +# Echo commands so that the progress can be seen in CI server logs. +set -x + +# Check the shell scripts +shellcheck .github/workflows/run_invocation_tests.sh +shellcheck test_profile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5dc9994 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +## Rust +target/ +Cargo.lock +# These are backup files generated by rustfmt +**/*.rs.bk + +## Java +*.class + +## IntelliJ +.idea/ +*.iml diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7c34d68 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.cargo.features": [ + "invocation" + ] +}
\ No newline at end of file @@ -39,11 +39,10 @@ license { rust_test { name: "jni_test_src_lib", - // has rustc warnings host_supported: true, crate_name: "jni", cargo_env_compat: true, - cargo_pkg_version: "0.19.0", + cargo_pkg_version: "0.20.0", srcs: ["src/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -64,11 +63,10 @@ rust_test { rust_library { name: "libjni", - // has rustc warnings host_supported: true, crate_name: "jni", cargo_env_compat: true, - cargo_pkg_version: "0.19.0", + cargo_pkg_version: "0.20.0", srcs: ["src/lib.rs"], edition: "2018", features: ["default"], diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f4c10..aeb692b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,33 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] + +## [0.20.0] — 2022-10-17 + +### Added +- `Default` trait implemented for `JObject`, `JString`, `JClass`, and `JByteBuffer` ([#199](https://github.com/jni-rs/jni-rs/issues/199)) +- `Debug` trait implemented for `JavaVM`, `GlobalRef`, `GlobalRefGuard`, `JStaticMethodID` and `ReleaseMode` ([#345](https://github.com/jni-rs/jni-rs/pull/345)) +- `ReturnType` for specifying object return types without a String allocation. ([#329](https://github.com/jni-rs/jni-rs/issues/329)) + +### Changed +- The `release_string_utf_chars` function has been marked as unsafe. ([#334](https://github.com/jni-rs/jni-rs/pull/334)) +- Mark `JNIEnv::new_direct_byte_buffer` as `unsafe` ([#320](https://github.com/jni-rs/jni-rs/pull/320)) +- `JNIEnv::new_direct_byte_buffer` now takes a raw pointer and size instead of a slice ([#351](https://github.com/jni-rs/jni-rs/pull/351) and [#364](https://github.com/jni-rs/jni-rs/pull/364)) +- `JNIEnv::direct_buffer_address` returns a raw pointer instead of a slice ([#364](https://github.com/jni-rs/jni-rs/pull/364)) +- The lifetime of `AutoArray` is no longer tied to the lifetime of a particular `JNIEnv` reference. ([#302](https://github.com/jni-rs/jni-rs/issues/302)) +- Relaxed lifetime restrictions on `JNIEnv::new_local_ref`. Now it can be used to create a local + reference from a global reference. ([#301](https://github.com/jni-rs/jni-rs/issues/301) / [#319](https://github.com/jni-rs/jni-rs/pull/319)) +- `JMethodID` and `JStaticMethodID` implement `Send` + `Sync` and no longer has a lifetime parameter, making method + IDs cacheable (with a documented 'Safety' note about ensuring they remain valid). ([#346](https://github.com/jni-rs/jni-rs/pull/346)) +- `JFieldID` and `JStaticFieldID` implement `Send` + `Sync` and no longer has a lifetime parameter, making field + IDs cacheable (with a documented 'Safety' note about ensuring they remain valid). ([#346](https://github.com/jni-rs/jni-rs/pull/365)) +- The `call_*_method_unchecked` functions now take `jni:sys::jvalue` arguments to avoid allocating + a `Vec` on each call to map + collect `JValue`s as `sys:jvalue`s ([#329](https://github.com/jni-rs/jni-rs/issues/329)) +- The `From` trait implementations converting `jni_sys` types like `jobject` to `JObject` have been replaced + with `unsafe` `::from_raw` functions and corresponding `::into_raw` methods. Existing `::into_inner` APIs were + renamed `::into_raw` for symmetry. ([#197](https://github.com/jni-rs/jni-rs/issues/197)) +- The APIs `JNIEnv::set_rust_field`, `JNIEnv::get_rust_field` and `JNIEnv::take_rust_field` have been marked as `unsafe` ([#219](https://github.com/jni-rs/jni-rs/issues/219)) + ## [0.19.0] — 2021-01-24 ### Added @@ -218,7 +245,8 @@ 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.19.0...HEAD +[Unreleased]: https://github.com/jni-rs/jni-rs/compare/v0.20.0...HEAD +[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 [0.17.0]: https://github.com/jni-rs/jni-rs/compare/v0.16.0...v0.17.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 73b0b8f..eea3908 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.19.0/jni/struct.JNIEnv.html#checked-and-unchecked-methods +[checked-unchecked]: https://docs.rs/jni/0.20.0/jni/struct.JNIEnv.html#checked-and-unchecked-methods ## The Code Style @@ -3,27 +3,32 @@ # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies +# to registry (e.g., crates.io) dependencies. # -# If you believe there's an error in this file please file an -# issue against the rust-lang/cargo repository. If you're -# editing this file be aware that the upstream Cargo.toml -# will likely look very different (and much more reasonable) +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "jni" -version = "0.19.0" +version = "0.20.0" authors = ["Josh Chase <josh@prevoty.com>"] description = "Rust bindings to the JNI" documentation = "https://docs.rs/jni" readme = "README.md" -keywords = ["ffi", "jni", "java"] +keywords = [ + "ffi", + "jni", + "java", +] categories = ["api-bindings"] license = "MIT/Apache-2.0" repository = "https://github.com/jni-rs/jni-rs" + [package.metadata.docs.rs] features = ["invocation"] + [dependencies.cesu8] version = "1.1.0" @@ -38,8 +43,10 @@ version = "0.4.4" [dependencies.thiserror] version = "1.0.20" + [dev-dependencies.lazy_static] version = "1" + [build-dependencies.walkdir] version = "2" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index eea8601..913294d 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -13,7 +13,7 @@ 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.19.0" +version = "0.20.0" edition = "2018" [dependencies] @@ -1,3 +1,7 @@ +# 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 + name: "jni" description: "Rust bindings to the JNI" third_party { @@ -7,14 +11,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/jni/jni-0.19.0.crate" + value: "https://static.crates.io/crates/jni/jni-0.20.0.crate" } - version: "0.19.0" - # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. + version: "0.20.0" license_type: NOTICE last_upgrade_date { - year: 2021 - month: 7 - day: 30 + year: 2023 + month: 1 + day: 19 } } @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/jni-rs/jni-rs.svg?branch=master)](https://travis-ci.org/jni-rs/jni-rs) +[![Build Status](https://github.com/jni-rs/jni-rs/workflows/CI/badge.svg)](https://github.com/jni-rs/jni-rs/actions) [![Docs](https://docs.rs/jni/badge.svg)](https://docs.rs/jni) [![Crates.io](https://img.shields.io/crates/v/jni.svg)](https://crates.io/crates/jni) diff --git a/benches/api_calls.rs b/benches/api_calls.rs index 411b536..0ee0351 100644 --- a/benches/api_calls.rs +++ b/benches/api_calls.rs @@ -3,24 +3,28 @@ extern crate test; +use jni_sys::jvalue; use lazy_static::lazy_static; use jni::{ descriptors::Desc, objects::{JClass, JMethodID, JObject, JStaticMethodID, JValue}, - signature::{JavaType, Primitive}, + signature::{Primitive, ReturnType}, sys::jint, InitArgsBuilder, JNIEnv, JNIVersion, JavaVM, }; static CLASS_MATH: &str = "java/lang/Math"; static CLASS_OBJECT: &str = "java/lang/Object"; +static CLASS_LOCAL_DATE_TIME: &str = "java/time/LocalDateTime"; static METHOD_MATH_ABS: &str = "abs"; static METHOD_OBJECT_HASH_CODE: &str = "hashCode"; static METHOD_CTOR: &str = "<init>"; +static METHOD_LOCAL_DATE_TIME_OF: &str = "of"; static SIG_OBJECT_CTOR: &str = "()V"; 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;"; #[inline(never)] fn native_abs(x: i32) -> i32 { @@ -35,37 +39,81 @@ fn jni_abs_safe(env: &JNIEnv, x: jint) -> jint { v.i().unwrap() } -fn jni_call_static_unchecked<'c, C>( +fn jni_hash_safe(env: &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>, + year: jint, + month: jint, + day_of_month: jint, + hour: jint, + minute: jint, + second: jint, + nanosecond: jint, +) -> JObject<'e> { + let v = env + .call_static_method( + CLASS_LOCAL_DATE_TIME, + METHOD_LOCAL_DATE_TIME_OF, + SIG_LOCAL_DATE_TIME_OF, + &[ + year.into(), + month.into(), + day_of_month.into(), + hour.into(), + minute.into(), + second.into(), + nanosecond.into(), + ], + ) + .unwrap(); + v.l().unwrap() +} + +fn jni_int_call_static_unchecked<'c, C>( env: &JNIEnv<'c>, class: C, - method_id: JStaticMethodID<'c>, + method_id: JStaticMethodID, x: jint, ) -> jint where C: Desc<'c, JClass<'c>>, { let x = JValue::from(x); - let ret = JavaType::Primitive(Primitive::Int); + let ret = ReturnType::Primitive(Primitive::Int); let v = env - .call_static_method_unchecked(class, method_id, ret, &[x]) + .call_static_method_unchecked(class, method_id, ret, &[x.into()]) .unwrap(); v.i().unwrap() } -fn jni_hash_safe(env: &JNIEnv, obj: JObject) -> jint { - let v = env - .call_method(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE, &[]) - .unwrap(); +fn jni_int_call_unchecked<'m, M>(env: &JNIEnv<'m>, obj: JObject<'m>, method_id: M) -> jint +where + M: Desc<'m, JMethodID>, +{ + let ret = ReturnType::Primitive(Primitive::Int); + let v = env.call_method_unchecked(obj, method_id, ret, &[]).unwrap(); v.i().unwrap() } -fn jni_call_unchecked<'m, M>(env: &JNIEnv<'m>, obj: JObject<'m>, method_id: M) -> jint +fn jni_object_call_static_unchecked<'c, C>( + env: &JNIEnv<'c>, + class: C, + method_id: JStaticMethodID, + args: &[jvalue], +) -> JObject<'c> where - M: Desc<'m, JMethodID<'m>>, + C: Desc<'c, JClass<'c>>, { - let ret = JavaType::Primitive(Primitive::Int); - let v = env.call_method_unchecked(obj, method_id, ret, &[]).unwrap(); - v.i().unwrap() + let v = env + .call_static_method_unchecked(class, method_id, ReturnType::Object, args) + .unwrap(); + v.l().unwrap() } #[cfg(test)] @@ -93,36 +141,72 @@ mod tests { } #[bench] - fn jni_call_static_method_safe(b: &mut Bencher) { + fn jni_call_static_abs_method_safe(b: &mut Bencher) { let env = VM.attach_current_thread().unwrap(); b.iter(|| jni_abs_safe(&env, -3)); } #[bench] - fn jni_call_static_method_unchecked_str(b: &mut Bencher) { + fn jni_call_static_abs_method_unchecked_str(b: &mut Bencher) { let 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_call_static_unchecked(&env, class, method_id, -3)); + b.iter(|| jni_int_call_static_unchecked(&env, class, method_id, -3)); } #[bench] - fn jni_call_static_method_unchecked_jclass(b: &mut Bencher) { + 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 method_id = env .get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS) .unwrap(); - b.iter(|| jni_call_static_unchecked(&env, class, method_id, -3)); + b.iter(|| jni_int_call_static_unchecked(&env, class, method_id, -3)); + } + + #[bench] + fn jni_call_static_date_time_method_safe(b: &mut Bencher) { + let env = VM.attach_current_thread().unwrap(); + b.iter(|| { + let obj = jni_local_date_time_of_safe(&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 method_id = env + .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, + 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(), + ], + ); + env.delete_local_ref(obj).unwrap(); + }); } #[bench] - fn jni_call_object_method_safe(b: &mut Bencher) { + fn jni_call_object_hash_method_safe(b: &mut Bencher) { let env = VM.attach_current_thread().unwrap(); let s = env.new_string("").unwrap(); let obj = black_box(JObject::from(s)); @@ -131,7 +215,7 @@ mod tests { } #[bench] - fn jni_call_object_method_unchecked(b: &mut Bencher) { + fn jni_call_object_hash_method_unchecked(b: &mut Bencher) { let env = VM.attach_current_thread().unwrap(); let s = env.new_string("").unwrap(); let obj = black_box(JObject::from(s)); @@ -139,7 +223,7 @@ mod tests { .get_method_id(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE) .unwrap(); - b.iter(|| jni_call_unchecked(&env, obj, method_id)); + b.iter(|| jni_int_call_unchecked(&env, obj, method_id)); } #[bench] @@ -21,6 +21,7 @@ use std::{ #[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", @@ -57,7 +58,7 @@ fn main() { // 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 cfg!(target_os = "macos") { + if env::var("CARGO_CFG_TARGET_OS").unwrap() == "macos" { println!("cargo:rustc-link-lib=dylib=jli"); } else { println!("cargo:rustc-link-lib=dylib=jvm"); @@ -1,4 +1,7 @@ #![warn(missing_docs)] +#![allow(clippy::upper_case_acronyms)] +// TODO: https://github.com/jni-rs/jni-rs/issues/348 +#![allow(clippy::not_unsafe_ptr_arg_deref)] //! # Safe JNI Bindings in Rust //! @@ -46,7 +49,7 @@ //! //! To do that, first we need the name and type signature that our Rust function //! needs to adhere to. Luckily, the Java compiler can generate that for you! -//! Run `javac -h . HelloWorld` and you'll get a `HelloWorld.h` output to your +//! Run `javac -h . HelloWorld.java` and you'll get a `HelloWorld.h` output to your //! directory. It should look something like this: //! //! ```c @@ -82,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.19.0"` +//! * Under `[dependencies]`, add `jni = "0.20.0"` //! * Add a new `[lib]` section and under it, `crate_type = ["cdylib"]`. //! //! Now, if you run `cargo build` from inside the crate directory, you should diff --git a/src/wrapper/descriptors/exception_desc.rs b/src/wrapper/descriptors/exception_desc.rs index 71ccf68..4911650 100644 --- a/src/wrapper/descriptors/exception_desc.rs +++ b/src/wrapper/descriptors/exception_desc.rs @@ -40,7 +40,7 @@ impl<'a> Desc<'a, JThrowable<'a>> for String { } } -impl<'a, 'b> Desc<'a, JThrowable<'a>> for JNIString { +impl<'a> Desc<'a, JThrowable<'a>> for JNIString { fn lookup(self, env: &JNIEnv<'a>) -> Result<JThrowable<'a>> { (DEFAULT_EXCEPTION_CLASS, self).lookup(env) } diff --git a/src/wrapper/descriptors/field_desc.rs b/src/wrapper/descriptors/field_desc.rs index cdaf908..405c03b 100644 --- a/src/wrapper/descriptors/field_desc.rs +++ b/src/wrapper/descriptors/field_desc.rs @@ -6,24 +6,24 @@ use crate::{ JNIEnv, }; -impl<'a, 'c, T, U, V> Desc<'a, JFieldID<'a>> for (T, U, V) +impl<'a, 'c, T, U, V> Desc<'a, JFieldID> for (T, U, V) where T: Desc<'a, JClass<'c>>, U: Into<JNIString>, V: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JFieldID<'a>> { + fn lookup(self, env: &JNIEnv<'a>) -> Result<JFieldID> { env.get_field_id(self.0, self.1, self.2) } } -impl<'a, 'c, T, U, V> Desc<'a, JStaticFieldID<'a>> for (T, U, V) +impl<'a, 'c, T, U, V> Desc<'a, JStaticFieldID> for (T, U, V) where T: Desc<'a, JClass<'c>>, U: Into<JNIString>, V: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JStaticFieldID<'a>> { + fn lookup(self, env: &JNIEnv<'a>) -> Result<JStaticFieldID> { 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 bbd2651..1b94e04 100644 --- a/src/wrapper/descriptors/method_desc.rs +++ b/src/wrapper/descriptors/method_desc.rs @@ -6,34 +6,34 @@ use crate::{ JNIEnv, }; -impl<'a, 'c, T, U, V> Desc<'a, JMethodID<'a>> for (T, U, V) +impl<'a, 'c, T, U, V> Desc<'a, JMethodID> for (T, U, V) where T: Desc<'a, JClass<'c>>, U: Into<JNIString>, V: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JMethodID<'a>> { + fn lookup(self, env: &JNIEnv<'a>) -> Result<JMethodID> { env.get_method_id(self.0, self.1, self.2) } } -impl<'a, 'c, T, Signature> Desc<'a, JMethodID<'a>> for (T, Signature) +impl<'a, 'c, T, Signature> Desc<'a, JMethodID> for (T, Signature) where T: Desc<'a, JClass<'c>>, Signature: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JMethodID<'a>> { + fn lookup(self, env: &JNIEnv<'a>) -> Result<JMethodID> { (self.0, "<init>", self.1).lookup(env) } } -impl<'a, 'c, T, U, V> Desc<'a, JStaticMethodID<'a>> for (T, U, V) +impl<'a, 'c, T, U, V> Desc<'a, JStaticMethodID> for (T, U, V) where T: Desc<'a, JClass<'c>>, U: Into<JNIString>, V: Into<JNIString>, { - fn lookup(self, env: &JNIEnv<'a>) -> Result<JStaticMethodID<'a>> { + fn lookup(self, env: &JNIEnv<'a>) -> Result<JStaticMethodID> { env.get_static_method_id(self.0, self.1, self.2) } } diff --git a/src/wrapper/java_vm/vm.rs b/src/wrapper/java_vm/vm.rs index d3e2179..e127634 100644 --- a/src/wrapper/java_vm/vm.rs +++ b/src/wrapper/java_vm/vm.rs @@ -12,6 +12,7 @@ use crate::{errors::*, sys, JNIEnv}; #[cfg(feature = "invocation")] use crate::InitArgs; +use std::thread::Thread; /// The Java VM, providing [Invocation API][invocation-api] support. /// @@ -58,7 +59,7 @@ use crate::InitArgs; /// in the Cargo.toml: /// /// ```toml -/// jni = { version = "0.19.0", features = ["invocation"] } +/// jni = { version = "0.20.0", features = ["invocation"] } /// ``` /// /// The application will require linking to the dynamic `jvm` library, which is distributed @@ -131,6 +132,7 @@ use crate::InitArgs; /// [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 #[repr(transparent)] +#[derive(Debug)] pub struct JavaVM(*mut sys::JavaVM); unsafe impl Send for JavaVM {} @@ -348,11 +350,20 @@ enum ThreadType { #[derive(Debug)] struct InternalAttachGuard { java_vm: *mut sys::JavaVM, + /// A call std::thread::current() function can panic in case the local data has been destroyed + /// before the thead local variables. The possibility of this happening depends on the platform + /// implementation of the crate::sys_common::thread_local_dtor::register_dtor_fallback. + /// The InternalAttachGuard is a thread-local vairable, so capture the thread meta-data + /// during creation + thread: Thread, } impl InternalAttachGuard { fn new(java_vm: *mut sys::JavaVM) -> Self { - Self { java_vm } + Self { + java_vm, + thread: current(), + } } /// Stores guard in thread local storage. @@ -384,8 +395,8 @@ impl InternalAttachGuard { debug!( "Attached thread {} ({:?}). {} threads attached", - current().name().unwrap_or_default(), - current().id(), + self.thread.name().unwrap_or_default(), + self.thread.id(), ATTACHED_THREADS.load(Ordering::SeqCst) ); @@ -406,8 +417,8 @@ impl InternalAttachGuard { debug!( "Attached daemon thread {} ({:?}). {} threads attached", - current().name().unwrap_or_default(), - current().id(), + self.thread.name().unwrap_or_default(), + self.thread.id(), ATTACHED_THREADS.load(Ordering::SeqCst) ); @@ -421,8 +432,8 @@ impl InternalAttachGuard { ATTACHED_THREADS.fetch_sub(1, Ordering::SeqCst); debug!( "Detached thread {} ({:?}). {} threads remain attached", - current().name().unwrap_or_default(), - current().id(), + self.thread.name().unwrap_or_default(), + self.thread.id(), ATTACHED_THREADS.load(Ordering::SeqCst) ); @@ -436,8 +447,8 @@ impl Drop for InternalAttachGuard { error!( "Error detaching current thread: {:#?}\nThread {} id={:?}", e, - current().name().unwrap_or_default(), - current().id(), + self.thread.name().unwrap_or_default(), + self.thread.id(), ); } } diff --git a/src/wrapper/jnienv.rs b/src/wrapper/jnienv.rs index 82cd644..f0da5d2 100644 --- a/src/wrapper/jnienv.rs +++ b/src/wrapper/jnienv.rs @@ -1,13 +1,14 @@ use std::{ marker::PhantomData, os::raw::{c_char, c_void}, - ptr, slice, str, + ptr, str, str::FromStr, sync::{Mutex, MutexGuard}, }; use log::warn; +use crate::signature::ReturnType; use crate::{ descriptors::Desc, errors::*, @@ -129,11 +130,11 @@ impl<'a> JNIEnv<'a> { self.internal, DefineClass, name, - loader.into_inner(), + loader.into_raw(), buf.as_ptr() as *const jbyte, buf.len() as jsize ); - Ok(class) + Ok(unsafe { JClass::from_raw(class) }) } /// Look up a class by name. @@ -148,7 +149,7 @@ impl<'a> JNIEnv<'a> { { let name = name.into(); let class = jni_non_null_call!(self.internal, FindClass, name.as_ptr()); - Ok(class) + Ok(unsafe { JClass::from_raw(class) }) } /// Returns the superclass for a particular class OR `JObject::null()` for `java.lang.Object` or @@ -158,7 +159,13 @@ impl<'a> JNIEnv<'a> { T: Desc<'a, JClass<'c>>, { let class = class.lookup(self)?; - Ok(jni_non_void_call!(self.internal, GetSuperclass, class.into_inner()).into()) + Ok(unsafe { + JClass::from_raw(jni_non_void_call!( + self.internal, + GetSuperclass, + class.into_raw() + )) + }) } /// Tests whether class1 is assignable from class2. @@ -172,8 +179,8 @@ impl<'a> JNIEnv<'a> { Ok(jni_unchecked!( self.internal, IsAssignableFrom, - class1.into_inner(), - class2.into_inner() + class1.into_raw(), + class2.into_raw() ) == sys::JNI_TRUE) } @@ -193,8 +200,8 @@ impl<'a> JNIEnv<'a> { Ok(jni_unchecked!( self.internal, IsInstanceOf, - object.into().into_inner(), - class.into_inner() + object.into().into_raw(), + class.into_raw() ) == sys::JNI_TRUE) } @@ -208,8 +215,8 @@ impl<'a> JNIEnv<'a> { Ok(jni_unchecked!( self.internal, IsSameObject, - ref1.into().into_inner(), - ref2.into().into_inner() + ref1.into().into_raw(), + ref2.into().into_raw() ) == sys::JNI_TRUE) } @@ -231,7 +238,7 @@ impl<'a> JNIEnv<'a> { E: Desc<'a, JThrowable<'e>>, { let throwable = obj.lookup(self)?; - let res: i32 = jni_unchecked!(self.internal, Throw, throwable.into_inner()); + let res: i32 = jni_unchecked!(self.internal, Throw, throwable.into_raw()); if res == 0 { Ok(()) } else { @@ -253,7 +260,7 @@ impl<'a> JNIEnv<'a> { { let class = class.lookup(self)?; let msg = msg.into(); - let res: i32 = jni_unchecked!(self.internal, ThrowNew, class.into_inner(), msg.as_ptr()); + let res: i32 = jni_unchecked!(self.internal, ThrowNew, class.into_raw(), msg.as_ptr()); if res == 0 { Ok(()) } else { @@ -266,7 +273,7 @@ impl<'a> JNIEnv<'a> { /// not caught in a java function until `exception_clear` is called. pub fn exception_occurred(&self) -> Result<JThrowable<'a>> { let throwable = jni_unchecked!(self.internal, ExceptionOccurred); - Ok(JThrowable::from(throwable)) + Ok(unsafe { JThrowable::from_raw(throwable) }) } /// Print exception information to the console. @@ -292,7 +299,7 @@ impl<'a> JNIEnv<'a> { unreachable!() }); - panic!(res.unwrap_err()); + panic!("{:?}", res.unwrap_err()); } /// Check to see if an exception is being thrown. This only differs from @@ -303,34 +310,64 @@ impl<'a> JNIEnv<'a> { Ok(check) } - /// Create a new instance of a direct java.nio.ByteBuffer. - pub fn new_direct_byte_buffer(&self, data: &mut [u8]) -> Result<JByteBuffer<'a>> { - let obj: JObject = jni_non_null_call!( + /// Create a new instance of a direct java.nio.ByteBuffer + /// + /// # Example + /// ```rust,ignore + /// 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) }; + /// ``` + /// + /// # Safety + /// + /// Expects a valid (non-null) pointer and length + /// + /// Caller must ensure the lifetime of `data` extends to all uses of the returned + /// `ByteBuffer`. The JVM may maintain references to the `ByteBuffer` beyond the lifetime + /// of this `JNIEnv`. + pub unsafe fn new_direct_byte_buffer( + &self, + data: *mut u8, + len: usize, + ) -> Result<JByteBuffer<'a>> { + non_null!(data, "new_direct_byte_buffer data argument"); + let obj = jni_non_null_call!( self.internal, NewDirectByteBuffer, - data.as_mut_ptr() as *mut c_void, - data.len() as jlong + data as *mut c_void, + len as jlong ); - Ok(JByteBuffer::from(obj)) + Ok(JByteBuffer::from_raw(obj)) } /// 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: *mut c_void = - jni_unchecked!(self.internal, GetDirectBufferAddress, buf.into_inner()); + let ptr = jni_unchecked!(self.internal, GetDirectBufferAddress, buf.into_raw()); non_null!(ptr, "get_direct_buffer_address return value"); - let capacity = self.get_direct_buffer_capacity(buf)?; - unsafe { Ok(slice::from_raw_parts_mut(ptr as *mut u8, capacity as usize)) } + Ok(ptr as _) } - /// Returns the capacity of the direct java.nio.ByteBuffer. - pub fn get_direct_buffer_capacity(&self, buf: JByteBuffer) -> Result<jlong> { - let capacity = jni_unchecked!(self.internal, GetDirectBufferCapacity, buf.into_inner()); + /// Returns the capacity (length) of the direct java.nio.ByteBuffer. + /// + /// # Terminology + /// + /// "capacity" here means the length that was passed to [`Self::new_direct_byte_buffer()`] + /// which does not reflect the (potentially) larger size of the underlying allocation (unlike the `Vec` + /// API). + /// + /// The terminology is simply kept from the original JNI API (`GetDirectBufferCapacity`). + 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()); match capacity { -1 => Err(Error::JniCall(JniError::Unknown)), - _ => Ok(capacity), + _ => Ok(capacity as usize), } } @@ -341,20 +378,109 @@ impl<'a> JNIEnv<'a> { where O: Into<JObject<'a>>, { - let new_ref: JObject = - jni_unchecked!(self.internal, NewGlobalRef, obj.into().into_inner()).into(); - let global = unsafe { GlobalRef::from_raw(self.get_java_vm()?, new_ref.into_inner()) }; + let new_ref = jni_unchecked!(self.internal, NewGlobalRef, obj.into().into_raw()); + let global = unsafe { GlobalRef::from_raw(self.get_java_vm()?, new_ref) }; Ok(global) } - /// Create a new local ref to an object. + /// Create a new local reference to an object. + /// + /// Specifically, this calls the JNI function [`NewLocalRef`], which creates a reference in the + /// current local reference frame, regardless of whether the original reference belongs to the + /// same local reference frame, a different one, or is a [global reference][GlobalRef]. In Rust + /// terms, this method accepts a JNI reference with any valid lifetime and produces a clone of + /// that reference with the lifetime of this `JNIEnv`. The returned reference can outlive the + /// original. + /// + /// This method is useful when you have a strong global reference and you can't prevent it from + /// being dropped before you're finished with it. In that case, you can use this method to + /// create a new local reference that's guaranteed to remain valid for the duration of the + /// current local reference frame, regardless of what later happens to the original global + /// reference. + /// + /// # Lifetimes + /// + /// `'a` is the lifetime of this `JNIEnv`. This method creates a new local reference with + /// lifetime `'a`. + /// + /// `'b` is the lifetime of the original reference. It can be any valid lifetime, even one that + /// `'a` 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.) + /// + /// # Example + /// + /// In the following example, the `ExampleError::extract_throwable` method uses + /// `JNIEnv::new_local_ref` to create a new local reference that outlives the original global + /// reference: + /// + /// ```no_run + /// # use jni::{JNIEnv, objects::*}; + /// # use std::fmt::Display; + /// # + /// # type SomeOtherErrorType = Box<dyn Display>; + /// # + /// /// An error that may be caused by either a Java exception or something going wrong in Rust + /// /// code. + /// enum ExampleError { + /// /// This variant represents a Java exception. + /// /// + /// /// The enclosed `GlobalRef` points to a Java object of class `java.lang.Throwable` + /// /// (or one of its many subclasses). + /// Exception(GlobalRef), + /// + /// /// This variant represents an error in Rust code, not a Java exception. + /// Other(SomeOtherErrorType), + /// } + /// + /// impl ExampleError { + /// /// Consumes this `ExampleError` and produces a `JThrowable`, suitable for throwing + /// /// back to Java code. + /// /// + /// /// 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> { + /// let throwable: JObject = match self { + /// ExampleError::Exception(exception) => { + /// // The error was caused by a Java exception. /// - /// Note that the object passed to this is *already* a local ref. This - /// creates yet another reference to it, which is most likely not what you - /// want. - pub fn new_local_ref<T>(&self, obj: JObject<'a>) -> Result<JObject<'a>> { - let local: JObject = jni_unchecked!(self.internal, NewLocalRef, obj.into_inner()).into(); - Ok(local) + /// // Here, `exception` is a `GlobalRef` pointing to a Java `Throwable`. It + /// // will be dropped at the end of this `match` arm. We'll use + /// // `new_local_ref` to create a local reference that will outlive the + /// // `GlobalRef`. + /// + /// env.new_local_ref(exception.as_obj())? + /// } + /// + /// ExampleError::Other(error) => { + /// // The error was caused by something that happened in Rust code. Create a + /// // new `java.lang.Error` to represent it. + /// + /// env.new_object( + /// "java/lang/Error", + /// "(Ljava/lang/String;)V", + /// &[ + /// env.new_string(error.to_string())?.into(), + /// ], + /// )? + /// } + /// }; + /// + /// Ok(JThrowable::from(throwable)) + /// } + /// } + /// ``` + /// + /// [`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>> + where + O: Into<JObject<'b>>, + { + let local = jni_unchecked!(self.internal, NewLocalRef, obj.into().into_raw()); + Ok(unsafe { JObject::from_raw(local) }) } /// Creates a new auto-deleted local reference. @@ -389,7 +515,7 @@ 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_inner()); + jni_unchecked!(self.internal, DeleteLocalRef, obj.into_raw()); Ok(()) } @@ -416,7 +542,13 @@ impl<'a> JNIEnv<'a> { /// The resulting `JObject` will be `NULL` iff `result` is `NULL`. pub fn pop_local_frame(&self, result: JObject<'a>) -> Result<JObject<'a>> { // This method is safe to call in case of pending exceptions (see chapter 2 of the spec) - Ok(jni_unchecked!(self.internal, PopLocalFrame, result.into_inner()).into()) + Ok(unsafe { + JObject::from_raw(jni_unchecked!( + self.internal, + PopLocalFrame, + result.into_raw() + )) + }) } /// Executes the given function in a new local reference frame, in which at least a given number @@ -449,11 +581,8 @@ impl<'a> JNIEnv<'a> { T: Desc<'a, JClass<'c>>, { let class = class.lookup(self)?; - Ok(jni_non_null_call!( - self.internal, - AllocObject, - class.into_inner() - )) + let obj = jni_non_null_call!(self.internal, AllocObject, class.into_raw()); + Ok(unsafe { JObject::from_raw(obj) }) } /// Common functionality for finding methods. @@ -498,20 +627,21 @@ impl<'a> JNIEnv<'a> { /// let method_id: JMethodID = /// env.get_method_id("java/lang/String", "substring", "(II)Ljava/lang/String;"); /// ``` - pub fn get_method_id<'c, T, U, V>(&self, class: T, name: U, sig: V) -> Result<JMethodID<'a>> + pub fn get_method_id<'c, T, U, V>(&self, class: T, name: U, sig: V) -> Result<JMethodID> where T: Desc<'a, JClass<'c>>, U: Into<JNIString>, V: Into<JNIString>, { self.get_method_id_base(class, name, sig, |class, name, sig| { - Ok(jni_non_null_call!( + let method_id = jni_non_null_call!( self.internal, GetMethodID, - class.into_inner(), + class.into_raw(), name.as_ptr(), sig.as_ptr() - )) + ); + Ok(unsafe { JMethodID::from_raw(method_id) }) }) } @@ -528,20 +658,21 @@ impl<'a> JNIEnv<'a> { class: T, name: U, sig: V, - ) -> Result<JStaticMethodID<'a>> + ) -> Result<JStaticMethodID> where T: Desc<'a, JClass<'c>>, U: Into<JNIString>, V: Into<JNIString>, { self.get_method_id_base(class, name, sig, |class, name, sig| { - Ok(jni_non_null_call!( + let method_id = jni_non_null_call!( self.internal, GetStaticMethodID, - class.into_inner(), + class.into_raw(), name.as_ptr(), sig.as_ptr() - )) + ); + Ok(unsafe { JStaticMethodID::from_raw(method_id) }) }) } @@ -551,7 +682,7 @@ impl<'a> JNIEnv<'a> { /// ```rust,ignore /// let field_id = env.get_field_id("com/my/Class", "intField", "I"); /// ``` - pub fn get_field_id<'c, T, U, V>(&self, class: T, name: U, sig: V) -> Result<JFieldID<'a>> + pub fn get_field_id<'c, T, U, V>(&self, class: T, name: U, sig: V) -> Result<JFieldID> where T: Desc<'a, JClass<'c>>, U: Into<JNIString>, @@ -562,13 +693,14 @@ impl<'a> JNIEnv<'a> { let ffi_sig = sig.into(); let res: Result<JFieldID> = catch!({ - Ok(jni_non_null_call!( + let field_id = jni_non_null_call!( self.internal, GetFieldID, - class.into_inner(), + class.into_raw(), ffi_name.as_ptr(), ffi_sig.as_ptr() - )) + ); + Ok(unsafe { JFieldID::from_raw(field_id) }) }); match res { @@ -595,7 +727,7 @@ impl<'a> JNIEnv<'a> { class: T, name: U, sig: V, - ) -> Result<JStaticFieldID<'a>> + ) -> Result<JStaticFieldID> where T: Desc<'a, JClass<'c>>, U: Into<JNIString>, @@ -606,13 +738,14 @@ impl<'a> JNIEnv<'a> { let ffi_sig = sig.into(); let res: Result<JStaticFieldID> = catch!({ - Ok(jni_non_null_call!( + let field_id = jni_non_null_call!( self.internal, GetStaticFieldID, - class.into_inner(), + class.into_raw(), ffi_name.as_ptr(), ffi_sig.as_ptr() - )) + ); + Ok(unsafe { JStaticFieldID::from_raw(field_id) }) }); match res { @@ -635,7 +768,13 @@ impl<'a> JNIEnv<'a> { { let obj = obj.into(); non_null!(obj, "get_object_class"); - Ok(jni_unchecked!(self.internal, GetObjectClass, obj.into_inner()).into()) + Ok(unsafe { + JClass::from_raw(jni_unchecked!( + self.internal, + GetObjectClass, + obj.into_raw() + )) + }) } /// Call a static method in an unsafe manner. This does nothing to check @@ -644,41 +783,38 @@ 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, 'm, T, U>( + pub fn call_static_method_unchecked<'c, T, U>( &self, class: T, method_id: U, - ret: JavaType, - args: &[JValue], + ret: ReturnType, + args: &[jvalue], ) -> Result<JValue<'a>> where T: Desc<'a, JClass<'c>>, - U: Desc<'a, JStaticMethodID<'m>>, + U: Desc<'a, JStaticMethodID>, { let class = class.lookup(self)?; - let method_id = method_id.lookup(self)?.into_inner(); + let method_id = method_id.lookup(self)?.into_raw(); - let class = class.into_inner(); - let args: Vec<jvalue> = args.iter().map(|v| v.to_jni()).collect(); + let class = class.into_raw(); let jni_args = args.as_ptr(); // TODO clean this up Ok(match ret { - JavaType::Object(_) | JavaType::Array(_) => { - let obj: JObject = jni_non_void_call!( + ReturnType::Object | ReturnType::Array => { + let obj = jni_non_void_call!( self.internal, CallStaticObjectMethodA, class, method_id, jni_args - ) - .into(); + ); + let obj = unsafe { JObject::from_raw(obj) }; obj.into() } - // JavaType::Object - JavaType::Method(_) => unimplemented!(), - JavaType::Primitive(p) => match p { + ReturnType::Primitive(p) => match p { Primitive::Boolean => jni_non_void_call!( self.internal, CallStaticBooleanMethodA, @@ -763,35 +899,32 @@ impl<'a> JNIEnv<'a> { /// /// Under the hood, this simply calls the `Call<Type>MethodA` method with /// the provided arguments. - pub fn call_method_unchecked<'m, O, T>( + pub fn call_method_unchecked<O, T>( &self, obj: O, method_id: T, - ret: JavaType, - args: &[JValue], + ret: ReturnType, + args: &[jvalue], ) -> Result<JValue<'a>> where O: Into<JObject<'a>>, - T: Desc<'a, JMethodID<'m>>, + T: Desc<'a, JMethodID>, { - let method_id = method_id.lookup(self)?.into_inner(); + let method_id = method_id.lookup(self)?.into_raw(); - let obj = obj.into().into_inner(); + let obj = obj.into().into_raw(); - let args: Vec<jvalue> = args.iter().map(|v| v.to_jni()).collect(); let jni_args = args.as_ptr(); // TODO clean this up Ok(match ret { - JavaType::Object(_) | JavaType::Array(_) => { - let obj: JObject = - jni_non_void_call!(self.internal, CallObjectMethodA, obj, method_id, jni_args) - .into(); + ReturnType::Object | ReturnType::Array => { + let obj = + jni_non_void_call!(self.internal, CallObjectMethodA, obj, method_id, jni_args); + let obj = unsafe { JObject::from_raw(obj) }; obj.into() } - // JavaType::Object - JavaType::Method(_) => unimplemented!(), - JavaType::Primitive(p) => match p { + ReturnType::Primitive(p) => match p { Primitive::Boolean => { jni_non_void_call!(self.internal, CallBooleanMethodA, obj, method_id, jni_args) .into() @@ -867,7 +1000,8 @@ impl<'a> JNIEnv<'a> { let class = self.auto_local(self.get_object_class(obj)?); - self.call_method_unchecked(obj, (&class, name, sig), parsed.ret, args) + let args: Vec<jvalue> = args.iter().map(|v| v.to_jni()).collect(); + self.call_method_unchecked(obj, (&class, name, sig), parsed.ret, &args) } /// Calls a static method safely. This comes with a number of @@ -902,7 +1036,8 @@ impl<'a> JNIEnv<'a> { // and we'll need that for the next call. let class = class.lookup(self)?; - self.call_static_method_unchecked(class, (class, name, sig), parsed.ret, args) + let args: Vec<jvalue> = args.iter().map(|v| v.to_jni()).collect(); + self.call_static_method_unchecked(class, (class, name, sig), parsed.ret, &args) } /// Create a new object using a constructor. This is done safely using @@ -924,7 +1059,7 @@ impl<'a> JNIEnv<'a> { return Err(Error::InvalidArgList(parsed)); } - if parsed.ret != JavaType::Primitive(Primitive::Void) { + if parsed.ret != ReturnType::Primitive(Primitive::Void) { return Err(Error::InvalidCtorReturn); } @@ -953,13 +1088,14 @@ impl<'a> JNIEnv<'a> { let jni_args: Vec<jvalue> = ctor_args.iter().map(|v| v.to_jni()).collect(); let jni_args = jni_args.as_ptr(); - Ok(jni_non_null_call!( + let obj = jni_non_null_call!( self.internal, NewObjectA, - class.into_inner(), - ctor_id.into_inner(), + class.into_raw(), + ctor_id.into_raw(), jni_args - )) + ); + Ok(unsafe { JObject::from_raw(obj) }) } /// Cast a JObject to a `JList`. This won't throw exceptions or return errors @@ -983,6 +1119,10 @@ impl<'a> JNIEnv<'a> { /// /// This entails a call to `GetStringUTFChars` and only decodes java's /// modified UTF-8 format on conversion to a rust-compatible string. + /// + /// # Panics + /// + /// 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, '_>> { non_null!(obj, "get_string obj argument"); JavaStr::from_env(self, obj) @@ -999,19 +1139,32 @@ impl<'a> JNIEnv<'a> { let ptr: *const c_char = jni_non_null_call!( self.internal, GetStringUTFChars, - obj.into_inner(), + obj.into_raw(), ::std::ptr::null::<jboolean>() as *mut jboolean ); Ok(ptr) } /// Unpin the array returned by `get_string_utf_chars`. - // It is safe to dereference a pointer that comes from `get_string_utf_chars`. - #[allow(clippy::not_unsafe_ptr_arg_deref)] - pub fn release_string_utf_chars(&self, obj: JString, arr: *const c_char) -> Result<()> { + /// + /// # Safety + /// + /// The behaviour is undefined if the array isn't returned by the `get_string_utf_chars` + /// function. + /// + /// # Examples + /// + /// ```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_inner(), arr); + jni_unchecked!(self.internal, ReleaseStringUTFChars, obj.into_raw(), arr); Ok(()) } @@ -1020,11 +1173,8 @@ impl<'a> JNIEnv<'a> { /// format. pub fn new_string<S: Into<JNIString>>(&self, from: S) -> Result<JString<'a>> { let ffi_str = from.into(); - Ok(jni_non_null_call!( - self.internal, - NewStringUTF, - ffi_str.as_ptr() - )) + 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 @@ -1057,8 +1207,8 @@ impl<'a> JNIEnv<'a> { self.internal, NewObjectArray, length, - class.into_inner(), - initial_element.into().into_inner() + class.into_raw(), + initial_element.into().into_raw() )) } @@ -1069,7 +1219,14 @@ impl<'a> JNIEnv<'a> { index: jsize, ) -> Result<JObject<'a>> { non_null!(array, "get_object_array_element array argument"); - Ok(jni_non_void_call!(self.internal, GetObjectArrayElement, array, index).into()) + Ok(unsafe { + JObject::from_raw(jni_non_void_call!( + self.internal, + GetObjectArrayElement, + array, + index + )) + }) } /// Sets an element of the `jobjectArray` array. @@ -1088,7 +1245,7 @@ impl<'a> JNIEnv<'a> { SetObjectArrayElement, array, index, - value.into().into_inner() + value.into().into_raw() ); Ok(()) } @@ -1544,32 +1701,25 @@ impl<'a> JNIEnv<'a> { } /// Get a field without checking the provided type against the actual field. - pub fn get_field_unchecked<'f, O, T>( - &self, - obj: O, - field: T, - ty: JavaType, - ) -> Result<JValue<'a>> + pub fn get_field_unchecked<O, T>(&self, obj: O, field: T, ty: ReturnType) -> Result<JValue<'a>> where O: Into<JObject<'a>>, - T: Desc<'a, JFieldID<'f>>, + T: Desc<'a, JFieldID>, { let obj = obj.into(); non_null!(obj, "get_field_typed obj argument"); - let field = field.lookup(self)?.into_inner(); - let obj = obj.into_inner(); + let field = field.lookup(self)?.into_raw(); + let obj = obj.into_raw(); // TODO clean this up Ok(match ty { - JavaType::Object(_) | JavaType::Array(_) => { - let obj: JObject = - jni_non_void_call!(self.internal, GetObjectField, obj, field).into(); + ReturnType::Object | ReturnType::Array => { + let obj = jni_non_void_call!(self.internal, GetObjectField, obj, field); + let obj = unsafe { JObject::from_raw(obj) }; obj.into() } - // JavaType::Object - JavaType::Method(_) => unimplemented!(), - JavaType::Primitive(p) => match p { + ReturnType::Primitive(p) => match p { Primitive::Boolean => { jni_unchecked!(self.internal, GetBooleanField, obj, field).into() } @@ -1590,21 +1740,21 @@ impl<'a> JNIEnv<'a> { } /// Set a field without any type checking. - pub fn set_field_unchecked<'f, O, T>(&self, obj: O, field: T, val: JValue) -> Result<()> + pub fn set_field_unchecked<O, T>(&self, obj: O, field: T, val: JValue) -> Result<()> where O: Into<JObject<'a>>, - T: Desc<'a, JFieldID<'f>>, + T: Desc<'a, JFieldID>, { let obj = obj.into(); non_null!(obj, "set_field_typed obj argument"); - let field = field.lookup(self)?.into_inner(); - let obj = obj.into_inner(); + let field = field.lookup(self)?.into_raw(); + let obj = obj.into_raw(); // TODO clean this up match val { JValue::Object(o) => { - jni_unchecked!(self.internal, SetObjectField, obj, field, o.into_inner()); + jni_unchecked!(self.internal, SetObjectField, obj, field, o.into_raw()); } // JavaType::Object JValue::Bool(b) => { @@ -1650,7 +1800,7 @@ impl<'a> JNIEnv<'a> { let obj = obj.into(); let class = self.auto_local(self.get_object_class(obj)?); - let parsed = JavaType::from_str(ty.as_ref())?; + let parsed = ReturnType::from_str(ty.as_ref())?; let field_id: JFieldID = (&class, name, ty).lookup(self)?; @@ -1696,7 +1846,7 @@ impl<'a> JNIEnv<'a> { /// Get a static field without checking the provided type against the actual /// field. - pub fn get_static_field_unchecked<'c, 'f, T, U>( + pub fn get_static_field_unchecked<'c, T, U>( &self, class: T, field: U, @@ -1704,16 +1854,18 @@ impl<'a> JNIEnv<'a> { ) -> Result<JValue<'a>> where T: Desc<'a, JClass<'c>>, - U: Desc<'a, JStaticFieldID<'f>>, + U: Desc<'a, JStaticFieldID>, { use JavaType::Primitive as JP; - let class = class.lookup(self)?.into_inner(); - let field = field.lookup(self)?.into_inner(); + let class = class.lookup(self)?.into_raw(); + let field = field.lookup(self)?.into_raw(); let result = match ty { JavaType::Object(_) | JavaType::Array(_) => { - jni_non_void_call!(self.internal, GetStaticObjectField, class, field).into() + let obj = jni_non_void_call!(self.internal, GetStaticObjectField, class, field); + let obj = unsafe { JObject::from_raw(obj) }; + obj.into() } JavaType::Method(_) => return Err(Error::WrongJValueType("Method", "see java field")), JP(Primitive::Boolean) => { @@ -1763,13 +1915,13 @@ impl<'a> JNIEnv<'a> { } /// Set a static field. Requires a class lookup and a field id lookup internally. - pub fn set_static_field<'c, 'f, T, U>(&self, class: T, field: U, value: JValue) -> Result<()> + pub fn set_static_field<'c, T, U>(&self, class: T, field: U, value: JValue) -> Result<()> where T: Desc<'a, JClass<'c>>, - U: Desc<'a, JStaticFieldID<'f>>, + U: Desc<'a, JStaticFieldID>, { - let class = class.lookup(self)?.into_inner(); - let field = field.lookup(self)?.into_inner(); + let class = class.lookup(self)?.into_raw(); + let field = field.lookup(self)?.into_raw(); match value { JValue::Object(v) => jni_unchecked!( @@ -1777,7 +1929,7 @@ impl<'a> JNIEnv<'a> { SetStaticObjectField, class, field, - v.into_inner() + v.into_raw() ), JValue::Byte(v) => jni_unchecked!(self.internal, SetStaticByteField, class, field, v), JValue::Char(v) => jni_unchecked!(self.internal, SetStaticCharField, class, field, v), @@ -1797,18 +1949,29 @@ impl<'a> JNIEnv<'a> { Ok(()) } - /// Surrenders ownership of a rust object to Java. Requires an object with a - /// `long` field to store the pointer. The Rust value will be wrapped in a - /// Mutex since Java will be controlling where it'll be used thread-wise. - /// Unsafe because it leaks memory if `take_rust_field` is never called (so - /// be sure to make a finalizer). + /// Surrenders ownership of a Rust value to Java. + /// + /// This requires an object with a `long` field to store the pointer. + /// + /// The Rust value will be implicitly wrapped in a `Box<Mutex<T>>`. + /// + /// The Java object will be locked before changing the field value. + /// + /// # Safety + /// + /// It's important to note that using this API will leak memory if + /// [`Self::take_rust_field`] is never called so that the Rust type may be + /// dropped. /// - /// **DO NOT** make a copy of the object containing one of these fields. If - /// you've set up a finalizer to pass it back to Rust upon being GC'd, it - /// will point to invalid memory and will likely attempt to be deallocated - /// again. + /// One suggestion that may help ensure that a set Rust field will be + /// 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. #[allow(unused_variables)] - pub fn set_rust_field<O, S, T>(&self, obj: O, field: S, rust_object: T) -> Result<()> + pub unsafe fn set_rust_field<O, S, T>(&self, obj: O, field: S, rust_object: T) -> Result<()> where O: Into<JObject<'a>>, S: AsRef<str>, @@ -1823,7 +1986,7 @@ impl<'a> JNIEnv<'a> { // Check to see if we've already set this value. If it's not null, that // means that we're going to leak memory if it gets overwritten. let field_ptr = self - .get_field_unchecked(obj, field_id, JavaType::Primitive(Primitive::Long))? + .get_field_unchecked(obj, field_id, ReturnType::Primitive(Primitive::Long))? .j()? as *mut Mutex<T>; if !field_ptr.is_null() { return Err(Error::FieldAlreadySet(field.as_ref().to_owned())); @@ -1835,12 +1998,21 @@ impl<'a> JNIEnv<'a> { self.set_field_unchecked(obj, field_id, (ptr as crate::sys::jlong).into()) } - /// Gets a lock on a Rust value that's been given to a Java object. Java - /// still retains ownership and `take_rust_field` will still need to be - /// called at some point. Checks for a null pointer, but assumes that the - /// data it points to is valid for T. + /// Gets a lock on a Rust value that's been given to a Java object. + /// + /// Java still retains ownership and [`Self::take_rust_field`] will still + /// need to be called at some point. + /// + /// The Java object will be locked before reading the field value but the + /// Java object lock will be released after the Rust `Mutex` lock for the + /// field value has been taken (i.e the Java object won't be locked once + /// this function returns). + /// + /// # Safety + /// + /// Checks for a null pointer, but assumes that the data it points to is valid for T. #[allow(unused_variables)] - pub fn get_rust_field<O, S, T>(&self, obj: O, field: S) -> Result<MutexGuard<T>> + pub unsafe fn get_rust_field<O, S, T>(&self, obj: O, field: S) -> Result<MutexGuard<T>> where O: Into<JObject<'a>>, S: Into<JNIString>, @@ -1851,20 +2023,22 @@ impl<'a> JNIEnv<'a> { let ptr = self.get_field(obj, field, "J")?.j()? as *mut Mutex<T>; non_null!(ptr, "rust value from Java"); - unsafe { - // dereferencing is safe, because we checked it for null - Ok((*ptr).lock().unwrap()) - } + // dereferencing is safe, because we checked it for null + Ok((*ptr).lock().unwrap()) } - /// Take a Rust field back from Java. Makes sure that the pointer is - /// non-null, but still assumes that the data it points to is valid for T. - /// Sets the field to a null pointer to signal that it's empty. + /// Take a Rust field back from Java. + /// + /// It sets the field to a null pointer to signal that it's empty. + /// + /// The Java object will be locked before taking the field value. + /// + /// # Safety /// - /// This will return an error in the event that there's an outstanding lock - /// on the object. + /// 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 fn take_rust_field<O, S, T>(&self, obj: O, field: S) -> Result<T> + pub unsafe fn take_rust_field<O, S, T>(&self, obj: O, field: S) -> Result<T> where O: Into<JObject<'a>>, S: AsRef<str>, @@ -1878,12 +2052,12 @@ impl<'a> JNIEnv<'a> { let guard = self.lock_obj(obj)?; let ptr = self - .get_field_unchecked(obj, field_id, JavaType::Primitive(Primitive::Long))? + .get_field_unchecked(obj, field_id, ReturnType::Primitive(Primitive::Long))? .j()? as *mut Mutex<T>; non_null!(ptr, "rust value from Java"); - let mbox = unsafe { Box::from_raw(ptr) }; + let mbox = Box::from_raw(ptr); // attempt to acquire the lock. This prevents us from consuming the // mutex if there's an outstanding lock. No one else will be able to @@ -1908,7 +2082,7 @@ impl<'a> JNIEnv<'a> { where O: Into<JObject<'a>>, { - let inner = obj.into().into_inner(); + let inner = obj.into().into_raw(); let _ = jni_unchecked!(self.internal, MonitorEnter, inner); Ok(MonitorGuard { @@ -1957,7 +2131,7 @@ impl<'a> JNIEnv<'a> { let res = jni_non_void_call!( self.internal, RegisterNatives, - class.into_inner(), + class.into_raw(), jni_native_methods.as_ptr(), jni_native_methods.len() as jint ); @@ -1970,7 +2144,7 @@ impl<'a> JNIEnv<'a> { T: Desc<'a, JClass<'c>>, { let class = class.lookup(self)?; - let res = jni_non_void_call!(self.internal, UnregisterNatives, class.into_inner()); + let res = jni_non_void_call!(self.internal, UnregisterNatives, class.into_raw()); jni_error_code_to_result(res) } @@ -1999,9 +2173,9 @@ impl<'a> JNIEnv<'a> { &self, array: jarray, mode: ReleaseMode, - ) -> Result<AutoArray<T>> { + ) -> Result<AutoArray<'a, T>> { non_null!(array, "get_array_elements array argument"); - AutoArray::new(self, array.into(), mode) + AutoArray::new(self, unsafe { JObject::from_raw(array) }, mode) } /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) @@ -2009,7 +2183,7 @@ impl<'a> JNIEnv<'a> { &self, array: jintArray, mode: ReleaseMode, - ) -> Result<AutoArray<jint>> { + ) -> Result<AutoArray<'a, jint>> { self.get_array_elements(array, mode) } @@ -2018,7 +2192,7 @@ impl<'a> JNIEnv<'a> { &self, array: jlongArray, mode: ReleaseMode, - ) -> Result<AutoArray<jlong>> { + ) -> Result<AutoArray<'a, jlong>> { self.get_array_elements(array, mode) } @@ -2027,7 +2201,7 @@ impl<'a> JNIEnv<'a> { &self, array: jbyteArray, mode: ReleaseMode, - ) -> Result<AutoArray<jbyte>> { + ) -> Result<AutoArray<'a, jbyte>> { self.get_array_elements(array, mode) } @@ -2036,7 +2210,7 @@ impl<'a> JNIEnv<'a> { &self, array: jbooleanArray, mode: ReleaseMode, - ) -> Result<AutoArray<jboolean>> { + ) -> Result<AutoArray<'a, jboolean>> { self.get_array_elements(array, mode) } @@ -2045,7 +2219,7 @@ impl<'a> JNIEnv<'a> { &self, array: jcharArray, mode: ReleaseMode, - ) -> Result<AutoArray<jchar>> { + ) -> Result<AutoArray<'a, jchar>> { self.get_array_elements(array, mode) } @@ -2054,7 +2228,7 @@ impl<'a> JNIEnv<'a> { &self, array: jshortArray, mode: ReleaseMode, - ) -> Result<AutoArray<jshort>> { + ) -> Result<AutoArray<'a, jshort>> { self.get_array_elements(array, mode) } @@ -2063,7 +2237,7 @@ impl<'a> JNIEnv<'a> { &self, array: jfloatArray, mode: ReleaseMode, - ) -> Result<AutoArray<jfloat>> { + ) -> Result<AutoArray<'a, jfloat>> { self.get_array_elements(array, mode) } @@ -2072,7 +2246,7 @@ impl<'a> JNIEnv<'a> { &self, array: jdoubleArray, mode: ReleaseMode, - ) -> Result<AutoArray<jdouble>> { + ) -> Result<AutoArray<'a, jdouble>> { self.get_array_elements(array, mode) } @@ -2114,7 +2288,13 @@ impl<'a> JNIEnv<'a> { array, &mut is_copy ); - AutoPrimitiveArray::new(self, array.into(), ptr, mode, is_copy == sys::JNI_TRUE) + AutoPrimitiveArray::new( + self, + unsafe { JObject::from_raw(array) }, + ptr, + mode, + is_copy == sys::JNI_TRUE, + ) } } diff --git a/src/wrapper/macros.rs b/src/wrapper/macros.rs index f4d508e..68604ed 100644 --- a/src/wrapper/macros.rs +++ b/src/wrapper/macros.rs @@ -4,7 +4,7 @@ macro_rules! jni_non_null_call { ( $jnienv:expr, $name:tt $(, $args:expr )* ) => ({ let res = jni_non_void_call!($jnienv, $name $(, $args)*); - non_null!(res, concat!(stringify!($name), " result")).into() + non_null!(res, concat!(stringify!($name), " result")) }) } @@ -14,6 +14,7 @@ macro_rules! jni_non_void_call { ( $jnienv:expr, $name:tt $(, $args:expr )* ) => ({ log::trace!("calling checked jni method: {}", stringify!($name)); + #[allow(unused_unsafe)] let res = unsafe { jni_method!($jnienv, $name)($jnienv, $($args),*) }; @@ -39,6 +40,7 @@ macro_rules! jni_void_call { ( $jnienv:expr, $name:tt $(, $args:expr )* ) => ({ log::trace!("calling checked jni method: {}", stringify!($name)); + #[allow(unused_unsafe)] unsafe { jni_method!($jnienv, $name)($jnienv, $($args),*) }; @@ -53,6 +55,7 @@ macro_rules! jni_unchecked { ( $jnienv:expr, $name:tt $(, $args:expr )* ) => ({ log::trace!("calling unchecked jni method: {}", stringify!($name)); + #[allow(unused_unsafe)] unsafe { jni_method!($jnienv, $name)($jnienv, $($args),*) } diff --git a/src/wrapper/objects/auto_array.rs b/src/wrapper/objects/auto_array.rs index 253dacf..2c73fac 100644 --- a/src/wrapper/objects/auto_array.rs +++ b/src/wrapper/objects/auto_array.rs @@ -64,16 +64,16 @@ type_array!(jdouble, GetDoubleArrayElements, ReleaseDoubleArrayElements); /// /// AutoArray provides automatic array release through a call to appropriate /// Release<Type>ArrayElements when it goes out of scope. -pub struct AutoArray<'a: 'b, 'b, T: TypeArray> { +pub struct AutoArray<'a, T: TypeArray> { obj: JObject<'a>, ptr: NonNull<T>, mode: ReleaseMode, is_copy: bool, - env: &'b JNIEnv<'a>, + env: JNIEnv<'a>, } -impl<'a, 'b, T: TypeArray> AutoArray<'a, 'b, T> { - pub(crate) fn new(env: &'b JNIEnv<'a>, obj: JObject<'a>, mode: ReleaseMode) -> Result<Self> { +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, @@ -83,7 +83,7 @@ impl<'a, 'b, T: TypeArray> AutoArray<'a, 'b, T> { }, mode, is_copy: is_copy == sys::JNI_TRUE, - env, + env: *env, }) } @@ -98,7 +98,7 @@ impl<'a, 'b, T: TypeArray> AutoArray<'a, 'b, T> { } fn release_array_elements(&self, mode: i32) -> Result<()> { - T::release(self.env, self.obj, self.ptr, mode) + T::release(&self.env, self.obj, self.ptr, mode) } /// Don't commit the changes to the array on release (if it is a copy). @@ -120,7 +120,7 @@ impl<'a, 'b, T: TypeArray> AutoArray<'a, 'b, T> { } } -impl<'a, 'b, T: TypeArray> Drop for AutoArray<'a, 'b, T> { +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 { @@ -130,7 +130,7 @@ impl<'a, 'b, T: TypeArray> Drop for AutoArray<'a, 'b, T> { } } -impl<'a, T: TypeArray> From<&'a AutoArray<'a, '_, T>> for *mut T { +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/global_ref.rs b/src/wrapper/objects/global_ref.rs index c9ab5e6..7e8b81c 100644 --- a/src/wrapper/objects/global_ref.rs +++ b/src/wrapper/objects/global_ref.rs @@ -21,11 +21,12 @@ use crate::{errors::Result, objects::JObject, sys, JNIEnv, JavaVM}; /// the `GlobalRef#drop` will print a warning and implicitly `attach` and `detach` it, which /// significantly affects performance. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct GlobalRef { inner: Arc<GlobalRefGuard>, } +#[derive(Debug)] struct GlobalRefGuard { obj: JObject<'static>, vm: JavaVM, @@ -66,7 +67,7 @@ impl GlobalRefGuard { /// has already been called. unsafe fn from_raw(vm: JavaVM, obj: sys::jobject) -> Self { GlobalRefGuard { - obj: JObject::from(obj), + obj: JObject::from_raw(obj), vm, } } @@ -85,7 +86,7 @@ impl Drop for GlobalRefGuard { fn drop_impl(env: &JNIEnv, global_ref: JObject) -> 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_inner()); + jni_unchecked!(internal, DeleteGlobalRef, global_ref.into_raw()); Ok(()) } diff --git a/src/wrapper/objects/jbytebuffer.rs b/src/wrapper/objects/jbytebuffer.rs index 90434f0..fd6cb60 100644 --- a/src/wrapper/objects/jbytebuffer.rs +++ b/src/wrapper/objects/jbytebuffer.rs @@ -3,15 +3,9 @@ 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)] +#[derive(Clone, Copy, Debug)] pub struct JByteBuffer<'a>(JObject<'a>); -impl<'a> From<jobject> for JByteBuffer<'a> { - fn from(other: jobject) -> Self { - JByteBuffer(From::from(other)) - } -} - impl<'a> ::std::ops::Deref for JByteBuffer<'a> { type Target = JObject<'a>; @@ -27,7 +21,29 @@ impl<'a> From<JByteBuffer<'a>> for JObject<'a> { } impl<'a> From<JObject<'a>> for JByteBuffer<'a> { - fn from(other: JObject) -> JByteBuffer { - (other.into_inner() as jobject).into() + fn from(other: JObject) -> Self { + unsafe { Self::from_raw(other.into_raw()) } + } +} + +impl<'a> std::default::Default for JByteBuffer<'a> { + fn default() -> Self { + Self(JObject::null()) + } +} + +impl<'a> JByteBuffer<'a> { + /// Creates a [`JByteBuffer`] that wraps the given `raw` [`jobject`] + /// + /// # Safety + /// No runtime check is made to verify that the given [`jobject`] is an instance of + /// a `ByteBuffer`. + pub unsafe fn from_raw(raw: jobject) -> Self { + Self(JObject::from_raw(raw as jobject)) + } + + /// Unwrap to the raw jni type. + pub fn into_raw(self) -> jobject { + self.0.into_raw() as jobject } } diff --git a/src/wrapper/objects/jclass.rs b/src/wrapper/objects/jclass.rs index 4849032..aa7194b 100644 --- a/src/wrapper/objects/jclass.rs +++ b/src/wrapper/objects/jclass.rs @@ -9,12 +9,6 @@ use crate::{ #[derive(Clone, Copy, Debug)] pub struct JClass<'a>(JObject<'a>); -impl<'a> From<jclass> for JClass<'a> { - fn from(other: jclass) -> Self { - JClass(From::from(other as jobject)) - } -} - impl<'a> ::std::ops::Deref for JClass<'a> { type Target = JObject<'a>; @@ -31,7 +25,29 @@ impl<'a> From<JClass<'a>> for JObject<'a> { /// This conversion assumes that the `JObject` is a pointer to a class object. impl<'a> From<JObject<'a>> for JClass<'a> { - fn from(other: JObject) -> JClass { - (other.into_inner() as jclass).into() + fn from(other: JObject) -> Self { + unsafe { Self::from_raw(other.into_raw()) } + } +} + +impl<'a> std::default::Default for JClass<'a> { + fn default() -> Self { + Self(JObject::null()) + } +} + +impl<'a> JClass<'a> { + /// Creates a [`JClass`] that wraps the given `raw` [`jclass`] + /// + /// # Safety + /// + /// Expects a valid pointer or `null` + pub unsafe fn from_raw(raw: jclass) -> Self { + Self(JObject::from_raw(raw as jobject)) + } + + /// 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 1d171a4..c445ec6 100644 --- a/src/wrapper/objects/jfieldid.rs +++ b/src/wrapper/objects/jfieldid.rs @@ -1,31 +1,48 @@ -use std::marker::PhantomData; - use crate::sys::jfieldID; -/// Wrapper around `sys::jfieldid` 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 -/// `jfieldid`. +/// Wrapper around [`jfieldID`] that implements `Send` + `Sync` since method IDs +/// are valid across threads (not tied to a `JNIEnv`). +/// +/// There is no lifetime associated with these since they aren't garbage +/// collected like objects and their lifetime is not implicitly connected with +/// the scope in which they are queried. +/// +/// 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 [`jfieldID`]. +/// +/// # Safety +/// +/// According to the JNI spec field IDs may be invalidated when the +/// corresponding class is unloaded. +/// +/// Since this constraint can't be encoded as a Rust lifetime, and to avoid the +/// excessive cost of having every Method ID be associated with a global +/// reference to the corresponding class then it is the developers +/// responsibility to ensure they hold some class reference for the lifetime of +/// cached method IDs. #[repr(transparent)] #[derive(Copy, Clone)] -pub struct JFieldID<'a> { +pub struct JFieldID { internal: jfieldID, - lifetime: PhantomData<&'a ()>, } -impl<'a> From<jfieldID> for JFieldID<'a> { - fn from(other: jfieldID) -> Self { - JFieldID { - internal: other, - lifetime: PhantomData, - } +// Field IDs are valid across threads (not tied to a JNIEnv) +unsafe impl Send for JFieldID {} +unsafe impl Sync for JFieldID {} + +impl JFieldID { + /// Creates a [`JFieldID`] that wraps the given `raw` [`jfieldID`] + /// + /// # Safety + /// + /// Expects a valid, non-`null` ID + pub unsafe fn from_raw(raw: jfieldID) -> Self { + debug_assert!(!raw.is_null(), "from_raw fieldID argument"); + Self { internal: raw } } -} -impl<'a> JFieldID<'a> { /// Unwrap to the internal jni type. - pub fn into_inner(self) -> jfieldID { + pub fn into_raw(self) -> jfieldID { self.internal } } diff --git a/src/wrapper/objects/jlist.rs b/src/wrapper/objects/jlist.rs index 34b15ef..1f07f71 100644 --- a/src/wrapper/objects/jlist.rs +++ b/src/wrapper/objects/jlist.rs @@ -1,7 +1,7 @@ use crate::{ errors::*, - objects::{JMethodID, JObject}, - signature::{JavaType, Primitive}, + objects::{JMethodID, JObject, JValue}, + signature::{Primitive, ReturnType}, sys::jint, JNIEnv, }; @@ -13,11 +13,11 @@ use crate::{ /// call. pub struct JList<'a: 'b, 'b> { internal: JObject<'a>, - get: JMethodID<'a>, - add: JMethodID<'a>, - add_idx: JMethodID<'a>, - remove: JMethodID<'a>, - size: JMethodID<'a>, + get: JMethodID, + add: JMethodID, + add_idx: JMethodID, + remove: JMethodID, + size: JMethodID, env: &'b JNIEnv<'a>, } @@ -65,8 +65,8 @@ impl<'a: 'b, 'b> JList<'a, 'b> { let result = self.env.call_method_unchecked( self.internal, self.get, - JavaType::Object("java/lang/Object".into()), - &[idx.into()], + ReturnType::Object, + &[JValue::from(idx).to_jni()], ); match result { @@ -83,8 +83,8 @@ impl<'a: 'b, 'b> JList<'a, 'b> { let result = self.env.call_method_unchecked( self.internal, self.add, - JavaType::Primitive(Primitive::Boolean), - &[value.into()], + ReturnType::Primitive(Primitive::Boolean), + &[JValue::from(value).to_jni()], ); let _ = result?; @@ -96,8 +96,8 @@ impl<'a: 'b, 'b> JList<'a, 'b> { let result = self.env.call_method_unchecked( self.internal, self.add_idx, - JavaType::Primitive(Primitive::Void), - &[idx.into(), value.into()], + ReturnType::Primitive(Primitive::Void), + &[JValue::from(idx).to_jni(), JValue::from(value).to_jni()], ); let _ = result?; @@ -109,8 +109,8 @@ impl<'a: 'b, 'b> JList<'a, 'b> { let result = self.env.call_method_unchecked( self.internal, self.remove, - JavaType::Object("java/lang/Object".into()), - &[idx.into()], + ReturnType::Object, + &[JValue::from(idx).to_jni()], ); match result { @@ -127,7 +127,7 @@ impl<'a: 'b, 'b> JList<'a, 'b> { let result = self.env.call_method_unchecked( self.internal, self.size, - JavaType::Primitive(Primitive::Int), + ReturnType::Primitive(Primitive::Int), &[], ); @@ -146,8 +146,8 @@ impl<'a: 'b, 'b> JList<'a, 'b> { let result = self.env.call_method_unchecked( self.internal, self.remove, - JavaType::Object("java/lang/Object".into()), - &[(size - 1).into()], + ReturnType::Object, + &[JValue::from(size - 1).to_jni()], ); match result { @@ -163,7 +163,7 @@ impl<'a: 'b, 'b> JList<'a, 'b> { /// `EntrySet` from java and iterating over it. pub fn iter(&self) -> Result<JListIter<'a, 'b, '_>> { Ok(JListIter { - list: &self, + list: self, current: 0, size: self.size()?, }) diff --git a/src/wrapper/objects/jmap.rs b/src/wrapper/objects/jmap.rs index 23ac892..bc391e3 100644 --- a/src/wrapper/objects/jmap.rs +++ b/src/wrapper/objects/jmap.rs @@ -1,7 +1,7 @@ use crate::{ errors::*, - objects::{AutoLocal, JMethodID, JObject}, - signature::{JavaType, Primitive}, + objects::{AutoLocal, JMethodID, JObject, JValue}, + signature::{Primitive, ReturnType}, JNIEnv, }; @@ -13,9 +13,9 @@ use crate::{ pub struct JMap<'a: 'b, 'b> { internal: JObject<'a>, class: AutoLocal<'a, 'b>, - get: JMethodID<'a>, - put: JMethodID<'a>, - remove: JMethodID<'a>, + get: JMethodID, + put: JMethodID, + remove: JMethodID, env: &'b JNIEnv<'a>, } @@ -67,8 +67,8 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { let result = self.env.call_method_unchecked( self.internal, self.get, - JavaType::Object("java/lang/Object".into()), - &[key.into()], + ReturnType::Object, + &[JValue::from(key).to_jni()], ); match result { @@ -86,8 +86,8 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { let result = self.env.call_method_unchecked( self.internal, self.put, - JavaType::Object("java/lang/Object".into()), - &[key.into(), value.into()], + ReturnType::Object, + &[JValue::from(key).to_jni(), JValue::from(value).to_jni()], ); match result { @@ -105,8 +105,8 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { let result = self.env.call_method_unchecked( self.internal, self.remove, - JavaType::Object("java/lang/Object".into()), - &[key.into()], + ReturnType::Object, + &[JValue::from(key).to_jni()], ); match result { @@ -152,7 +152,7 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { .call_method_unchecked( self.internal, (&self.class, "entrySet", "()Ljava/util/Set;"), - JavaType::Object("java/util/Set".into()), + ReturnType::Object, &[], )? .l()?; @@ -162,7 +162,7 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { .call_method_unchecked( entry_set, ("java/util/Set", "iterator", "()Ljava/util/Iterator;"), - JavaType::Object("java/util/Iterator".into()), + ReturnType::Object, &[], )? .l()?; @@ -172,7 +172,7 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { let iter = self.env.auto_local(iter); Ok(JMapIter { - map: &self, + map: self, has_next, next, get_key, @@ -188,10 +188,10 @@ impl<'a: 'b, 'b> JMap<'a, 'b> { /// and generic enough to use elsewhere. pub struct JMapIter<'a, 'b, 'c> { map: &'c JMap<'a, 'b>, - has_next: JMethodID<'a>, - next: JMethodID<'a>, - get_key: JMethodID<'a>, - get_value: JMethodID<'a>, + has_next: JMethodID, + next: JMethodID, + get_key: JMethodID, + get_value: JMethodID, iter: AutoLocal<'a, 'b>, } @@ -204,7 +204,7 @@ impl<'a: 'b, 'b: 'c, 'c> JMapIter<'a, 'b, 'c> { .call_method_unchecked( iter, self.has_next, - JavaType::Primitive(Primitive::Boolean), + ReturnType::Primitive(Primitive::Boolean), &[], )? .z()?; @@ -215,34 +215,19 @@ impl<'a: 'b, 'b: 'c, 'c> JMapIter<'a, 'b, 'c> { let next = self .map .env - .call_method_unchecked( - iter, - self.next, - JavaType::Object("java/util/Map$Entry".into()), - &[], - )? + .call_method_unchecked(iter, self.next, ReturnType::Object, &[])? .l()?; let key = self .map .env - .call_method_unchecked( - next, - self.get_key, - JavaType::Object("java/lang/Object".into()), - &[], - )? + .call_method_unchecked(next, self.get_key, ReturnType::Object, &[])? .l()?; let value = self .map .env - .call_method_unchecked( - next, - self.get_value, - JavaType::Object("java/lang/Object".into()), - &[], - )? + .call_method_unchecked(next, self.get_value, ReturnType::Object, &[])? .l()?; Ok(Some((key, value))) diff --git a/src/wrapper/objects/jmethodid.rs b/src/wrapper/objects/jmethodid.rs index d016767..892fe43 100644 --- a/src/wrapper/objects/jmethodid.rs +++ b/src/wrapper/objects/jmethodid.rs @@ -1,31 +1,48 @@ -use std::marker::PhantomData; - use crate::sys::jmethodID; -/// Wrapper around `sys::jmethodid` 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 -/// `jmethodid`. +/// Wrapper around [`jmethodID`] that implements `Send` + `Sync` since method IDs +/// are valid across threads (not tied to a `JNIEnv`). +/// +/// There is no lifetime associated with these since they aren't garbage +/// collected like objects and their lifetime is not implicitly connected with +/// the scope in which they are queried. +/// +/// 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 [`jmethodID`]. +/// +/// # Safety +/// +/// According to the JNI spec method IDs may be invalidated when the +/// corresponding class is unloaded. +/// +/// Since this constraint can't be encoded as a Rust lifetime, and to avoid the +/// excessive cost of having every Method ID be associated with a global +/// reference to the corresponding class then it is the developers +/// responsibility to ensure they hold some class reference for the lifetime of +/// cached method IDs. #[repr(transparent)] #[derive(Copy, Clone, Debug)] -pub struct JMethodID<'a> { +pub struct JMethodID { internal: jmethodID, - lifetime: PhantomData<&'a ()>, } -impl<'a> From<jmethodID> for JMethodID<'a> { - fn from(other: jmethodID) -> Self { - JMethodID { - internal: other, - lifetime: PhantomData, - } +// Method IDs are valid across threads (not tied to a JNIEnv) +unsafe impl Send for JMethodID {} +unsafe impl Sync for JMethodID {} + +impl JMethodID { + /// Creates a [`JMethodID`] that wraps the given `raw` [`jmethodID`] + /// + /// # Safety + /// + /// Expects a valid, non-`null` ID + pub unsafe fn from_raw(raw: jmethodID) -> Self { + debug_assert!(!raw.is_null(), "from_raw methodID argument"); + Self { internal: raw } } -} -impl<'a> JMethodID<'a> { /// Unwrap to the internal jni type. - pub fn into_inner(self) -> jmethodID { + pub fn into_raw(self) -> jmethodID { self.internal } } diff --git a/src/wrapper/objects/jobject.rs b/src/wrapper/objects/jobject.rs index be28f76..1b55dae 100644 --- a/src/wrapper/objects/jobject.rs +++ b/src/wrapper/objects/jobject.rs @@ -17,15 +17,6 @@ pub struct JObject<'a> { lifetime: PhantomData<&'a ()>, } -impl<'a> From<jobject> for JObject<'a> { - fn from(other: jobject) -> Self { - JObject { - internal: other, - lifetime: PhantomData, - } - } -} - impl<'a> ::std::ops::Deref for JObject<'a> { type Target = jobject; @@ -35,13 +26,31 @@ impl<'a> ::std::ops::Deref for JObject<'a> { } impl<'a> JObject<'a> { + /// Creates a [`JObject`] that wraps the given `raw` [`jobject`] + /// + /// # Safety + /// + /// Expects a valid pointer or `null` + pub unsafe fn from_raw(raw: jobject) -> Self { + Self { + internal: raw, + lifetime: PhantomData, + } + } + /// Unwrap to the internal jni type. - pub fn into_inner(self) -> jobject { + pub fn into_raw(self) -> jobject { self.internal } /// Creates a new null object pub fn null() -> JObject<'a> { - (::std::ptr::null_mut() as jobject).into() + unsafe { Self::from_raw(std::ptr::null_mut() as jobject) } + } +} + +impl<'a> std::default::Default for JObject<'a> { + fn default() -> Self { + Self::null() } } diff --git a/src/wrapper/objects/jstaticfieldid.rs b/src/wrapper/objects/jstaticfieldid.rs index ac72b7f..4579b44 100644 --- a/src/wrapper/objects/jstaticfieldid.rs +++ b/src/wrapper/objects/jstaticfieldid.rs @@ -1,31 +1,48 @@ -use std::marker::PhantomData; - use crate::sys::jfieldID; -/// Wrapper around `sys::jstaticfieldid` 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 -/// `jstaticfieldid`. +/// Wrapper around [`jfieldID`] that implements `Send` + `Sync` since field IDs +/// are valid across threads (not tied to a `JNIEnv`). +/// +/// There is no lifetime associated with these since they aren't garbage +/// collected like objects and their lifetime is not implicitly connected with +/// the scope in which they are queried. +/// +/// 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 [`jfieldID`]. +/// +/// # Safety +/// +/// According to the JNI spec field IDs may be invalidated when the +/// corresponding class is unloaded. +/// +/// Since this constraint can't be encoded as a Rust lifetime, and to avoid the +/// excessive cost of having every Method ID be associated with a global +/// reference to the corresponding class then it is the developers +/// responsibility to ensure they hold some class reference for the lifetime of +/// cached method IDs. #[repr(transparent)] -#[derive(Copy, Clone)] -pub struct JStaticFieldID<'a> { +#[derive(Copy, Clone, Debug)] +pub struct JStaticFieldID { internal: jfieldID, - lifetime: PhantomData<&'a ()>, } -impl<'a> From<jfieldID> for JStaticFieldID<'a> { - fn from(other: jfieldID) -> Self { - JStaticFieldID { - internal: other, - lifetime: PhantomData, - } +// Static Field IDs are valid across threads (not tied to a JNIEnv) +unsafe impl Send for JStaticFieldID {} +unsafe impl Sync for JStaticFieldID {} + +impl JStaticFieldID { + /// Creates a [`JStaticFieldID`] that wraps the given `raw` [`jfieldID`] + /// + /// # Safety + /// + /// Expects a valid, non-`null` ID + pub unsafe fn from_raw(raw: jfieldID) -> Self { + debug_assert!(!raw.is_null(), "from_raw fieldID argument"); + Self { internal: raw } } -} -impl<'a> JStaticFieldID<'a> { /// Unwrap to the internal jni type. - pub fn into_inner(self) -> jfieldID { + pub fn into_raw(self) -> jfieldID { self.internal } } diff --git a/src/wrapper/objects/jstaticmethodid.rs b/src/wrapper/objects/jstaticmethodid.rs index 55a9f3e..ada3fb9 100644 --- a/src/wrapper/objects/jstaticmethodid.rs +++ b/src/wrapper/objects/jstaticmethodid.rs @@ -1,32 +1,48 @@ -use std::marker::PhantomData; - use crate::sys::jmethodID; -/// Wrapper around `sys::jmethodid` 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 -/// `jmethodid`. This represents static methods only since they require a -/// different set of JNI signatures. +/// Wrapper around [`jmethodID`] that implements `Send` + `Sync` since method IDs +/// are valid across threads (not tied to a `JNIEnv`). +/// +/// There is no lifetime associated with these since they aren't garbage +/// collected like objects and their lifetime is not implicitly connected with +/// the scope in which they are queried. +/// +/// 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 [`jmethodID`]. +/// +/// # Safety +/// +/// According to the JNI spec method IDs may be invalidated when the +/// corresponding class is unloaded. +/// +/// Since this constraint can't be encoded as a Rust lifetime, and to avoid the +/// excessive cost of having every Method ID be associated with a global +/// reference to the corresponding class then it is the developers +/// responsibility to ensure they hold some class reference for the lifetime of +/// cached method IDs. #[repr(transparent)] -#[derive(Copy, Clone)] -pub struct JStaticMethodID<'a> { +#[derive(Copy, Clone, Debug)] +pub struct JStaticMethodID { internal: jmethodID, - lifetime: PhantomData<&'a ()>, } -impl<'a> From<jmethodID> for JStaticMethodID<'a> { - fn from(other: jmethodID) -> Self { - JStaticMethodID { - internal: other, - lifetime: PhantomData, - } +// Method IDs are valid across threads (not tied to a JNIEnv) +unsafe impl Send for JStaticMethodID {} +unsafe impl Sync for JStaticMethodID {} + +impl JStaticMethodID { + /// Creates a [`JStaticMethodID`] that wraps the given `raw` [`jmethodID`] + /// + /// # Safety + /// + /// Expects a valid, non-`null` ID + pub unsafe fn from_raw(raw: jmethodID) -> Self { + debug_assert!(!raw.is_null(), "from_raw methodID argument"); + Self { internal: raw } } -} -impl<'a> JStaticMethodID<'a> { /// Unwrap to the internal jni type. - pub fn into_inner(self) -> jmethodID { + pub fn into_raw(self) -> jmethodID { self.internal } } diff --git a/src/wrapper/objects/jstring.rs b/src/wrapper/objects/jstring.rs index 92669a6..a5df7aa 100644 --- a/src/wrapper/objects/jstring.rs +++ b/src/wrapper/objects/jstring.rs @@ -9,12 +9,6 @@ use crate::{ #[derive(Clone, Copy)] pub struct JString<'a>(JObject<'a>); -impl<'a> From<jstring> for JString<'a> { - fn from(other: jstring) -> Self { - JString(From::from(other as jobject)) - } -} - impl<'a> ::std::ops::Deref for JString<'a> { type Target = JObject<'a>; @@ -30,7 +24,29 @@ impl<'a> From<JString<'a>> for JObject<'a> { } impl<'a> From<JObject<'a>> for JString<'a> { - fn from(other: JObject) -> JString { - (other.into_inner() as jstring).into() + fn from(other: JObject) -> Self { + unsafe { Self::from_raw(other.into_raw()) } + } +} + +impl<'a> std::default::Default for JString<'a> { + fn default() -> Self { + Self(JObject::null()) + } +} + +impl<'a> JString<'a> { + /// Creates a [`JString`] that wraps the given `raw` [`jstring`] + /// + /// # Safety + /// + /// Expects a valid pointer or `null` + pub unsafe fn from_raw(raw: jstring) -> Self { + Self(JObject::from_raw(raw as jobject)) + } + + /// Unwrap to the raw jni type. + pub fn into_raw(self) -> jstring { + self.0.into_raw() as jstring } } diff --git a/src/wrapper/objects/jthrowable.rs b/src/wrapper/objects/jthrowable.rs index 03edc46..f77f6e3 100644 --- a/src/wrapper/objects/jthrowable.rs +++ b/src/wrapper/objects/jthrowable.rs @@ -9,12 +9,6 @@ use crate::{ #[derive(Clone, Copy)] pub struct JThrowable<'a>(JObject<'a>); -impl<'a> From<jthrowable> for JThrowable<'a> { - fn from(other: jthrowable) -> Self { - JThrowable(From::from(other as jobject)) - } -} - impl<'a> ::std::ops::Deref for JThrowable<'a> { type Target = JObject<'a>; @@ -30,7 +24,29 @@ impl<'a> From<JThrowable<'a>> for JObject<'a> { } impl<'a> From<JObject<'a>> for JThrowable<'a> { - fn from(other: JObject) -> JThrowable { - (other.into_inner() as jthrowable).into() + fn from(other: JObject) -> Self { + unsafe { Self::from_raw(other.into_raw()) } + } +} + +impl<'a> std::default::Default for JThrowable<'a> { + fn default() -> Self { + Self(JObject::null()) + } +} + +impl<'a> JThrowable<'a> { + /// Creates a [`JThrowable`] that wraps the given `raw` [`jthrowable`] + /// + /// # Safety + /// + /// Expects a valid pointer or `null` + pub unsafe fn from_raw(raw: jthrowable) -> Self { + Self(JObject::from_raw(raw as jobject)) + } + + /// Unwrap to the raw jni type. + pub fn into_raw(self) -> jthrowable { + self.0.into_raw() as jthrowable } } diff --git a/src/wrapper/objects/release_mode.rs b/src/wrapper/objects/release_mode.rs index 93e4eea..ff81df7 100644 --- a/src/wrapper/objects/release_mode.rs +++ b/src/wrapper/objects/release_mode.rs @@ -4,7 +4,7 @@ use crate::sys::JNI_ABORT; /// /// This defines the release mode of AutoArray (and AutoPrimitiveArray) resources, and /// related release array functions. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] #[repr(i32)] pub enum ReleaseMode { /// Copy back the content and free the elems buffer. For read-only access, prefer diff --git a/src/wrapper/signature.rs b/src/wrapper/signature.rs index 36b6da6..46444cc 100644 --- a/src/wrapper/signature.rs +++ b/src/wrapper/signature.rs @@ -70,6 +70,40 @@ impl fmt::Display for JavaType { } } +/// Enum representing any java type that may be used as a return value +/// +/// This type intentionally avoids capturing any heap allocated types (to avoid +/// allocations while making JNI method calls) and so it doesn't fully qualify +/// the object or array types with a String like `JavaType::Object` does. +#[allow(missing_docs)] +#[derive(Eq, PartialEq, Debug, Clone)] +pub enum ReturnType { + Primitive(Primitive), + Object, + Array, +} + +impl FromStr for ReturnType { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + parser(parse_return) + .parse(s) + .map(|res| res.0) + .map_err(|e| Error::ParseFailed(e, s.to_owned())) + } +} + +impl fmt::Display for ReturnType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ReturnType::Primitive(ref ty) => ty.fmt(f), + ReturnType::Object => write!(f, "L;"), + ReturnType::Array => write!(f, "["), + } + } +} + /// A method type signature. This is the structure representation of something /// like `(Ljava/lang/String;)Z`. Used by the `call_(object|static)_method` /// functions on jnienv to ensure safety. @@ -77,7 +111,7 @@ impl fmt::Display for JavaType { #[derive(Eq, PartialEq, Debug, Clone)] pub struct TypeSignature { pub args: Vec<JavaType>, - pub ret: JavaType, + pub ret: ReturnType, } impl TypeSignature { @@ -105,7 +139,7 @@ impl fmt::Display for TypeSignature { } } -fn parse_primitive<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S> +fn parse_primitive<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Primitive, S> where S::Error: ParseError<char, S::Range, S::Position>, { @@ -128,7 +162,6 @@ where .or(long) .or(short) .or(void)) - .map(JavaType::Primitive) .parse_stream(input) .into() } @@ -160,6 +193,7 @@ where S::Error: ParseError<char, S::Range, S::Position>, { parser(parse_primitive) + .map(JavaType::Primitive) .or(parser(parse_array)) .or(parser(parse_object)) .or(parser(parse_sig)) @@ -167,6 +201,18 @@ where .into() } +fn parse_return<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<ReturnType, S> +where + S::Error: ParseError<char, S::Range, S::Position>, +{ + parser(parse_primitive) + .map(ReturnType::Primitive) + .or(parser(parse_array).map(|_| ReturnType::Array)) + .or(parser(parse_object).map(|_| ReturnType::Object)) + .parse_stream(input) + .into() +} + fn parse_args<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Vec<JavaType>, S> where S::Error: ParseError<char, S::Range, S::Position>, @@ -180,7 +226,7 @@ fn parse_sig<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, where S::Error: ParseError<char, S::Range, S::Position>, { - (parser(parse_args), parser(parse_type)) + (parser(parse_args), parser(parse_return)) .map(|(a, r)| TypeSignature { args: a, ret: r }) .map(|sig| JavaType::Method(Box::new(sig))) .parse_stream(input) @@ -196,7 +242,8 @@ mod test { let inputs = [ "(Ljava/lang/String;I)V", "[Lherp;", - "(IBVZ)Ljava/lang/String;", + // fails because the return type does not contain the class name: "(IBVZ)L;" + // "(IBVZ)Ljava/lang/String;", ]; for each in inputs.iter() { diff --git a/src/wrapper/strings/java_str.rs b/src/wrapper/strings/java_str.rs index 2829ce6..f964df2 100644 --- a/src/wrapper/strings/java_str.rs +++ b/src/wrapper/strings/java_str.rs @@ -49,7 +49,7 @@ impl<'a: 'b, 'b: 'c, 'c> From<&'c JavaStr<'a, 'b>> for &'c JNIStr { impl<'a: 'b, 'b: 'c, 'c> From<&'c JavaStr<'a, 'b>> for Cow<'c, str> { fn from(other: &'c JavaStr) -> Cow<'c, str> { - let jni_str: &JNIStr = &*other; + let jni_str: &JNIStr = other; jni_str.into() } } @@ -63,7 +63,7 @@ impl<'a: 'b, 'b> From<JavaStr<'a, 'b>> for String { impl<'a: 'b, 'b> Drop for JavaStr<'a, 'b> { fn drop(&mut self) { - match self.env.release_string_utf_chars(self.obj, self.internal) { + match unsafe { self.env.release_string_utf_chars(self.obj, self.internal) } { Ok(()) => {} Err(e) => warn!("error dropping java str: {}", e), } diff --git a/test_profile b/test_profile index a9c4e52..61dc8c8 100755..100644 --- a/test_profile +++ b/test_profile @@ -18,3 +18,7 @@ fi LIBJVM_PATH="$(find "${JAVA_HOME}" -type f -name "${LIB_NAME}.*" -print0 -quit | xargs -0 -n1 dirname)" export LD_LIBRARY_PATH="${LIBJVM_PATH}" + +# on macOS, cargo use DYLD_FALLBACK_LIBRARY_PATH to locate dynamic libraries. +# See https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths +export DYLD_FALLBACK_LIBRARY_PATH="${LIBJVM_PATH}":$DYLD_FALLBACK_LIBRARY_PATH diff --git a/tests/java_integers.rs b/tests/java_integers.rs index b4b2ed6..0d7c6fb 100644 --- a/tests/java_integers.rs +++ b/tests/java_integers.rs @@ -16,11 +16,9 @@ fn test_java_integers() { let integer_value = env.new_object("java/lang/Integer", "(I)V", &[JValue::Int(value)])?; - let values_array = JObject::from(env.new_object_array( - array_length, - "java/lang/Integer", - integer_value, - )?); + let values_array = + env.new_object_array(array_length, "java/lang/Integer", integer_value)?; + let values_array = unsafe { JObject::from_raw(values_array) }; let result = env .call_static_method( diff --git a/tests/jni_api.rs b/tests/jni_api.rs index 5d35719..050f8ec 100644 --- a/tests/jni_api.rs +++ b/tests/jni_api.rs @@ -305,7 +305,7 @@ pub fn java_byte_array_from_slice() { 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, JObject::from(java_array)); + let obj = AutoLocal::new(&env, unsafe { JObject::from_raw(java_array) }); assert!(!obj.as_obj().is_null()); let mut res: [i8; 3] = [0; 3]; @@ -333,8 +333,12 @@ macro_rules! test_get_array_elements { // Use a scope to test Drop { // Get byte array elements auto wrapper - let auto_ptr: AutoArray<$jni_type> = - env.$jni_get(java_array, ReleaseMode::CopyBack).unwrap(); + let auto_ptr: AutoArray<$jni_type> = { + // 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() + }; // Check array size assert_eq!(auto_ptr.size().unwrap(), 2); @@ -572,19 +576,34 @@ pub fn get_object_class_null_arg() { #[test] pub fn new_direct_byte_buffer() { let env = attach_current_thread(); - let mut vec: Vec<u8> = vec![0, 1, 2, 3]; - let buf = vec.as_mut_slice(); - let result = env.new_direct_byte_buffer(buf); + let vec: Vec<u8> = vec![0, 1, 2, 3]; + let (addr, len) = { + // (would use buf.into_raw_parts() on nightly) + let buf = vec.leak(); + (buf.as_mut_ptr(), buf.len()) + }; + let result = unsafe { env.new_direct_byte_buffer(addr, len) }; assert!(result.is_ok()); assert!(!result.unwrap().is_null()); } #[test] +pub fn new_direct_byte_buffer_invalid_addr() { + let 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 vec: Vec<u8> = vec![0, 1, 2, 3]; - let buf = vec.as_mut_slice(); - let result = env.new_direct_byte_buffer(buf).unwrap(); + let vec: Vec<u8> = vec![0, 1, 2, 3]; + let (addr, len) = { + // (would use buf.into_raw_parts() on nightly) + let buf = vec.leak(); + (buf.as_mut_ptr(), buf.len()) + }; + let result = unsafe { env.new_direct_byte_buffer(addr, len) }.unwrap(); assert!(!result.is_null()); let capacity = env.get_direct_buffer_capacity(result).unwrap(); @@ -594,21 +613,32 @@ pub fn get_direct_buffer_capacity_ok() { #[test] pub fn get_direct_buffer_capacity_wrong_arg() { let env = attach_current_thread(); - let wrong_obj = JByteBuffer::from(env.new_string("wrong").unwrap().into_inner()); + let wrong_obj = unsafe { JByteBuffer::from_raw(env.new_string("wrong").unwrap().into_raw()) }; 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()); + assert!(result.is_err()); +} + +#[test] pub fn get_direct_buffer_address_ok() { let env = attach_current_thread(); - let mut vec: Vec<u8> = vec![0, 1, 2, 3]; - let buf = vec.as_mut_slice(); - let result = env.new_direct_byte_buffer(buf).unwrap(); + let vec: Vec<u8> = vec![0, 1, 2, 3]; + let (addr, len) = { + // (would use buf.into_raw_parts() on nightly) + let buf = vec.leak(); + (buf.as_mut_ptr(), buf.len()) + }; + 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(); - assert_eq!(buf, dest_buffer); + assert_eq!(addr, dest_buffer); } #[test] diff --git a/tests/util/mod.rs b/tests/util/mod.rs index 511550d..fb07386 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -79,7 +79,7 @@ pub fn print_exception(env: &JNIEnv) { #[allow(dead_code)] pub fn unwrap<T>(env: &JNIEnv, res: Result<T>) -> T { res.unwrap_or_else(|e| { - print_exception(&env); + print_exception(env); panic!("{:#?}", e); }) } |