aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-02-04 13:14:22 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-02-04 13:14:22 +0000
commit25b0464be676fc4675d3b2a553e90675b31bc33d (patch)
treeaf62ef3b254156b04b01b4998d81b79144b2ce72
parentff9d1c5e44fa55a18b0e5bec12529e8558d4f40c (diff)
parent05c80856628da27713cf29f6f4bbd73fbc2f1078 (diff)
downloadjni-android13-mainline-media-release.tar.gz
Snap for 9564107 from 05c80856628da27713cf29f6f4bbd73fbc2f1078 to mainline-media-releaseaml_med_331911000aml_med_331712010aml_med_331612000android13-mainline-media-release
Change-Id: Iaa5902661369031bd891066406d60a2ecae28026
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md12
-rw-r--r--.github/dependabot.yml12
-rw-r--r--.github/workflows/ci.yml99
-rw-r--r--.github/workflows/run_invocation_tests.sh8
-rw-r--r--.github/workflows/run_windows_invocation_tests.ps13
-rw-r--r--.github/workflows/shellcheck.sh9
-rw-r--r--.gitignore12
-rw-r--r--.vscode/settings.json5
-rw-r--r--Android.bp6
-rw-r--r--CHANGELOG.md30
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Cargo.toml21
-rw-r--r--Cargo.toml.orig2
-rw-r--r--METADATA15
-rw-r--r--README.md2
-rw-r--r--benches/api_calls.rs128
-rw-r--r--build.rs3
-rw-r--r--src/lib.rs7
-rw-r--r--src/wrapper/descriptors/exception_desc.rs2
-rw-r--r--src/wrapper/descriptors/field_desc.rs8
-rw-r--r--src/wrapper/descriptors/method_desc.rs12
-rw-r--r--src/wrapper/java_vm/vm.rs31
-rw-r--r--src/wrapper/jnienv.rs552
-rw-r--r--src/wrapper/macros.rs5
-rw-r--r--src/wrapper/objects/auto_array.rs16
-rw-r--r--src/wrapper/objects/global_ref.rs7
-rw-r--r--src/wrapper/objects/jbytebuffer.rs34
-rw-r--r--src/wrapper/objects/jclass.rs32
-rw-r--r--src/wrapper/objects/jfieldid.rs53
-rw-r--r--src/wrapper/objects/jlist.rs38
-rw-r--r--src/wrapper/objects/jmap.rs59
-rw-r--r--src/wrapper/objects/jmethodid.rs53
-rw-r--r--src/wrapper/objects/jobject.rs31
-rw-r--r--src/wrapper/objects/jstaticfieldid.rs55
-rw-r--r--src/wrapper/objects/jstaticmethodid.rs56
-rw-r--r--src/wrapper/objects/jstring.rs32
-rw-r--r--src/wrapper/objects/jthrowable.rs32
-rw-r--r--src/wrapper/objects/release_mode.rs2
-rw-r--r--src/wrapper/signature.rs57
-rw-r--r--src/wrapper/strings/java_str.rs4
-rw-r--r--[-rwxr-xr-x]test_profile4
-rw-r--r--tests/java_integers.rs8
-rw-r--r--tests/jni_api.rs58
-rw-r--r--tests/util/mod.rs2
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
diff --git a/Android.bp b/Android.bp
index e397f6e..452023f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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
diff --git a/Cargo.toml b/Cargo.toml
index 717cf5e..2441b4a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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]
diff --git a/METADATA b/METADATA
index cac09e3..f891cf7 100644
--- a/METADATA
+++ b/METADATA
@@ -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
}
}
diff --git a/README.md b/README.md
index 438c1e0..53a8508 100644
--- a/README.md
+++ b/README.md
@@ -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]
diff --git a/build.rs b/build.rs
index 4d06057..05419f1 100644
--- a/build.rs
+++ b/build.rs
@@ -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");
diff --git a/src/lib.rs b/src/lib.rs
index 6dd04be..dff0c20 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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);
})
}