From d8010e71ab77cdb1e12974fcf7c1bd4c0922fdc1 Mon Sep 17 00:00:00 2001 From: James Farrell Date: Wed, 8 May 2024 21:55:29 +0000 Subject: Update Android.bp by running cargo_embargo Test: ran cargo_embargo Change-Id: I9fd678a5dabe977d262f15a97984f0100bcac1c6 --- Android.bp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Android.bp b/Android.bp index 0b2e481..3fb81b3 100644 --- a/Android.bp +++ b/Android.bp @@ -1,5 +1,7 @@ // This file is generated by cargo_embargo. -// Do not modify this file as changes will be overridden on upgrade. +// Do not modify this file after the first "rust_*" or "genrule" module +// because the changes will be overridden on upgrade. +// Content before the first "rust_*" or "genrule" module is preserved. package { default_applicable_licenses: ["external_rust_crates_jni_license"], -- cgit v1.2.3 From 27d0b7d2a286a61f3b2fd06fc3c37289c30ee449 Mon Sep 17 00:00:00 2001 From: Matthew Maurer Date: Tue, 14 May 2024 19:17:33 +0000 Subject: Create temporary legacy JNI version for migration Test: TH Change-Id: I08826e8c251fbaefa63e1b8cb2a4e43dcbf52c14 --- 0.20.0/Android.bp | 24 + 0.20.0/CHANGELOG.md | 264 +++ 0.20.0/CODE_OF_CONDUCT.md | 73 + 0.20.0/CONTRIBUTING.md | 165 ++ 0.20.0/Cargo.toml | 55 + 0.20.0/Cargo.toml.orig | 38 + 0.20.0/LICENSE | 1 + 0.20.0/LICENSE-APACHE | 201 ++ 0.20.0/LICENSE-MIT | 21 + 0.20.0/METADATA | 23 + 0.20.0/MODULE_LICENSE_APACHE2 | 0 0.20.0/OWNERS | 1 + 0.20.0/README.md | 34 + 0.20.0/benches/api_calls.rs | 366 +++ 0.20.0/build.rs | 109 + 0.20.0/cargo_embargo.json | 8 + 0.20.0/clippy.toml | 2 + 0.20.0/example/HelloWorld.h | 69 + 0.20.0/example/HelloWorld.java | 49 + 0.20.0/example/Makefile | 10 + 0.20.0/src/lib.rs | 231 ++ 0.20.0/src/sys.rs | 1 + 0.20.0/src/wrapper/descriptors/class_desc.rs | 39 + 0.20.0/src/wrapper/descriptors/desc.rs | 17 + 0.20.0/src/wrapper/descriptors/exception_desc.rs | 47 + 0.20.0/src/wrapper/descriptors/field_desc.rs | 29 + 0.20.0/src/wrapper/descriptors/method_desc.rs | 39 + 0.20.0/src/wrapper/descriptors/mod.rs | 14 + 0.20.0/src/wrapper/errors.rs | 89 + 0.20.0/src/wrapper/executor.rs | 96 + 0.20.0/src/wrapper/java_vm/init_args.rs | 138 ++ 0.20.0/src/wrapper/java_vm/mod.rs | 7 + 0.20.0/src/wrapper/java_vm/vm.rs | 455 ++++ 0.20.0/src/wrapper/jnienv.rs | 2334 ++++++++++++++++++++ 0.20.0/src/wrapper/macros.rs | 142 ++ 0.20.0/src/wrapper/objects/auto_array.rs | 137 ++ 0.20.0/src/wrapper/objects/auto_local.rs | 80 + 0.20.0/src/wrapper/objects/auto_primitive_array.rs | 90 + 0.20.0/src/wrapper/objects/global_ref.rs | 107 + 0.20.0/src/wrapper/objects/jbytebuffer.rs | 49 + 0.20.0/src/wrapper/objects/jclass.rs | 53 + 0.20.0/src/wrapper/objects/jfieldid.rs | 48 + 0.20.0/src/wrapper/objects/jlist.rs | 202 ++ 0.20.0/src/wrapper/objects/jmap.rs | 246 +++ 0.20.0/src/wrapper/objects/jmethodid.rs | 48 + 0.20.0/src/wrapper/objects/jobject.rs | 56 + 0.20.0/src/wrapper/objects/jstaticfieldid.rs | 48 + 0.20.0/src/wrapper/objects/jstaticmethodid.rs | 48 + 0.20.0/src/wrapper/objects/jstring.rs | 52 + 0.20.0/src/wrapper/objects/jthrowable.rs | 52 + 0.20.0/src/wrapper/objects/jvalue.rs | 353 +++ 0.20.0/src/wrapper/objects/mod.rs | 56 + 0.20.0/src/wrapper/objects/release_mode.rs | 15 + 0.20.0/src/wrapper/signature.rs | 274 +++ 0.20.0/src/wrapper/strings/ffi_str.rs | 109 + 0.20.0/src/wrapper/strings/java_str.rs | 71 + 0.20.0/src/wrapper/strings/mod.rs | 6 + 0.20.0/src/wrapper/version.rs | 43 + 0.20.0/test_profile | 24 + 0.20.0/tests/executor.rs | 75 + 0.20.0/tests/executor_nested_attach.rs | 62 + 0.20.0/tests/java_integers.rs | 41 + 0.20.0/tests/jmap.rs | 41 + 0.20.0/tests/jni_api.rs | 958 ++++++++ 0.20.0/tests/jni_global_refs.rs | 80 + 0.20.0/tests/threads_attach_guard.rs | 18 + 0.20.0/tests/threads_detach.rs | 19 + 0.20.0/tests/threads_detach_daemon.rs | 19 + 0.20.0/tests/threads_explicit_detach.rs | 17 + 0.20.0/tests/threads_explicit_detach_daemon.rs | 17 + 0.20.0/tests/threads_explicit_detach_permanent.rs | 17 + 0.20.0/tests/threads_nested_attach_daemon.rs | 48 + 0.20.0/tests/threads_nested_attach_guard.rs | 54 + 0.20.0/tests/threads_nested_attach_permanently.rs | 49 + 0.20.0/tests/util/example_proxy.rs | 52 + 0.20.0/tests/util/mod.rs | 85 + 76 files changed, 9110 insertions(+) create mode 100644 0.20.0/Android.bp create mode 100644 0.20.0/CHANGELOG.md create mode 100644 0.20.0/CODE_OF_CONDUCT.md create mode 100644 0.20.0/CONTRIBUTING.md create mode 100644 0.20.0/Cargo.toml create mode 100644 0.20.0/Cargo.toml.orig create mode 120000 0.20.0/LICENSE create mode 100644 0.20.0/LICENSE-APACHE create mode 100644 0.20.0/LICENSE-MIT create mode 100644 0.20.0/METADATA create mode 100644 0.20.0/MODULE_LICENSE_APACHE2 create mode 100644 0.20.0/OWNERS create mode 100644 0.20.0/README.md create mode 100644 0.20.0/benches/api_calls.rs create mode 100644 0.20.0/build.rs create mode 100644 0.20.0/cargo_embargo.json create mode 100644 0.20.0/clippy.toml create mode 100644 0.20.0/example/HelloWorld.h create mode 100644 0.20.0/example/HelloWorld.java create mode 100644 0.20.0/example/Makefile create mode 100644 0.20.0/src/lib.rs create mode 100644 0.20.0/src/sys.rs create mode 100644 0.20.0/src/wrapper/descriptors/class_desc.rs create mode 100644 0.20.0/src/wrapper/descriptors/desc.rs create mode 100644 0.20.0/src/wrapper/descriptors/exception_desc.rs create mode 100644 0.20.0/src/wrapper/descriptors/field_desc.rs create mode 100644 0.20.0/src/wrapper/descriptors/method_desc.rs create mode 100644 0.20.0/src/wrapper/descriptors/mod.rs create mode 100644 0.20.0/src/wrapper/errors.rs create mode 100644 0.20.0/src/wrapper/executor.rs create mode 100644 0.20.0/src/wrapper/java_vm/init_args.rs create mode 100644 0.20.0/src/wrapper/java_vm/mod.rs create mode 100644 0.20.0/src/wrapper/java_vm/vm.rs create mode 100644 0.20.0/src/wrapper/jnienv.rs create mode 100644 0.20.0/src/wrapper/macros.rs create mode 100644 0.20.0/src/wrapper/objects/auto_array.rs create mode 100644 0.20.0/src/wrapper/objects/auto_local.rs create mode 100644 0.20.0/src/wrapper/objects/auto_primitive_array.rs create mode 100644 0.20.0/src/wrapper/objects/global_ref.rs create mode 100644 0.20.0/src/wrapper/objects/jbytebuffer.rs create mode 100644 0.20.0/src/wrapper/objects/jclass.rs create mode 100644 0.20.0/src/wrapper/objects/jfieldid.rs create mode 100644 0.20.0/src/wrapper/objects/jlist.rs create mode 100644 0.20.0/src/wrapper/objects/jmap.rs create mode 100644 0.20.0/src/wrapper/objects/jmethodid.rs create mode 100644 0.20.0/src/wrapper/objects/jobject.rs create mode 100644 0.20.0/src/wrapper/objects/jstaticfieldid.rs create mode 100644 0.20.0/src/wrapper/objects/jstaticmethodid.rs create mode 100644 0.20.0/src/wrapper/objects/jstring.rs create mode 100644 0.20.0/src/wrapper/objects/jthrowable.rs create mode 100644 0.20.0/src/wrapper/objects/jvalue.rs create mode 100644 0.20.0/src/wrapper/objects/mod.rs create mode 100644 0.20.0/src/wrapper/objects/release_mode.rs create mode 100644 0.20.0/src/wrapper/signature.rs create mode 100644 0.20.0/src/wrapper/strings/ffi_str.rs create mode 100644 0.20.0/src/wrapper/strings/java_str.rs create mode 100644 0.20.0/src/wrapper/strings/mod.rs create mode 100644 0.20.0/src/wrapper/version.rs create mode 100644 0.20.0/test_profile create mode 100644 0.20.0/tests/executor.rs create mode 100644 0.20.0/tests/executor_nested_attach.rs create mode 100644 0.20.0/tests/java_integers.rs create mode 100644 0.20.0/tests/jmap.rs create mode 100644 0.20.0/tests/jni_api.rs create mode 100644 0.20.0/tests/jni_global_refs.rs create mode 100644 0.20.0/tests/threads_attach_guard.rs create mode 100644 0.20.0/tests/threads_detach.rs create mode 100644 0.20.0/tests/threads_detach_daemon.rs create mode 100644 0.20.0/tests/threads_explicit_detach.rs create mode 100644 0.20.0/tests/threads_explicit_detach_daemon.rs create mode 100644 0.20.0/tests/threads_explicit_detach_permanent.rs create mode 100644 0.20.0/tests/threads_nested_attach_daemon.rs create mode 100644 0.20.0/tests/threads_nested_attach_guard.rs create mode 100644 0.20.0/tests/threads_nested_attach_permanently.rs create mode 100644 0.20.0/tests/util/example_proxy.rs create mode 100644 0.20.0/tests/util/mod.rs diff --git a/0.20.0/Android.bp b/0.20.0/Android.bp new file mode 100644 index 0000000..29ed79a --- /dev/null +++ b/0.20.0/Android.bp @@ -0,0 +1,24 @@ +rust_library { + name: "libjni_legacy", + host_supported: true, + crate_name: "jni", + cargo_env_compat: true, + cargo_pkg_version: "0.20.0", + srcs: ["src/lib.rs"], + edition: "2018", + features: ["default"], + rustlibs: [ + "libcesu8", + "libcombine", + "libjni_sys", + "liblog_rust", + "libthiserror", + ], + apex_available: [ + "//apex_available:platform", + "com.android.uwb", + ], + product_available: true, + vendor_available: true, + min_sdk_version: "29", +} diff --git a/0.20.0/CHANGELOG.md b/0.20.0/CHANGELOG.md new file mode 100644 index 0000000..aeb692b --- /dev/null +++ b/0.20.0/CHANGELOG.md @@ -0,0 +1,264 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + + + +## [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 +- `AutoArray` and generic `get_array_elements()`, along with `get__array_elements` helpers. (#287) +- `size()` method to `AutoArray` and `AutoPrimitiveArray`. (#278 / #287) +- `discard()` method to `AutoArray` and `AutoPrimitiveArray`. (#275 / #287) + +### Changed +- Removed AutoPrimitiveArray::commit(). (#290) +- `AutoByte/PrimitiveArray.commit()` now returns `Result`. (#275) +- Removed methods get/release/commit_byte/primitive_array_{elements|critical}. (#281) +- Renamed methods get_auto_byte/long/primitive_array_{elements|critical} to + get_byte/long/primitive_array_{elements|critical}. (#281) + +## [0.18.0] — 2020-09-23 + +### Added +- `JNIEnv#define_unnamed_class` function that allows loading a class without + specifying its name. The name is inferred from the class data. (#246) +- `SetStaticField`. (#248) +- `TryFrom` for types inside JValue variants (#264). +- Implemented Copy for JNIEnv (#255). +- `repr(transparent)` attribute to JavaVM struct (#259) + +### Changed +- Switch from `error-chain` to `thiserror`, making all errors `Send`. Also, support all JNI errors + in the `jni_error_code_to_result` function and add more information to the `InvalidArgList` + error. ([#242](https://github.com/jni-rs/jni-rs/pull/242)) + +## [0.17.0] — 2020-06-30 + +### Added +- Get/ReleaseByteArrayElements, and Get/ReleasePrimitiveArrayCritical. (#237) + +## [0.16.0] — 2020-02-28 + +### Fixed +- Java VM instantiation with some MacOS configurations. (#220, #229, #230). + +## [0.15.0] — 2020-02-28 + +### Added +- Ability to pass object wrappers that are convertible to `JObject` as arguments to the majority + of JNIEnv methods without explicit conversion. (#213) +- `JNIEnv#is_same_object` implementation. (#213) +- `JNIEnv#register_native_methods`. (#214) +- Conversion from `Into` to `JValue::Object`. + +### Fixed +- Passing `null` as class loader to `define_class` method now allowed according + to the JNI specification. (#225) + +## [0.14.0] — 2019-10-31 + +### Changed +- Relaxed some lifetime restrictions in JNIEnv to support the case when + method, field ids; and global references to classes + have a different (larger) lifetime than JNIEnv. (#209) + +## [0.13.1] — 2019-08-22 + +### Changed +- Various documentation improvements. + +## [0.13.0] — 2019-07-05 + +0.13 brings major improvements in thread management, allowing to attach the native threads +permanently and safely; `Executor` for extra convenience and safety; and other +improvements and fixes. + +:warning: If your code attaches native threads — make sure to check the updated documentation +of [JavaVM](https://docs.rs/jni/0.13.0/jni/struct.JavaVM.html) to learn about the new features! + +### Added +- `JavaVM::attach_current_thread_permanently` method, which attaches the current + thread and detaches it when the thread finishes. Daemon threads attached + with `JavaVM::attach_current_thread_as_daemon` also automatically detach themselves + when finished. The number of currently attached threads may be acquired using + `JavaVM::threads_attached` method. (#179, #180) +- `Executor` — a simple thread attachment manager which helps to safely + execute a closure in attached thread context and to automatically free + created local references at closure exit. (#186) + +### Changed +- The default JNI API version in `InitArgsBuilder` from V1 to V8. (#178) +- Extended the lifetimes of `AutoLocal` to make it more flexible. (#190) +- Default exception type from checked `java.lang.Exception` to unchecked `java.lang.RuntimeException`. + It is used implicitly when `JNIEnv#throw` is invoked with exception message: + `env.throw("Exception message")`; however, for efficiency reasons, it is recommended + to specify the exception type explicitly *and* use `throw_new`: + `env.throw_new(exception_type, "Exception message")`. (#194) + +### Fixed +- Native threads attached with `JavaVM::attach_current_thread_as_daemon` now automatically detach + themselves on exit, preventing Java Thread leaks. (#179) +- Local reference leaks in `JList`, `JMap` and `JMapIter`. (#190, #191) + +## [0.12.3] + +### Added +- `From` implementation for `JValue` (#173) +- `Debug` trait for InitArgsBuilder. (#175) +- `InitArgsBuilder#options` returning the collected JVM options. (#177) + +## [0.12.2] + +### Changed +- Updated documentation of GetXArrayRegion methods (#169) +- Improved ABI compatibility on various platforms (#170) + +## [0.12.1] + +This release does not bring code changes. + +### Changed +- Updated project documentation. + +## [0.12.0] + +### Changed +- `JString`, `JMap` and `JavaStr` and their respective iterators now require an extra lifetime so + that they can now work with `&'b JNIEnv<'a>`, where `'a: 'b`. + +## [0.11.0] + +### Highlights +This release brings various improvements and fixes, outlined below. The most notable changes are: +- `null` is no longer represented as an `Err` with error kind `NullPtr` if it is a value of some + nullable Java reference (not an indication of an error). Related issues: #136, #148, #163. +- `unsafe` methods, providing a low-level API similar to JNI, has been marked safe and renamed + to have `_unchecked` suffix. Such methods can be used to implement caching of class references + and method IDs to improve performance in loops and frequently called Java callbacks. + If you have such, check out [the docs][unchecked-docs] and [one of early usages][cache-exonum] + of this feature. + +[unchecked-docs]: https://docs.rs/jni/0.11.0/jni/struct.JNIEnv.html +[cache-exonum]: https://github.com/exonum/exonum-java-binding/blob/affa85c026c1870b502725b291822c00f199745d/exonum-java-binding/core/rust/src/utils/jni_cache.rs#L40 + +### Added +- Invocation API support on Windows and AppVeyor CI (#149) + +### Changed +- `push_local_frame`, `delete_global_ref` and `release_string_utf_chars` +no longer check for exceptions as they are +[safe](https://docs.oracle.com/javase/10/docs/specs/jni/design.html#exception-handling) +to call if there is a pending exception (#124): + - `push_local_frame` will now work in case of pending exceptions — as + the spec requires; and fail in case of allocation errors + - `delete_global_ref` and `release_string_utf_chars` won't print incorrect + log messages + +- Rename some macros to better express their intent (see #123): + - Rename `jni_call` to `jni_non_null_call` as it checks the return value + to be non-null. + - Rename `jni_non_null_call` (which may return nulls) to `jni_non_void_call`. + +- A lot of public methods of `JNIEnv` have been marked as safe, all unsafe code + has been isolated inside internal macros. Methods with `_unsafe` suffixes have + been renamed and now have `_unchecked` suffixes (#140) + +- `from_str` method of the `JavaType` has been replaced by the `FromStr` + implementation + +- Implemented Sync for GlobalRef (#102). + +- Improvements in macro usage for JNI methods calls (#136): + - `call_static_method_unchecked` and `get_static_field_unchecked` methods are + allowed to return NULL object + - Added checking for pending exception to the `call_static_method_unchecked` + method (eliminated WARNING messages in log) + +- Further improvements in macro usage for JNI method calls (#150): + - The new_global_ref() and new_local_ref() functions are allowed to work with NULL objects according to specification. + - Fixed the family of functions new_direct_byte_buffer(), get_direct_buffer_address() and get_direct_buffer_capacity() + by adding checking for null and error codes. + - Increased tests coverage for JNIEnv functions. + +- Implemented Clone for JNIEnv (#147). + +- The get_superclass(), get_field_unchecked() and get_object_array_element() are allowed to return NULL object according + to the specification (#163). + +### Fixed +- The issue with early detaching of a thread by nested AttachGuard. (#139) + +## [0.10.2] + +### Added +- `JavaVM#get_java_vm_pointer` to retrieve a JavaVM pointer (#98) +- This changelog and other project documents (#106) + +### Changed +- The project is moved to an organization (#104) +- Updated versions of dependencies (#105) +- Improved project documents (#107) + +### Fixed +- Crate type of a shared library with native methods + must be `cdylib` (#100) + +## [0.10.1] +- No changes has been made to the Changelog until this release. + +[Unreleased]: https://github.com/jni-rs/jni-rs/compare/v0.20.0...HEAD +[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 +[0.16.0]: https://github.com/jni-rs/jni-rs/compare/v0.15.0...v0.16.0 +[0.15.0]: https://github.com/jni-rs/jni-rs/compare/v0.14.0...v0.15.0 +[0.14.0]: https://github.com/jni-rs/jni-rs/compare/v0.13.1...v0.14.0 +[0.13.1]: https://github.com/jni-rs/jni-rs/compare/v0.13.0...v0.13.1 +[0.13.0]: https://github.com/jni-rs/jni-rs/compare/v0.12.3...v0.13.0 +[0.12.3]: https://github.com/jni-rs/jni-rs/compare/v0.12.2...v0.12.3 +[0.12.2]: https://github.com/jni-rs/jni-rs/compare/v0.12.1...v0.12.2 +[0.12.1]: https://github.com/jni-rs/jni-rs/compare/v0.12.0...v0.12.1 +[0.12.0]: https://github.com/jni-rs/jni-rs/compare/v0.11.0...v0.12.0 +[0.11.0]: https://github.com/jni-rs/jni-rs/compare/v0.10.2...v0.11.0 +[0.10.2]: https://github.com/jni-rs/jni-rs/compare/v0.10.1...v0.10.2 +[0.10.1]: https://github.com/jni-rs/jni-rs/compare/v0.1...v0.10.1 diff --git a/0.20.0/CODE_OF_CONDUCT.md b/0.20.0/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..6fd4aee --- /dev/null +++ b/0.20.0/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Jni-rs Contributor Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, nationality, personal appearance, race, religion, or sexual identity +and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at ilya.bogdanov@xdev.re. The project +team will review and investigate all complaints, and will respond in a way that +it deems appropriate to the circumstances. The project team is obligated to +maintain confidentiality with regard to the reporter of an incident. Further +details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at [http://contributor-covenant.org/version/1/4][url]. + +[homepage]: http://contributor-covenant.org +[url]: http://contributor-covenant.org/version/1/4/ diff --git a/0.20.0/CONTRIBUTING.md b/0.20.0/CONTRIBUTING.md new file mode 100644 index 0000000..eea3908 --- /dev/null +++ b/0.20.0/CONTRIBUTING.md @@ -0,0 +1,165 @@ +# Jni-rs Contribution Guide + +Jni-rs is open to any contributions, whether +it is a feedback on existing features, a request for a new one, a bug report +or a pull request. This document describes how to work with this project: + +* how to [build](#how-to-build) it +* how to [test](#tests) it +* the [code style guidelines](#the-code-style) +* how to [submit an issue](#submitting-issues) +* how to [submit a PR](#submitting-pull-requests). + +## How to Build + +### System Dependencies + +You need to install the following dependencies: + +* [JDK 1.8+](http://jdk.java.net/10/). +* [Rust (latest stable)](https://www.rust-lang.org/). + +### Building + +To build `jni-rs`, simply run + +```$sh +cargo build +``` + +inside project root directory. You can also build the library in release mode by +adding `--release` flag. + +## Tests + +### Categories of Tests + +* Unit tests are typically placed at the bottom of their module file. + E.g. [unit tests of signature module](src/wrapper/signature.rs). Tests are wrapped + in private module with `test` attribute: + + ```rust + #[cfg(test)] + mod test { + use super::*; + + #[test] + fn first_test() { /* ... */ } + + // More tests... + } + ``` + +* Integration tests are in [tests directory](tests). They use the same pattern as + unit tests, but split into several files instead of private modules. + Integration tests use `jni-rs` as every other Rust application — by importing + library using `extern crate` keyword. + + ```rust + extern crate jni; + use jni::*; + ``` + + Integration tests typically require running a JVM, so you should add + `#![cfg(feature = "invocation")]` at the top of the file. You can use helper + methods from [util module](tests/util/mod.rs) to run JVM. + + Keep in mind, that only one JVM can be run per process. Therefore, tests that + need to launch it with different parameters have to be placed in different + source files. `Cargo` runs tests from different modules in parallel. + +* Benchmarks are in [benches](benches) directory. As integration tests, they + require launching a JVM from native. + +* Doc tests are rarely used, but they allow to efficiently test some functionality + by providing an example of its usage. Consult + [Rust documentation](https://doc.rust-lang.org/beta/rustdoc/documentation-tests.html) + for more info about writing these tests. + +### Running Tests + +#### Setup Environment + +As some tests need to launch a JVM, add a directory with JVM library that you want +to use to `LD_LIBRARY_PATH` on Linux/Mac or `PATH` environment variable on Windows. +On Linux/Mac, you can use the following script for this purpose: + +```$sh +source test_profile +``` + +#### Run Tests + +To run all tests, execute the following command: + +```$sh +cargo test --features=invocation +``` + +This command will run all tests, including unit, integration and documentation +tests. + +#### Run Benchmarks + +To run all benchmarks, execute the following command (nightly Rust required): + +```$sh +cargo +nightly bench --features=invocation +``` + +They might help you to see the performance differences between +two [API flavours][checked-unchecked]: checked and unchecked, and +pick the right one for your application. + +[checked-unchecked]: https://docs.rs/jni/0.20.0/jni/struct.JNIEnv.html#checked-and-unchecked-methods + +## The Code Style + +Rust code follows the [Rust style guide](https://github.com/rust-lang-nursery/fmt-rfcs/blob/master/guide/guide.md). +[`rustfmt`](https://github.com/rust-lang-nursery/rustfmt) enforces the code style. + +After installation, you can run it with + +```$sh +cargo fmt --all -- --check +``` + +Every public entry of the API must be thoroughly documented and covered with tests if it is possible. +You can use [JNI specification](https://docs.oracle.com/javase/10/docs/specs/jni/index.html) as +a reference for how to write detailed documentation. + +Do not forget to update project guides and tutorials along with documentation. + +To open local documentation of the project, you can use the following command: + +```$sh +cargo doc --open +``` + +## Submitting Issues + +Use Github Issues to submit an issue, whether it is a question, some feedback, a bug or a feature request: + +## Submitting Pull Requests + +Before starting to work on a PR, please submit an issue describing the intended changes. +Chances are — we are already working on something similar. If not — we can then offer some +help with the requirements, design, implementation or documentation. + +It’s fine to open a PR as soon as you need any feedback — ask any questions in the description. + +## License + +Licensed under either of + +* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + at your option. + +### Contributor License Agreement + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion is the work by you, as defined in the +Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/0.20.0/Cargo.toml b/0.20.0/Cargo.toml new file mode 100644 index 0000000..2441b4a --- /dev/null +++ b/0.20.0/Cargo.toml @@ -0,0 +1,55 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# 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. +# +# 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.20.0" +authors = ["Josh Chase "] +description = "Rust bindings to the JNI" +documentation = "https://docs.rs/jni" +readme = "README.md" +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" + +[dependencies.combine] +version = "4.1.0" + +[dependencies.jni-sys] +version = "0.3.0" + +[dependencies.log] +version = "0.4.4" + +[dependencies.thiserror] +version = "1.0.20" + +[dev-dependencies.lazy_static] +version = "1" + +[build-dependencies.walkdir] +version = "2" + +[features] +default = [] +invocation = [] diff --git a/0.20.0/Cargo.toml.orig b/0.20.0/Cargo.toml.orig new file mode 100644 index 0000000..913294d --- /dev/null +++ b/0.20.0/Cargo.toml.orig @@ -0,0 +1,38 @@ +[package] +authors = ["Josh Chase "] +description = "Rust bindings to the JNI" +documentation = "https://docs.rs/jni" +readme = "README.md" +keywords = [ + "ffi", + "jni", + "java", +] +categories = ["api-bindings"] +license = "MIT/Apache-2.0" +name = "jni" +repository = "https://github.com/jni-rs/jni-rs" +# ¡When bumping version please also update it in examples and documentation! +version = "0.20.0" +edition = "2018" + +[dependencies] +cesu8 = "1.1.0" +combine = "4.1.0" +jni-sys = "0.3.0" +log = "0.4.4" +thiserror = "1.0.20" + +[build-dependencies] +walkdir = "2" + +[dev-dependencies] +lazy_static = "1" + + +[features] +invocation = [] +default = [] + +[package.metadata.docs.rs] +features = ["invocation"] diff --git a/0.20.0/LICENSE b/0.20.0/LICENSE new file mode 120000 index 0000000..6b579aa --- /dev/null +++ b/0.20.0/LICENSE @@ -0,0 +1 @@ +LICENSE-APACHE \ No newline at end of file diff --git a/0.20.0/LICENSE-APACHE b/0.20.0/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/0.20.0/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/0.20.0/LICENSE-MIT b/0.20.0/LICENSE-MIT new file mode 100644 index 0000000..7432388 --- /dev/null +++ b/0.20.0/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Prevoty, Inc. and jni-rs contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/0.20.0/METADATA b/0.20.0/METADATA new file mode 100644 index 0000000..f891cf7 --- /dev/null +++ b/0.20.0/METADATA @@ -0,0 +1,23 @@ +# 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 { + url { + type: HOMEPAGE + value: "https://crates.io/crates/jni" + } + url { + type: ARCHIVE + value: "https://static.crates.io/crates/jni/jni-0.20.0.crate" + } + version: "0.20.0" + license_type: NOTICE + last_upgrade_date { + year: 2023 + month: 1 + day: 19 + } +} diff --git a/0.20.0/MODULE_LICENSE_APACHE2 b/0.20.0/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 diff --git a/0.20.0/OWNERS b/0.20.0/OWNERS new file mode 100644 index 0000000..5a2b844 --- /dev/null +++ b/0.20.0/OWNERS @@ -0,0 +1 @@ +include platform/prebuilts/rust:main:/OWNERS diff --git a/0.20.0/README.md b/0.20.0/README.md new file mode 100644 index 0000000..53a8508 --- /dev/null +++ b/0.20.0/README.md @@ -0,0 +1,34 @@ +[![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) + +# JNI Bindings for Rust + +[![Join the chat at https://gitter.im/jni-rs/Lobby](https://badges.gitter.im/jni-rs/Lobby.svg)](https://gitter.im/jni-rs/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +This project provides complete JNI bindings for Rust, allowing to: + +- Implement native Java methods for JVM and Android in Rust +- Call Java code from Rust +- Embed JVM in Rust applications and use any Java libraries + +See the [docs](https://docs.rs/jni) for more details. + +## Example +``` +cd example +make +``` + +## Contribution + +See the [Contribution Guide](CONTRIBUTING.md) for details. + + +## License + +Licensed under either of + + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +at your option. diff --git a/0.20.0/benches/api_calls.rs b/0.20.0/benches/api_calls.rs new file mode 100644 index 0000000..0ee0351 --- /dev/null +++ b/0.20.0/benches/api_calls.rs @@ -0,0 +1,366 @@ +#![cfg(feature = "invocation")] +#![feature(test)] + +extern crate test; + +use jni_sys::jvalue; +use lazy_static::lazy_static; + +use jni::{ + descriptors::Desc, + objects::{JClass, JMethodID, JObject, JStaticMethodID, JValue}, + 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 = ""; +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 { + x.abs() +} + +fn jni_abs_safe(env: &JNIEnv, x: jint) -> jint { + let x = JValue::from(x); + let v = env + .call_static_method(CLASS_MATH, METHOD_MATH_ABS, SIG_MATH_ABS, &[x]) + .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(); + 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, + x: jint, +) -> jint +where + C: Desc<'c, JClass<'c>>, +{ + let x = JValue::from(x); + let ret = ReturnType::Primitive(Primitive::Int); + let v = env + .call_static_method_unchecked(class, method_id, ret, &[x.into()]) + .unwrap(); + v.i().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_object_call_static_unchecked<'c, C>( + env: &JNIEnv<'c>, + class: C, + method_id: JStaticMethodID, + args: &[jvalue], +) -> JObject<'c> +where + C: Desc<'c, JClass<'c>>, +{ + let v = env + .call_static_method_unchecked(class, method_id, ReturnType::Object, args) + .unwrap(); + v.l().unwrap() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::rc::Rc; + use std::sync::Arc; + use test::{black_box, Bencher}; + + lazy_static! { + static ref VM: JavaVM = { + let args = InitArgsBuilder::new() + .version(JNIVersion::V8) + .build() + .unwrap(); + JavaVM::new(args).unwrap() + }; + } + + #[bench] + fn native_call_function(b: &mut Bencher) { + b.iter(|| { + let _ = native_abs(black_box(-3)); + }); + } + + #[bench] + 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_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_int_call_static_unchecked(&env, class, method_id, -3)); + } + + #[bench] + fn jni_call_static_abs_method_unchecked_jclass(b: &mut Bencher) { + let env = VM.attach_current_thread().unwrap(); + let class: JClass = CLASS_MATH.lookup(&env).unwrap(); + let method_id = env + .get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS) + .unwrap(); + + b.iter(|| jni_int_call_static_unchecked(&env, class, method_id, -3)); + } + + #[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_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)); + + b.iter(|| jni_hash_safe(&env, obj)); + } + + #[bench] + 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)); + let method_id = env + .get_method_id(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE) + .unwrap(); + + b.iter(|| jni_int_call_unchecked(&env, obj, method_id)); + } + + #[bench] + fn jni_new_object_str(b: &mut Bencher) { + let env = VM.attach_current_thread().unwrap(); + let class = CLASS_OBJECT; + + b.iter(|| { + let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap(); + env.delete_local_ref(obj).unwrap(); + }); + } + + #[bench] + fn jni_new_object_by_id_str(b: &mut Bencher) { + let env = VM.attach_current_thread().unwrap(); + let class = CLASS_OBJECT; + let ctor_id = env + .get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR) + .unwrap(); + + b.iter(|| { + let obj = env.new_object_unchecked(class, ctor_id, &[]).unwrap(); + env.delete_local_ref(obj).unwrap(); + }); + } + + #[bench] + fn jni_new_object_jclass(b: &mut Bencher) { + let env = VM.attach_current_thread().unwrap(); + let class: JClass = CLASS_OBJECT.lookup(&env).unwrap(); + + b.iter(|| { + let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap(); + env.delete_local_ref(obj).unwrap(); + }); + } + + #[bench] + fn jni_new_object_by_id_jclass(b: &mut Bencher) { + let env = VM.attach_current_thread().unwrap(); + let class: JClass = CLASS_OBJECT.lookup(&env).unwrap(); + let ctor_id = env + .get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR) + .unwrap(); + + b.iter(|| { + let obj = env.new_object_unchecked(class, ctor_id, &[]).unwrap(); + env.delete_local_ref(obj).unwrap(); + }); + } + + #[bench] + fn jni_new_global_ref(b: &mut Bencher) { + let env = VM.attach_current_thread().unwrap(); + let class = CLASS_OBJECT; + let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap(); + let global_ref = env.new_global_ref(obj).unwrap(); + env.delete_local_ref(obj).unwrap(); + + b.iter(|| env.new_global_ref(&global_ref).unwrap()); + } + + /// Checks the overhead of checking if exception has occurred. + /// + /// Such checks are required each time a Java method is called, but + /// can be omitted if we call a JNI method that returns an error status. + /// + /// See also #58 + #[bench] + fn jni_check_exception(b: &mut Bencher) { + let env = VM.attach_current_thread().unwrap(); + + b.iter(|| env.exception_check().unwrap()); + } + + #[bench] + fn jni_get_java_vm(b: &mut Bencher) { + let env = VM.attach_current_thread().unwrap(); + + b.iter(|| { + let _jvm = env.get_java_vm().unwrap(); + }); + } + + /// A benchmark measuring Push/PopLocalFrame overhead. + /// + /// Such operations are *required* if one attaches a long-running + /// native thread to the JVM because there is no 'return-from-native-method' + /// event when created local references are freed, hence no way for + /// the JVM to know that the local references are no longer used in the native code. + #[bench] + fn jni_noop_with_local_frame(b: &mut Bencher) { + // Local frame size actually doesn't matter since JVM does not preallocate anything. + const LOCAL_FRAME_SIZE: i32 = 32; + let env = VM.attach_current_thread().unwrap(); + b.iter(|| { + env.with_local_frame(LOCAL_FRAME_SIZE, || Ok(JObject::null())) + .unwrap() + }); + } + + /// A benchmark of the overhead of attaching and detaching a native thread. + /// + /// It is *huge* — two orders of magnitude higher than calling a single + /// Java method using unchecked APIs (e.g., `jni_call_static_unchecked`). + /// + #[bench] + fn jvm_noop_attach_detach_native_thread(b: &mut Bencher) { + b.iter(|| { + let env = VM.attach_current_thread().unwrap(); + black_box(&env); + }); + } + + #[bench] + fn native_arc(b: &mut Bencher) { + let env = VM.attach_current_thread().unwrap(); + let class = CLASS_OBJECT; + let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap(); + let global_ref = env.new_global_ref(obj).unwrap(); + env.delete_local_ref(obj).unwrap(); + let arc = Arc::new(global_ref); + + b.iter(|| { + let _ = black_box(Arc::clone(&arc)); + }); + } + + #[bench] + fn native_rc(b: &mut Bencher) { + let _env = VM.attach_current_thread().unwrap(); + let env = VM.get_env().unwrap(); + let rc = Rc::new(env); + + b.iter(|| { + let _ = black_box(Rc::clone(&rc)); + }); + } +} diff --git a/0.20.0/build.rs b/0.20.0/build.rs new file mode 100644 index 0000000..05419f1 --- /dev/null +++ b/0.20.0/build.rs @@ -0,0 +1,109 @@ +//! This build script is used to link with `jvm` dynamic library when +//! `invocation` feature is enabled. +//! +//! To do so, we look for `JAVA_HOME` environment variable. +//! * If it exists, we recursively search for `jvm` library file inside `JAVA_HOME` directory. +//! * If it is not set, we use the following commmand to find actual JVM home directory: +//! ```bash +//! java -XshowSettings:properties -version | grep 'java.home' +//! ``` +//! Then, we search for `jvm` as we have `JAVA_HOME`. +//! +//! On Windows, we also need to find `jvm.lib` file which is used while linking +//! at build time. This file is typically placed in `$JAVA_HOME/lib` directory. + +use std::{ + env, + path::{Path, PathBuf}, + process::Command, +}; + +#[cfg(target_os = "windows")] +const EXPECTED_JVM_FILENAME: &str = "jvm.dll"; +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" +))] +const EXPECTED_JVM_FILENAME: &str = "libjvm.so"; +#[cfg(target_os = "macos")] +const EXPECTED_JVM_FILENAME: &str = "libjli.dylib"; + +fn main() { + if cfg!(feature = "invocation") { + let java_home = match env::var("JAVA_HOME") { + Ok(java_home) => PathBuf::from(java_home), + Err(_) => find_java_home().expect( + "Failed to find Java home directory. \ + Try setting JAVA_HOME", + ), + }; + + let libjvm_path = + find_libjvm(&java_home).expect("Failed to find libjvm.so. Check JAVA_HOME"); + + println!("cargo:rustc-link-search=native={}", libjvm_path.display()); + + // On Windows, we need additional file called `jvm.lib` + // and placed inside `JAVA_HOME\lib` directory. + if cfg!(windows) { + let lib_path = java_home.join("lib"); + println!("cargo:rustc-link-search={}", lib_path.display()); + } + + println!("cargo:rerun-if-env-changed=JAVA_HOME"); + + // On MacOS, we need to link to libjli instead of libjvm as a workaround + // to a Java8 bug. See here for more information: + // https://bugs.openjdk.java.net/browse/JDK-7131356 + if env::var("CARGO_CFG_TARGET_OS").unwrap() == "macos" { + println!("cargo:rustc-link-lib=dylib=jli"); + } else { + println!("cargo:rustc-link-lib=dylib=jvm"); + } + } +} + +/// To find Java home directory, we call +/// `java -XshowSettings:properties -version` command and parse its output to +/// find the line `java.home=`. +fn find_java_home() -> Option { + Command::new("java") + .arg("-XshowSettings:properties") + .arg("-version") + .output() + .ok() + .and_then(|output| { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + for line in stdout.lines().chain(stderr.lines()) { + if line.contains("java.home") { + let pos = line.find('=').unwrap() + 1; + let path = line[pos..].trim(); + return Some(PathBuf::from(path)); + } + } + None + }) +} + +fn find_libjvm>(path: S) -> Option { + let walker = walkdir::WalkDir::new(path).follow_links(true); + + for entry in walker { + let entry = match entry { + Ok(entry) => entry, + Err(_e) => continue, + }; + + let file_name = entry.file_name().to_str().unwrap_or(""); + + if file_name == EXPECTED_JVM_FILENAME { + return entry.path().parent().map(Into::into); + } + } + + None +} diff --git a/0.20.0/cargo_embargo.json b/0.20.0/cargo_embargo.json new file mode 100644 index 0000000..9b3ad03 --- /dev/null +++ b/0.20.0/cargo_embargo.json @@ -0,0 +1,8 @@ +{ + "apex_available": [ + "//apex_available:platform", + "com.android.uwb" + ], + "min_sdk_version": "29", + "tests": true +} diff --git a/0.20.0/clippy.toml b/0.20.0/clippy.toml new file mode 100644 index 0000000..00aaff1 --- /dev/null +++ b/0.20.0/clippy.toml @@ -0,0 +1,2 @@ +# Temporary increased until https://github.com/jni-rs/jni-rs/issues/184 +cognitive-complexity-threshold = 120 diff --git a/0.20.0/example/HelloWorld.h b/0.20.0/example/HelloWorld.h new file mode 100644 index 0000000..b8bf1d4 --- /dev/null +++ b/0.20.0/example/HelloWorld.h @@ -0,0 +1,69 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class HelloWorld */ + +#ifndef _Included_HelloWorld +#define _Included_HelloWorld +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: HelloWorld + * Method: hello + * Signature: (Ljava/lang/String;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_HelloWorld_hello + (JNIEnv *, jclass, jstring); + +/* + * Class: HelloWorld + * Method: helloByte + * Signature: ([B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_HelloWorld_helloByte + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: HelloWorld + * Method: factAndCallMeBack + * Signature: (ILHelloWorld;)V + */ +JNIEXPORT void JNICALL Java_HelloWorld_factAndCallMeBack + (JNIEnv *, jclass, jint, jobject); + +/* + * Class: HelloWorld + * Method: counterNew + * Signature: (LHelloWorld;)J + */ +JNIEXPORT jlong JNICALL Java_HelloWorld_counterNew + (JNIEnv *, jclass, jobject); + +/* + * Class: HelloWorld + * Method: counterIncrement + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_HelloWorld_counterIncrement + (JNIEnv *, jclass, jlong); + +/* + * Class: HelloWorld + * Method: counterDestroy + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_HelloWorld_counterDestroy + (JNIEnv *, jclass, jlong); + +/* + * Class: HelloWorld + * Method: asyncComputation + * Signature: (LHelloWorld;)V + */ +JNIEXPORT void JNICALL Java_HelloWorld_asyncComputation + (JNIEnv *, jclass, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/0.20.0/example/HelloWorld.java b/0.20.0/example/HelloWorld.java new file mode 100644 index 0000000..42d755e --- /dev/null +++ b/0.20.0/example/HelloWorld.java @@ -0,0 +1,49 @@ +class HelloWorld { + private static native String hello(String input); + private static native byte[] helloByte(byte[] input); + private static native void factAndCallMeBack(int n, HelloWorld callback); + + private static native long counterNew(HelloWorld callback); + private static native void counterIncrement(long counter_ptr); + private static native void counterDestroy(long counter_ptr); + + private static native void asyncComputation(HelloWorld callback); + + static { + System.loadLibrary("mylib"); + } + + public static void main(String[] args) { + + String output = HelloWorld.hello("josh"); + System.out.println(output); + byte[] outputByte = HelloWorld.helloByte("byte".getBytes()); + System.out.println(outputByte); + + + HelloWorld.factAndCallMeBack(6, new HelloWorld()); + + long counter_ptr = counterNew(new HelloWorld()); + + for (int i = 0; i < 5; i++) { + counterIncrement(counter_ptr); + } + + counterDestroy(counter_ptr); + + System.out.println("Invoking asyncComputation (thread id = " + Thread.currentThread().getId() + ")"); + asyncComputation(new HelloWorld()); + } + + public void factCallback(int res) { + System.out.println("factCallback: res = " + res); + } + + public void counterCallback(int count) { + System.out.println("counterCallback: count = " + count); + } + + public void asyncCallback(int progress) { + System.out.println("asyncCallback: thread id = " + Thread.currentThread().getId() + ", progress = " + progress + "%"); + } +} diff --git a/0.20.0/example/Makefile b/0.20.0/example/Makefile new file mode 100644 index 0000000..15f72c6 --- /dev/null +++ b/0.20.0/example/Makefile @@ -0,0 +1,10 @@ +java_run: lib + javac HelloWorld.java && java -Djava.library.path=mylib/target/debug/ HelloWorld + +.PHONY: lib + +javah: + javah HelloWorld + +lib: + cd mylib && cargo build diff --git a/0.20.0/src/lib.rs b/0.20.0/src/lib.rs new file mode 100644 index 0000000..dff0c20 --- /dev/null +++ b/0.20.0/src/lib.rs @@ -0,0 +1,231 @@ +#![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 +//! +//! This crate provides a (mostly) safe way to implement methods in Java using +//! the JNI. Because who wants to *actually* write Java? +//! +//! ## Getting Started +//! +//! Naturally, any ffi-related project is going to require some code in both +//! languages that we're trying to make communicate. Java requires all native +//! methods to adhere to the Java Native Interface (JNI), so we first have to +//! define our function signature from Java, and then we can write Rust that +//! will adhere to it. +//! +//! ### The Java side +//! +//! First, you need a Java class definition. `HelloWorld.java`: +//! +//! ```java +//! class HelloWorld { +//! // This declares that the static `hello` method will be provided +//! // a native library. +//! private static native String hello(String input); +//! +//! static { +//! // This actually loads the shared object that we'll be creating. +//! // The actual location of the .so or .dll may differ based on your +//! // platform. +//! System.loadLibrary("mylib"); +//! } +//! +//! // The rest is just regular ol' Java! +//! public static void main(String[] args) { +//! String output = HelloWorld.hello("josh"); +//! System.out.println(output); +//! } +//! } +//! ``` +//! +//! Compile this to a class file with `javac HelloWorld.java`. +//! +//! Trying to run it now will give us the error `Exception in thread "main" +//! java.lang.UnsatisfiedLinkError: no mylib in java.library.path` since we +//! haven't written our native code yet. +//! +//! 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.java` and you'll get a `HelloWorld.h` output to your +//! directory. It should look something like this: +//! +//! ```c +//! /* DO NOT EDIT THIS FILE - it is machine generated */ +//! #include +//! /* Header for class HelloWorld */ +//! +//! #ifndef _Included_HelloWorld +//! #define _Included_HelloWorld +//! #ifdef __cplusplus +//! extern "C" { +//! #endif +//! /* +//! * Class: HelloWorld +//! * Method: hello +//! * Signature: (Ljava/lang/String;)Ljava/lang/String; +//! */ +//! JNIEXPORT jstring JNICALL Java_HelloWorld_hello +//! (JNIEnv *, jclass, jstring); +//! +//! #ifdef __cplusplus +//! } +//! #endif +//! #endif +//! ``` +//! +//! It's a C header, but luckily for us, the types will mostly match up. Let's +//! make our crate that's going to compile to our native library. +//! +//! ### The Rust side +//! +//! Create your crate with `cargo new mylib`. This will create a directory +//! `mylib` that has everything needed to build an basic crate with `cargo`. We +//! need to make a couple of changes to `Cargo.toml` before we do anything else. +//! +//! * Under `[dependencies]`, add `jni = "0.20.0"` +//! * Add a new `[lib]` section and under it, `crate_type = ["cdylib"]`. +//! +//! Now, if you run `cargo build` from inside the crate directory, you should +//! see a `libmylib.so` (if you're on linux) or a `libmylib.dylib` (if you are on OSX) in the `target/debug` +//! directory. +//! +//! The last thing we need to do is to define our exported method. Add this to +//! your crate's `src/lib.rs`: +//! +//! ```rust,ignore +//! // This is the interface to the JVM that we'll call the majority of our +//! // methods on. +//! use jni::JNIEnv; +//! +//! // These objects are what you should use as arguments to your native +//! // function. They carry extra lifetime information to prevent them escaping +//! // this context and getting used after being GC'd. +//! use jni::objects::{JClass, JString}; +//! +//! // This is just a pointer. We'll be returning it from our function. We +//! // can't return one of the objects with lifetime information because the +//! // lifetime checker won't let us. +//! use jni::sys::jstring; +//! +//! // This keeps Rust from "mangling" the name and making it unique for this +//! // crate. +//! #[no_mangle] +//! pub extern "system" fn Java_HelloWorld_hello(env: JNIEnv, +//! // This is the class that owns our static method. It's not going to be used, +//! // but still must be present to match the expected signature of a static +//! // native method. +//! class: JClass, +//! input: JString) +//! -> jstring { +//! // First, we have to get the string out of Java. Check out the `strings` +//! // module for more info on how this works. +//! let input: String = +//! env.get_string(input).expect("Couldn't get java string!").into(); +//! +//! // Then we have to create a new Java string to return. Again, more info +//! // in the `strings` module. +//! let output = env.new_string(format!("Hello, {}!", input)) +//! .expect("Couldn't create java string!"); +//! +//! // Finally, extract the raw pointer to return. +//! output.into_inner() +//! } +//! ``` +//! +//! Note that the type signature for our function is almost identical to the one +//! from the generated header, aside from our lifetime-carrying arguments. +//! +//! ### Final steps +//! +//! That's it! Build your crate and try to run your Java class again. +//! +//! ... Same error as before you say? Well that's because JVM is looking for +//! `mylib` in all the wrong places. This will differ by platform thanks to +//! different linker/loader semantics, but on Linux, you can simply `export +//! LD_LIBRARY_PATH=/path/to/mylib/target/debug`. Now, you should get the +//! expected output `Hello, josh!` from your Java class. +//! +//! ## Launching JVM from Rust +//! +//! It is possible to launch a JVM from a native process using the [Invocation API], provided +//! by [`JavaVM`](struct.JavaVM.html). +//! +//! ## See Also +//! +//! ### Examples +//! - [Example project][jni-rs-example] +//! - Our [integration tests][jni-rs-its] and [benchmarks][jni-rs-benches] +//! +//! ### JNI Documentation +//! - [Java Native Interface Specification][jni-spec] +//! - [JNI tips][jni-tips] — general tips on JNI development and some Android-specific +//! +//! ### Open-Source Users +//! - The Servo browser engine Android [port][users-servo] +//! - The Exonum framework [Java Binding][users-ejb] +//! - MaidSafe [Java Binding][users-maidsafe] +//! +//! ### Other Projects Simplifying Java and Rust Communication +//! - Consider [JNR][projects-jnr] if you just need to use a native library with C interface +//! - Watch OpenJDK [Project Panama][projects-panama] which aims to enable using native libraries +//! with no JNI code +//! - Consider [GraalVM][projects-graalvm] — a recently released VM that gives zero-cost +//! interoperability between various languages (including Java and [Rust][graalvm-rust] compiled +//! into LLVM-bitcode) +//! +//! [Invocation API]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html +//! [jni-spec]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/index.html +//! [jni-tips]: https://developer.android.com/training/articles/perf-jni +//! [jni-rs-example]: https://github.com/jni-rs/jni-rs/tree/master/example +//! [jni-rs-its]: https://github.com/jni-rs/jni-rs/tree/master/tests +//! [jni-rs-benches]: https://github.com/jni-rs/jni-rs/tree/master/benches +//! [users-servo]: https://github.com/servo/servo/tree/master/ports/libsimpleservo +//! [users-ejb]: https://github.com/exonum/exonum-java-binding/tree/master/exonum-java-binding/core/rust +//! [users-maidsafe]: https://github.com/maidsafe/safe_client_libs/tree/master/safe_app_jni +//! [projects-jnr]: https://github.com/jnr/jnr-ffi/ +//! [projects-graalvm]: http://www.graalvm.org/docs/why-graal/#for-java-programs +//! [graalvm-rust]: http://www.graalvm.org/docs/reference-manual/languages/llvm/#running-rust +//! [projects-panama]: https://jdk.java.net/panama/ + +/// `jni-sys` re-exports +pub mod sys; + +mod wrapper { + mod version; + pub use self::version::*; + + #[macro_use] + mod macros; + + /// Errors. Do you really need more explanation? + pub mod errors; + + /// Descriptors for classes and method IDs. + pub mod descriptors; + + /// Parser for java type signatures. + pub mod signature; + + /// Wrappers for object pointers returned from the JVM. + pub mod objects; + + /// String types for going to/from java strings. + pub mod strings; + + /// Actual communication with the JVM. + mod jnienv; + pub use self::jnienv::*; + + /// Java VM interface. + mod java_vm; + pub use self::java_vm::*; + + /// Optional thread attachment manager. + mod executor; + pub use self::executor::*; +} + +pub use wrapper::*; diff --git a/0.20.0/src/sys.rs b/0.20.0/src/sys.rs new file mode 100644 index 0000000..e1c7b74 --- /dev/null +++ b/0.20.0/src/sys.rs @@ -0,0 +1 @@ +pub use jni_sys::*; diff --git a/0.20.0/src/wrapper/descriptors/class_desc.rs b/0.20.0/src/wrapper/descriptors/class_desc.rs new file mode 100644 index 0000000..9b2e46b --- /dev/null +++ b/0.20.0/src/wrapper/descriptors/class_desc.rs @@ -0,0 +1,39 @@ +use crate::{ + descriptors::Desc, + errors::*, + objects::{AutoLocal, GlobalRef, JClass, JObject}, + strings::JNIString, + JNIEnv, +}; + +impl<'a, T> Desc<'a, JClass<'a>> for T +where + T: Into, +{ + fn lookup(self, env: &JNIEnv<'a>) -> Result> { + env.find_class(self) + } +} + +impl<'a, 'b> Desc<'a, JClass<'a>> for JObject<'b> { + fn lookup(self, env: &JNIEnv<'a>) -> Result> { + env.get_object_class(self) + } +} + +/// This conversion assumes that the `GlobalRef` is a pointer to a class object. +impl<'a, 'b> Desc<'a, JClass<'b>> for &'b GlobalRef { + fn lookup(self, _: &JNIEnv<'a>) -> Result> { + Ok(self.as_obj().into()) + } +} + +/// This conversion assumes that the `AutoLocal` is a pointer to a class object. +impl<'a, 'b, 'c> Desc<'a, JClass<'b>> for &'b AutoLocal<'c, '_> +where + 'c: 'b, +{ + fn lookup(self, _: &JNIEnv<'a>) -> Result> { + Ok(self.as_obj().into()) + } +} diff --git a/0.20.0/src/wrapper/descriptors/desc.rs b/0.20.0/src/wrapper/descriptors/desc.rs new file mode 100644 index 0000000..2d7fd9d --- /dev/null +++ b/0.20.0/src/wrapper/descriptors/desc.rs @@ -0,0 +1,17 @@ +use crate::{errors::*, JNIEnv}; + +/// Trait for things that can be looked up through the JNI via a descriptor. +/// This will be something like the fully-qualified class name +/// `java/lang/String` or a tuple containing a class descriptor, method name, +/// and method signature. For convenience, this is also implemented for the +/// concrete types themselves in addition to their descriptors. +pub trait Desc<'a, T> { + /// Look up the concrete type from the JVM. + fn lookup(self, _: &JNIEnv<'a>) -> Result; +} + +impl<'a, T> Desc<'a, T> for T { + fn lookup(self, _: &JNIEnv<'a>) -> Result { + Ok(self) + } +} diff --git a/0.20.0/src/wrapper/descriptors/exception_desc.rs b/0.20.0/src/wrapper/descriptors/exception_desc.rs new file mode 100644 index 0000000..4911650 --- /dev/null +++ b/0.20.0/src/wrapper/descriptors/exception_desc.rs @@ -0,0 +1,47 @@ +use crate::{ + descriptors::Desc, + errors::*, + objects::{JClass, JObject, JThrowable, JValue}, + strings::JNIString, + JNIEnv, +}; + +const DEFAULT_EXCEPTION_CLASS: &str = "java/lang/RuntimeException"; + +impl<'a, 'c, C, M> Desc<'a, JThrowable<'a>> for (C, M) +where + C: Desc<'a, JClass<'c>>, + M: Into, +{ + fn lookup(self, env: &JNIEnv<'a>) -> Result> { + let jmsg: JObject = env.new_string(self.1)?.into(); + let obj: JThrowable = env + .new_object(self.0, "(Ljava/lang/String;)V", &[JValue::from(jmsg)])? + .into(); + Ok(obj) + } +} + +impl<'a> Desc<'a, JThrowable<'a>> for Exception { + fn lookup(self, env: &JNIEnv<'a>) -> Result> { + (self.class, self.msg).lookup(env) + } +} + +impl<'a, 'b> Desc<'a, JThrowable<'a>> for &'b str { + fn lookup(self, env: &JNIEnv<'a>) -> Result> { + (DEFAULT_EXCEPTION_CLASS, self).lookup(env) + } +} + +impl<'a> Desc<'a, JThrowable<'a>> for String { + fn lookup(self, env: &JNIEnv<'a>) -> Result> { + (DEFAULT_EXCEPTION_CLASS, self).lookup(env) + } +} + +impl<'a> Desc<'a, JThrowable<'a>> for JNIString { + fn lookup(self, env: &JNIEnv<'a>) -> Result> { + (DEFAULT_EXCEPTION_CLASS, self).lookup(env) + } +} diff --git a/0.20.0/src/wrapper/descriptors/field_desc.rs b/0.20.0/src/wrapper/descriptors/field_desc.rs new file mode 100644 index 0000000..405c03b --- /dev/null +++ b/0.20.0/src/wrapper/descriptors/field_desc.rs @@ -0,0 +1,29 @@ +use crate::{ + descriptors::Desc, + errors::*, + objects::{JClass, JFieldID, JStaticFieldID}, + strings::JNIString, + JNIEnv, +}; + +impl<'a, 'c, T, U, V> Desc<'a, JFieldID> for (T, U, V) +where + T: Desc<'a, JClass<'c>>, + U: Into, + V: Into, +{ + fn lookup(self, env: &JNIEnv<'a>) -> Result { + env.get_field_id(self.0, self.1, self.2) + } +} + +impl<'a, 'c, T, U, V> Desc<'a, JStaticFieldID> for (T, U, V) +where + T: Desc<'a, JClass<'c>>, + U: Into, + V: Into, +{ + fn lookup(self, env: &JNIEnv<'a>) -> Result { + env.get_static_field_id(self.0, self.1, self.2) + } +} diff --git a/0.20.0/src/wrapper/descriptors/method_desc.rs b/0.20.0/src/wrapper/descriptors/method_desc.rs new file mode 100644 index 0000000..1b94e04 --- /dev/null +++ b/0.20.0/src/wrapper/descriptors/method_desc.rs @@ -0,0 +1,39 @@ +use crate::{ + descriptors::Desc, + errors::*, + objects::{JClass, JMethodID, JStaticMethodID}, + strings::JNIString, + JNIEnv, +}; + +impl<'a, 'c, T, U, V> Desc<'a, JMethodID> for (T, U, V) +where + T: Desc<'a, JClass<'c>>, + U: Into, + V: Into, +{ + fn lookup(self, env: &JNIEnv<'a>) -> Result { + env.get_method_id(self.0, self.1, self.2) + } +} + +impl<'a, 'c, T, Signature> Desc<'a, JMethodID> for (T, Signature) +where + T: Desc<'a, JClass<'c>>, + Signature: Into, +{ + fn lookup(self, env: &JNIEnv<'a>) -> Result { + (self.0, "", self.1).lookup(env) + } +} + +impl<'a, 'c, T, U, V> Desc<'a, JStaticMethodID> for (T, U, V) +where + T: Desc<'a, JClass<'c>>, + U: Into, + V: Into, +{ + fn lookup(self, env: &JNIEnv<'a>) -> Result { + env.get_static_method_id(self.0, self.1, self.2) + } +} diff --git a/0.20.0/src/wrapper/descriptors/mod.rs b/0.20.0/src/wrapper/descriptors/mod.rs new file mode 100644 index 0000000..e2fc230 --- /dev/null +++ b/0.20.0/src/wrapper/descriptors/mod.rs @@ -0,0 +1,14 @@ +mod desc; +pub use self::desc::*; + +mod class_desc; +pub use self::class_desc::*; + +mod method_desc; +pub use self::method_desc::*; + +mod field_desc; +pub use self::field_desc::*; + +mod exception_desc; +pub use self::exception_desc::*; diff --git a/0.20.0/src/wrapper/errors.rs b/0.20.0/src/wrapper/errors.rs new file mode 100644 index 0000000..19b9d8c --- /dev/null +++ b/0.20.0/src/wrapper/errors.rs @@ -0,0 +1,89 @@ +#![allow(missing_docs)] + +use thiserror::Error; + +use crate::sys; +use crate::wrapper::signature::TypeSignature; + +pub type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Invalid JValue type cast: {0}. Actual type: {1}")] + WrongJValueType(&'static str, &'static str), + #[error("Invalid constructor return type (must be void)")] + InvalidCtorReturn, + #[error("Invalid number of arguments passed to java method: {0}")] + InvalidArgList(TypeSignature), + #[error("Method not found: {name} {sig}")] + MethodNotFound { name: String, sig: String }, + #[error("Field not found: {name} {sig}")] + FieldNotFound { name: String, sig: String }, + #[error("Java exception was thrown")] + JavaException, + #[error("JNIEnv null method pointer for {0}")] + JNIEnvMethodNotFound(&'static str), + #[error("Null pointer in {0}")] + NullPtr(&'static str), + #[error("Null pointer deref in {0}")] + NullDeref(&'static str), + #[error("Mutex already locked")] + TryLock, + #[error("JavaVM null method pointer for {0}")] + JavaVMMethodNotFound(&'static str), + #[error("Field already set: {0}")] + FieldAlreadySet(String), + #[error("Throw failed with error code {0}")] + ThrowFailed(i32), + #[error("Parse failed for input: {1}")] + ParseFailed(#[source] combine::error::StringStreamError, String), + #[error("JNI call failed")] + JniCall(#[source] JniError), +} + +#[derive(Debug, Error)] +pub enum JniError { + #[error("Unknown error")] + Unknown, + #[error("Current thread is not attached to the Java VM")] + ThreadDetached, + #[error("JNI version error")] + WrongVersion, + #[error("Not enough memory")] + NoMemory, + #[error("VM already created")] + AlreadyCreated, + #[error("Invalid arguments")] + InvalidArguments, + #[error("Error code {0}")] + Other(sys::jint), +} + +impl From<::std::sync::TryLockError> for Error { + fn from(_: ::std::sync::TryLockError) -> Self { + Error::TryLock + } +} + +pub fn jni_error_code_to_result(code: sys::jint) -> Result<()> { + match code { + sys::JNI_OK => Ok(()), + sys::JNI_ERR => Err(JniError::Unknown), + sys::JNI_EDETACHED => Err(JniError::ThreadDetached), + sys::JNI_EVERSION => Err(JniError::WrongVersion), + sys::JNI_ENOMEM => Err(JniError::NoMemory), + sys::JNI_EEXIST => Err(JniError::AlreadyCreated), + sys::JNI_EINVAL => Err(JniError::InvalidArguments), + _ => Err(JniError::Other(code)), + } + .map_err(Error::JniCall) +} + +pub struct Exception { + pub class: String, + pub msg: String, +} + +pub trait ToException { + fn to_exception(&self) -> Exception; +} diff --git a/0.20.0/src/wrapper/executor.rs b/0.20.0/src/wrapper/executor.rs new file mode 100644 index 0000000..9ef12db --- /dev/null +++ b/0.20.0/src/wrapper/executor.rs @@ -0,0 +1,96 @@ +use std::sync::Arc; + +use crate::{errors::*, objects::JObject, JNIEnv, JavaVM}; + +/// The capacity of local frames, allocated for attached threads by default. Same as the default +/// value Hotspot uses when calling native Java methods. +pub const DEFAULT_LOCAL_FRAME_CAPACITY: i32 = 32; + +/// Thread attachment manager. It allows to execute closures in attached threads with automatic +/// local references management done with `with_local_frame`. It combines the performance benefits +/// of permanent attaches whilst removing the risk of local references leaks if used consistently. +/// +/// Although all locals are freed on closure exit, it might be needed to manually free +/// locals _inside_ the closure if an unbounded number of them is created (e.g., in a loop). +/// See ["Local Reference Management"](struct.JavaVM.html#local-reference-management) for details. +/// +/// Threads using the Executor are attached on the first invocation as daemons, +/// hence they do not block JVM exit. Finished threads detach automatically. +/// +/// ## Example +/// +/// ```rust +/// # use jni::errors; +/// # // +/// # fn main() -> errors::Result<()> { +/// # // Ignore this test without invocation feature, so that simple `cargo test` works +/// # #[cfg(feature = "invocation")] { +/// # // +/// # use jni::{objects::JValue, Executor, InitArgsBuilder, JavaVM, sys::jint}; +/// # use std::sync::Arc; +/// # // +/// # let jvm_args = InitArgsBuilder::new() +/// # .build() +/// # .unwrap(); +/// # // Create a new VM +/// # let jvm = Arc::new(JavaVM::new(jvm_args)?); +/// +/// let exec = Executor::new(jvm); +/// +/// let val: jint = exec.with_attached(|env| { +/// let x = JValue::from(-10); +/// let val: jint = env.call_static_method("java/lang/Math", "abs", "(I)I", &[x])? +/// .i()?; +/// Ok(val) +/// })?; +/// +/// assert_eq!(val, 10); +/// +/// # } +/// # Ok(()) } +/// ``` +#[derive(Clone)] +pub struct Executor { + vm: Arc, +} + +impl Executor { + /// Creates new Executor with specified JVM. + pub fn new(vm: Arc) -> Self { + Self { vm } + } + + /// Executes a provided closure, making sure that the current thread + /// is attached to the JVM. Additionally ensures that local object references are freed after + /// call. + /// + /// Allocates a local frame with the specified capacity. + pub fn with_attached_capacity(&self, capacity: i32, f: F) -> Result + where + F: FnOnce(&JNIEnv) -> Result, + { + assert!(capacity > 0, "capacity should be a positive integer"); + + let jni_env = self.vm.attach_current_thread_as_daemon()?; + let mut result = None; + jni_env.with_local_frame(capacity, || { + result = Some(f(&jni_env)); + Ok(JObject::null()) + })?; + + result.expect("The result should be Some or this line shouldn't be reached") + } + + /// Executes a provided closure, making sure that the current thread + /// is attached to the JVM. Additionally ensures that local object references are freed after + /// call. + /// + /// Allocates a local frame with + /// [the default capacity](constant.DEFAULT_LOCAL_FRAME_CAPACITY.html). + pub fn with_attached(&self, f: F) -> Result + where + F: FnOnce(&JNIEnv) -> Result, + { + self.with_attached_capacity(DEFAULT_LOCAL_FRAME_CAPACITY, f) + } +} diff --git a/0.20.0/src/wrapper/java_vm/init_args.rs b/0.20.0/src/wrapper/java_vm/init_args.rs new file mode 100644 index 0000000..c1fd6e1 --- /dev/null +++ b/0.20.0/src/wrapper/java_vm/init_args.rs @@ -0,0 +1,138 @@ +use std::{ffi::CString, os::raw::c_void}; + +use thiserror::Error; + +use crate::{ + sys::{JavaVMInitArgs, JavaVMOption}, + JNIVersion, +}; + +/// Errors that can occur when invoking a [`JavaVM`](super::vm::JavaVM) with the +/// [Invocation API](https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html). +#[derive(Debug, Error)] +pub enum JvmError { + /// An internal `0` byte was found when constructing a string. + #[error("internal null in option: {0}")] + NullOptString(String), +} + +/// Builder for JavaVM InitArgs. +/// +/// *This API requires "invocation" feature to be enabled, +/// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).* +#[derive(Debug)] +pub struct InitArgsBuilder { + opts: Vec, + ignore_unrecognized: bool, + version: JNIVersion, +} + +impl Default for InitArgsBuilder { + fn default() -> Self { + InitArgsBuilder { + opts: vec![], + ignore_unrecognized: false, + version: JNIVersion::V8, + } + } +} + +impl InitArgsBuilder { + /// Create a new default InitArgsBuilder + pub fn new() -> Self { + Default::default() + } + + /// Add an option to the init args + /// + /// The `vfprintf`, `abort`, and `exit` options are unsupported at this time. + pub fn option(self, opt_string: &str) -> Self { + let mut s = self; + + match opt_string { + "vfprintf" | "abort" | "exit" => return s, + _ => {} + } + + s.opts.push(opt_string.into()); + + s + } + + /// Set JNI version for the init args + /// + /// Default: V8 + pub fn version(self, version: JNIVersion) -> Self { + let mut s = self; + s.version = version; + s + } + + /// Set the `ignoreUnrecognized` init arg flag + /// + /// If ignoreUnrecognized is true, JavaVM::new ignores all unrecognized option strings that + /// begin with "-X" or "_". If ignoreUnrecognized is false, JavaVM::new returns Err as soon as + /// it encounters any unrecognized option strings. + /// + /// Default: `false` + pub fn ignore_unrecognized(self, ignore: bool) -> Self { + let mut s = self; + s.ignore_unrecognized = ignore; + s + } + + /// Build the `InitArgs` + /// + /// This will check for internal nulls in the option strings and will return + /// an error if one is found. + pub fn build(self) -> Result { + let mut opts = Vec::with_capacity(self.opts.len()); + for opt in self.opts { + let option_string = + CString::new(opt.as_str()).map_err(|_| JvmError::NullOptString(opt))?; + let jvm_opt = JavaVMOption { + optionString: option_string.into_raw(), + extraInfo: ::std::ptr::null_mut(), + }; + opts.push(jvm_opt); + } + + Ok(InitArgs { + inner: JavaVMInitArgs { + version: self.version.into(), + ignoreUnrecognized: self.ignore_unrecognized as _, + options: opts.as_ptr() as _, + nOptions: opts.len() as _, + }, + opts, + }) + } + + /// Returns collected options + pub fn options(&self) -> Vec { + self.opts.clone() + } +} + +/// JavaVM InitArgs. +/// +/// *This API requires "invocation" feature to be enabled, +/// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).* +pub struct InitArgs { + inner: JavaVMInitArgs, + opts: Vec, +} + +impl InitArgs { + pub(crate) fn inner_ptr(&self) -> *mut c_void { + &self.inner as *const _ as _ + } +} + +impl Drop for InitArgs { + fn drop(&mut self) { + for opt in self.opts.iter() { + unsafe { CString::from_raw(opt.optionString) }; + } + } +} diff --git a/0.20.0/src/wrapper/java_vm/mod.rs b/0.20.0/src/wrapper/java_vm/mod.rs new file mode 100644 index 0000000..64e4c6c --- /dev/null +++ b/0.20.0/src/wrapper/java_vm/mod.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "invocation")] +mod init_args; +#[cfg(feature = "invocation")] +pub use self::init_args::*; + +mod vm; +pub use self::vm::*; diff --git a/0.20.0/src/wrapper/java_vm/vm.rs b/0.20.0/src/wrapper/java_vm/vm.rs new file mode 100644 index 0000000..e127634 --- /dev/null +++ b/0.20.0/src/wrapper/java_vm/vm.rs @@ -0,0 +1,455 @@ +use std::{ + cell::RefCell, + ops::Deref, + ptr, + sync::atomic::{AtomicUsize, Ordering}, + thread::current, +}; + +use log::{debug, error}; + +use crate::{errors::*, sys, JNIEnv}; + +#[cfg(feature = "invocation")] +use crate::InitArgs; +use std::thread::Thread; + +/// The Java VM, providing [Invocation API][invocation-api] support. +/// +/// The JavaVM can be obtained either via [`JNIEnv#get_java_vm`][get-vm] in an already attached +/// thread, or it can be [launched](#launching-jvm-from-rust) from Rust via `JavaVM#new`. +/// +/// ## Attaching Native Threads +/// +/// A native thread must «attach» itself to be able to call Java methods outside of a native Java +/// method. This library provides two modes of attachment, each ensuring the thread is promptly +/// detached: +/// * A scoped attachment with [`attach_current_thread`][act]. +/// The thread will automatically detach itself once the returned guard is dropped. +/// * A permanent attachment with [`attach_current_thread_permanently`][actp] +/// or [`attach_current_thread_as_daemon`][actd]. +/// The thread will automatically detach itself before it terminates. +/// +/// As attachment and detachment of a thread is an expensive operation, the scoped attachment +/// shall be used if happens infrequently. If you have an undefined scope where you need +/// to use `JNIEnv` and cannot keep the `AttachGuard`, consider attaching the thread +/// permanently. +/// +/// ### Local Reference Management +/// +/// Remember that the native thread attached to the VM **must** manage the local references +/// properly, i.e., do not allocate an excessive number of references and release them promptly +/// when they are no longer needed to enable the GC to collect them. A common approach is to use +/// an appropriately-sized local frame for larger code fragments +/// (see [`with_local_frame`](struct.JNIEnv.html#method.with_local_frame) and [Executor](#executor)) +/// and [auto locals](struct.JNIEnv.html#method.auto_local) in loops. +/// +/// See also the [JNI specification][spec-references] for details on referencing Java objects. +/// +/// ### Executor +/// +/// Jni-rs provides an [`Executor`](struct.Executor.html) — a helper struct that allows to +/// execute a closure with `JNIEnv`. It combines the performance benefits of permanent attaches +/// *and* automatic local reference management. Prefer it to manual permanent attaches if +/// they happen in various parts of the code to reduce the burden of local reference management. +/// +/// ## Launching JVM from Rust +/// +/// To [launch][launch-vm] a JVM from a native process, enable the `invocation` feature +/// in the Cargo.toml: +/// +/// ```toml +/// jni = { version = "0.20.0", features = ["invocation"] } +/// ``` +/// +/// The application will require linking to the dynamic `jvm` library, which is distributed +/// with the JVM, and allow to use `JavaVM#new`: +/// +/// ```rust +/// # use jni::errors; +/// # // +/// # fn main() -> errors::Result<()> { +/// # // Ignore this test without invocation feature, so that simple `cargo test` works +/// # #[cfg(feature = "invocation")] { +/// # // +/// # use jni::{AttachGuard, objects::JValue, InitArgsBuilder, JNIEnv, JNIVersion, JavaVM, sys::jint}; +/// # // +/// // Build the VM properties +/// let jvm_args = InitArgsBuilder::new() +/// // Pass the JNI API version (default is 8) +/// .version(JNIVersion::V8) +/// // You can additionally pass any JVM options (standard, like a system property, +/// // or VM-specific). +/// // Here we enable some extra JNI checks useful during development +/// .option("-Xcheck:jni") +/// .build() +/// .unwrap(); +/// +/// // Create a new VM +/// let jvm = JavaVM::new(jvm_args)?; +/// +/// // Attach the current thread to call into Java — see extra options in +/// // "Attaching Native Threads" section. +/// // +/// // This method returns the guard that will detach the current thread when dropped, +/// // also freeing any local references created in it +/// let env = jvm.attach_current_thread()?; +/// +/// // Call Java Math#abs(-10) +/// let x = JValue::from(-10); +/// let val: jint = env.call_static_method("java/lang/Math", "abs", "(I)I", &[x])? +/// .i()?; +/// +/// assert_eq!(val, 10); +/// +/// # } +/// # Ok(()) } +/// ``` +/// +/// During build time, the JVM installation path is determined: +/// 1. By `JAVA_HOME` environment variable, if it is set. +/// 2. Otherwise — from `java` output. +/// +/// It is recommended to set `JAVA_HOME` to have reproducible builds, +/// especially, in case of multiple VMs installed. +/// +/// At application run time, you must specify the path +/// to the `jvm` library so that the loader can locate it. +/// * On **Windows**, append the path to `jvm.dll` to `PATH` environment variable. +/// * On **MacOS**, append the path to `libjvm.dylib` to `LD_LIBRARY_PATH` environment variable. +/// * On **Linux**, append the path to `libjvm.so` to `LD_LIBRARY_PATH` environment variable. +/// +/// The exact relative path to `jvm` library is version-specific. +/// +/// For more information on linking — see documentation +/// in [build.rs](https://github.com/jni-rs/jni-rs/tree/master/build.rs). +/// +/// [invocation-api]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html +/// [get-vm]: struct.JNIEnv.html#method.get_java_vm +/// [launch-vm]: struct.JavaVM.html#method.new +/// [act]: struct.JavaVM.html#method.attach_current_thread +/// [actp]: struct.JavaVM.html#method.attach_current_thread_permanently +/// [actd]: struct.JavaVM.html#method.attach_current_thread_as_daemon +/// [spec-references]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/design.html#referencing-java-objects +#[repr(transparent)] +#[derive(Debug)] +pub struct JavaVM(*mut sys::JavaVM); + +unsafe impl Send for JavaVM {} +unsafe impl Sync for JavaVM {} + +impl JavaVM { + /// Launch a new JavaVM using the provided init args. + /// + /// Unlike original JNI API, the main thread (the thread from which this method is called) will + /// not be attached to JVM. You must explicitly use `attach_current_thread…` methods (refer + /// to [Attaching Native Threads section](#attaching-native-threads)). + /// + /// *This API requires "invocation" feature to be enabled, + /// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).* + #[cfg(feature = "invocation")] + pub fn new(args: InitArgs) -> Result { + use std::os::raw::c_void; + + let mut ptr: *mut sys::JavaVM = ::std::ptr::null_mut(); + let mut env: *mut sys::JNIEnv = ::std::ptr::null_mut(); + + unsafe { + jni_error_code_to_result(sys::JNI_CreateJavaVM( + &mut ptr as *mut _, + &mut env as *mut *mut sys::JNIEnv as *mut *mut c_void, + args.inner_ptr(), + ))?; + + let vm = Self::from_raw(ptr)?; + java_vm_unchecked!(vm.0, DetachCurrentThread); + + Ok(vm) + } + } + + /// Create a JavaVM from a raw pointer. + /// + /// # Safety + /// + /// Expects a valid pointer retrieved from the `JNI_CreateJavaVM` JNI function. Only does null check. + pub unsafe fn from_raw(ptr: *mut sys::JavaVM) -> Result { + non_null!(ptr, "from_raw ptr argument"); + Ok(JavaVM(ptr)) + } + + /// Returns underlying `sys::JavaVM` interface. + pub fn get_java_vm_pointer(&self) -> *mut sys::JavaVM { + self.0 + } + + /// Attaches the current thread to the JVM. Calling this for a thread that is already attached + /// is a no-op. + /// + /// The thread will detach itself automatically when it exits. + /// + /// Attached threads [block JVM exit][block]. If it is not desirable — consider using + /// [`attach_current_thread_as_daemon`][attach-as-daemon]. + /// + /// [block]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html#unloading-the-vm + /// [attach-as-daemon]: struct.JavaVM.html#method.attach_current_thread_as_daemon + pub fn attach_current_thread_permanently(&self) -> Result { + match self.get_env() { + Ok(env) => Ok(env), + Err(_) => self.attach_current_thread_impl(ThreadType::Normal), + } + } + + /// Attaches the current thread to the Java VM. The returned `AttachGuard` + /// can be dereferenced to a `JNIEnv` and automatically detaches the thread + /// when dropped. Calling this in a thread that is already attached is a no-op, and + /// will neither change its daemon status nor prematurely detach it. + /// + /// Attached threads [block JVM exit][block]. + /// + /// Attaching and detaching a thread is an expensive operation. If you use it frequently + /// in the same threads, consider either [attaching them permanently][attach-as-daemon], + /// or, if the scope where you need the `JNIEnv` is well-defined, keeping the returned guard. + /// + /// [block]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html#unloading-the-vm + /// [attach-as-daemon]: struct.JavaVM.html#method.attach_current_thread_as_daemon + pub fn attach_current_thread(&self) -> Result { + match self.get_env() { + Ok(env) => Ok(AttachGuard::new_nested(env)), + Err(_) => { + let env = self.attach_current_thread_impl(ThreadType::Normal)?; + Ok(AttachGuard::new(env)) + } + } + } + + /// Detaches current thread from the JVM. This operation is _rarely_ appropriate to use, + /// because the attachment methods [ensure](#attaching-native-threads) that the thread is + /// promptly detached. + /// + /// Detaching a non-attached thread is a no-op. + /// + /// __Any existing `JNIEnv`s and `AttachGuard`s created in the calling thread + /// will be invalidated after this method completes. It is the__ caller’s __responsibility + /// to ensure that no JNI calls are subsequently performed on these objects.__ + /// Failure to do so will result in unspecified errors, possibly, the process crash. + /// + /// Given some care is exercised, this method can be used to detach permanently attached + /// threads _before_ they exit (when automatic detachment occurs). However, it is + /// never appropriate to use it with the scoped attachment (`attach_current_thread`). + // This method is hidden because it is almost never needed and its use requires some + // extra care. Its status might be reconsidered if we learn of any use cases that require it. + #[doc(hidden)] + pub fn detach_current_thread(&self) { + InternalAttachGuard::clear_tls(); + } + + /// Attaches the current thread to the Java VM as a _daemon_. Calling this in a thread + /// that is already attached is a no-op, and will not change its status to a daemon thread. + /// + /// The thread will detach itself automatically when it exits. + pub fn attach_current_thread_as_daemon(&self) -> Result { + match self.get_env() { + Ok(env) => Ok(env), + Err(_) => self.attach_current_thread_impl(ThreadType::Daemon), + } + } + + /// Returns the current number of threads attached to the JVM. + /// + /// This method is provided mostly for diagnostic purposes. + pub fn threads_attached(&self) -> usize { + ATTACHED_THREADS.load(Ordering::SeqCst) + } + + /// Get the `JNIEnv` associated with the current thread, or + /// `ErrorKind::Detached` + /// if the current thread is not attached to the java VM. + pub fn get_env(&self) -> Result { + let mut ptr = ptr::null_mut(); + unsafe { + let res = java_vm_unchecked!(self.0, GetEnv, &mut ptr, sys::JNI_VERSION_1_1); + jni_error_code_to_result(res)?; + + JNIEnv::from_raw(ptr as *mut sys::JNIEnv) + } + } + + /// Creates `InternalAttachGuard` and attaches current thread. + fn attach_current_thread_impl(&self, thread_type: ThreadType) -> Result { + let guard = InternalAttachGuard::new(self.get_java_vm_pointer()); + let env_ptr = unsafe { + if thread_type == ThreadType::Daemon { + guard.attach_current_thread_as_daemon()? + } else { + guard.attach_current_thread()? + } + }; + + InternalAttachGuard::fill_tls(guard); + + unsafe { JNIEnv::from_raw(env_ptr as *mut sys::JNIEnv) } + } +} + +thread_local! { + static THREAD_ATTACH_GUARD: RefCell> = RefCell::new(None) +} + +static ATTACHED_THREADS: AtomicUsize = AtomicUsize::new(0); + +/// A RAII implementation of scoped guard which detaches the current thread +/// when dropped. The attached `JNIEnv` can be accessed through this guard +/// via its `Deref` implementation. +pub struct AttachGuard<'a> { + env: JNIEnv<'a>, + should_detach: bool, +} + +impl<'a> AttachGuard<'a> { + /// AttachGuard created with this method will detach current thread on drop + fn new(env: JNIEnv<'a>) -> Self { + Self { + env, + should_detach: true, + } + } + + /// AttachGuard created with this method will not detach current thread on drop, which is + /// the case for nested attaches. + fn new_nested(env: JNIEnv<'a>) -> Self { + Self { + env, + should_detach: false, + } + } +} + +impl<'a> Deref for AttachGuard<'a> { + type Target = JNIEnv<'a>; + + fn deref(&self) -> &Self::Target { + &self.env + } +} + +impl<'a> Drop for AttachGuard<'a> { + fn drop(&mut self) { + if self.should_detach { + InternalAttachGuard::clear_tls(); + } + } +} + +#[derive(PartialEq)] +enum ThreadType { + Normal, + Daemon, +} + +#[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, + thread: current(), + } + } + + /// Stores guard in thread local storage. + fn fill_tls(guard: InternalAttachGuard) { + THREAD_ATTACH_GUARD.with(move |f| { + *f.borrow_mut() = Some(guard); + }); + } + + /// Clears thread local storage, dropping the InternalAttachGuard and causing detach of + /// the current thread. + fn clear_tls() { + THREAD_ATTACH_GUARD.with(move |f| { + *f.borrow_mut() = None; + }); + } + + unsafe fn attach_current_thread(&self) -> Result<*mut sys::JNIEnv> { + let mut env_ptr = ptr::null_mut(); + let res = java_vm_unchecked!( + self.java_vm, + AttachCurrentThread, + &mut env_ptr, + ptr::null_mut() + ); + jni_error_code_to_result(res)?; + + ATTACHED_THREADS.fetch_add(1, Ordering::SeqCst); + + debug!( + "Attached thread {} ({:?}). {} threads attached", + self.thread.name().unwrap_or_default(), + self.thread.id(), + ATTACHED_THREADS.load(Ordering::SeqCst) + ); + + Ok(env_ptr as *mut sys::JNIEnv) + } + + unsafe fn attach_current_thread_as_daemon(&self) -> Result<*mut sys::JNIEnv> { + let mut env_ptr = ptr::null_mut(); + let res = java_vm_unchecked!( + self.java_vm, + AttachCurrentThreadAsDaemon, + &mut env_ptr, + ptr::null_mut() + ); + jni_error_code_to_result(res)?; + + ATTACHED_THREADS.fetch_add(1, Ordering::SeqCst); + + debug!( + "Attached daemon thread {} ({:?}). {} threads attached", + self.thread.name().unwrap_or_default(), + self.thread.id(), + ATTACHED_THREADS.load(Ordering::SeqCst) + ); + + Ok(env_ptr as *mut sys::JNIEnv) + } + + fn detach(&mut self) -> Result<()> { + unsafe { + java_vm_unchecked!(self.java_vm, DetachCurrentThread); + } + ATTACHED_THREADS.fetch_sub(1, Ordering::SeqCst); + debug!( + "Detached thread {} ({:?}). {} threads remain attached", + self.thread.name().unwrap_or_default(), + self.thread.id(), + ATTACHED_THREADS.load(Ordering::SeqCst) + ); + + Ok(()) + } +} + +impl Drop for InternalAttachGuard { + fn drop(&mut self) { + if let Err(e) = self.detach() { + error!( + "Error detaching current thread: {:#?}\nThread {} id={:?}", + e, + self.thread.name().unwrap_or_default(), + self.thread.id(), + ); + } + } +} diff --git a/0.20.0/src/wrapper/jnienv.rs b/0.20.0/src/wrapper/jnienv.rs new file mode 100644 index 0000000..f0da5d2 --- /dev/null +++ b/0.20.0/src/wrapper/jnienv.rs @@ -0,0 +1,2334 @@ +use std::{ + marker::PhantomData, + os::raw::{c_char, c_void}, + ptr, str, + str::FromStr, + sync::{Mutex, MutexGuard}, +}; + +use log::warn; + +use crate::signature::ReturnType; +use crate::{ + descriptors::Desc, + errors::*, + objects::{ + AutoArray, AutoLocal, AutoPrimitiveArray, GlobalRef, JByteBuffer, JClass, JFieldID, JList, + JMap, JMethodID, JObject, JStaticFieldID, JStaticMethodID, JString, JThrowable, JValue, + ReleaseMode, TypeArray, + }, + signature::{JavaType, Primitive, TypeSignature}, + strings::{JNIString, JavaStr}, + sys::{ + self, jarray, jboolean, jbooleanArray, jbyte, jbyteArray, jchar, jcharArray, jdouble, + jdoubleArray, jfloat, jfloatArray, jint, jintArray, jlong, jlongArray, jobjectArray, + jshort, jshortArray, jsize, jvalue, JNINativeMethod, + }, + JNIVersion, JavaVM, +}; + +/// FFI-compatible JNIEnv struct. You can safely use this as the JNIEnv argument +/// to exported methods that will be called by java. This is where most of the +/// magic happens. All methods on this object are wrappers around JNI functions, +/// so the documentation on their behavior is still pretty applicable. +/// +/// # Exception handling +/// +/// Since we're calling into the JVM with this, many methods also have the +/// potential to cause an exception to get thrown. If this is the case, an `Err` +/// result will be returned with the error kind `JavaException`. Note that this +/// will _not_ clear the exception - it's up to the caller to decide whether to +/// do so or to let it continue being thrown. +/// +/// ## `null` Java references +/// `null` Java references are handled by the following rules: +/// - If a `null` Java reference is passed to a method that expects a non-`null` +/// argument, an `Err` result with the kind `NullPtr` is returned. +/// - If a JNI function returns `null` to indicate an error (e.g. `new_int_array`), +/// it is converted to `Err`/`NullPtr` or, where possible, to a more applicable +/// error type, such as `MethodNotFound`. If the JNI function also throws +/// an exception, the `JavaException` error kind will be preferred. +/// - If a JNI function may return `null` Java reference as one of possible reference +/// values (e.g., `get_object_array_element` or `get_field_unchecked`), +/// it is converted to `JObject::null()`. +/// +/// # Checked and unchecked methods +/// +/// Some of the methods come in two versions: checked (e.g. `call_method`) and +/// unchecked (e.g. `call_method_unchecked`). Under the hood, checked methods +/// perform some checks to ensure the validity of provided signatures, names +/// and arguments, and then call the corresponding unchecked method. +/// +/// Checked methods are more flexible as they allow passing class names +/// and method/field descriptors as strings and may perform lookups +/// of class objects and method/field ids for you, also performing +/// all the needed precondition checks. However, these lookup operations +/// are expensive, so if you need to call the same method (or access +/// the same field) multiple times, it is +/// [recommended](https://docs.oracle.com/en/java/javase/11/docs/specs/jni/design.html#accessing-fields-and-methods) +/// to cache the instance of the class and the method/field id, e.g. +/// - in loops +/// - when calling the same Java callback repeatedly. +/// +/// If you do not cache references to classes and method/field ids, +/// you will *not* benefit from the unchecked methods. +/// +/// Calling unchecked methods with invalid arguments and/or invalid class and +/// method descriptors may lead to segmentation fault. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct JNIEnv<'a> { + internal: *mut sys::JNIEnv, + lifetime: PhantomData<&'a ()>, +} + +impl<'a> JNIEnv<'a> { + /// Create a JNIEnv from a raw pointer. + /// + /// # Safety + /// + /// Expects a valid pointer retrieved from the `GetEnv` JNI function. Only does a null check. + pub unsafe fn from_raw(ptr: *mut sys::JNIEnv) -> Result { + non_null!(ptr, "from_raw ptr argument"); + Ok(JNIEnv { + internal: ptr, + lifetime: PhantomData, + }) + } + + /// Get the java version that we're being executed from. + pub fn get_version(&self) -> Result { + Ok(jni_unchecked!(self.internal, GetVersion).into()) + } + + /// Load a class from a buffer of raw class data. The name of the class must match the name + /// encoded within the class file data. + pub fn define_class(&self, name: S, loader: JObject<'a>, buf: &[u8]) -> Result> + where + S: Into, + { + let name = name.into(); + self.define_class_impl(name.as_ptr(), loader, buf) + } + + /// Load a class from a buffer of raw class data. The name of the class is inferred from the + /// buffer. + pub fn define_unnamed_class(&self, loader: JObject<'a>, buf: &[u8]) -> Result> + where + S: Into, + { + self.define_class_impl(ptr::null(), loader, buf) + } + + fn define_class_impl( + &self, + name: *const c_char, + loader: JObject<'a>, + buf: &[u8], + ) -> Result> { + let class = jni_non_null_call!( + self.internal, + DefineClass, + name, + loader.into_raw(), + buf.as_ptr() as *const jbyte, + buf.len() as jsize + ); + Ok(unsafe { JClass::from_raw(class) }) + } + + /// Look up a class by name. + /// + /// # Example + /// ```rust,ignore + /// let class: JClass<'a> = env.find_class("java/lang/String"); + /// ``` + pub fn find_class(&self, name: S) -> Result> + where + S: Into, + { + let name = name.into(); + let class = jni_non_null_call!(self.internal, FindClass, name.as_ptr()); + Ok(unsafe { JClass::from_raw(class) }) + } + + /// Returns the superclass for a particular class OR `JObject::null()` for `java.lang.Object` or + /// an interface. As with `find_class`, takes a descriptor. + pub fn get_superclass<'c, T>(&self, class: T) -> Result> + where + T: Desc<'a, JClass<'c>>, + { + let class = class.lookup(self)?; + Ok(unsafe { + JClass::from_raw(jni_non_void_call!( + self.internal, + GetSuperclass, + class.into_raw() + )) + }) + } + + /// Tests whether class1 is assignable from class2. + pub fn is_assignable_from<'t, 'u, T, U>(&self, class1: T, class2: U) -> Result + where + T: Desc<'a, JClass<'t>>, + U: Desc<'a, JClass<'u>>, + { + let class1 = class1.lookup(self)?; + let class2 = class2.lookup(self)?; + Ok(jni_unchecked!( + self.internal, + IsAssignableFrom, + class1.into_raw(), + class2.into_raw() + ) == sys::JNI_TRUE) + } + + /// Returns true if the object reference can be cast to the given type. + /// + /// _NB: Unlike the operator `instanceof`, function `IsInstanceOf` *returns `true`* + /// for all classes *if `object` is `null`.*_ + /// + /// See [JNI documentation](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#IsInstanceOf) + /// for details. + pub fn is_instance_of<'c, O, T>(&self, object: O, class: T) -> Result + where + O: Into>, + T: Desc<'a, JClass<'c>>, + { + let class = class.lookup(self)?; + Ok(jni_unchecked!( + self.internal, + IsInstanceOf, + object.into().into_raw(), + class.into_raw() + ) == sys::JNI_TRUE) + } + + /// Returns true if ref1 and ref2 refer to the same Java object, or are both `NULL`. Otherwise, + /// returns false. + pub fn is_same_object<'b, 'c, O, T>(&self, ref1: O, ref2: T) -> Result + where + O: Into>, + T: Into>, + { + Ok(jni_unchecked!( + self.internal, + IsSameObject, + ref1.into().into_raw(), + ref2.into().into_raw() + ) == sys::JNI_TRUE) + } + + /// Raise an exception from an existing object. This will continue being + /// thrown in java unless `exception_clear` is called. + /// + /// # Examples + /// ```rust,ignore + /// let _ = env.throw(("java/lang/Exception", "something bad happened")); + /// ``` + /// + /// Defaulting to "java/lang/Exception": + /// + /// ```rust,ignore + /// let _ = env.throw("something bad happened"); + /// ``` + pub fn throw<'e, E>(&self, obj: E) -> Result<()> + where + E: Desc<'a, JThrowable<'e>>, + { + let throwable = obj.lookup(self)?; + let res: i32 = jni_unchecked!(self.internal, Throw, throwable.into_raw()); + if res == 0 { + Ok(()) + } else { + Err(Error::ThrowFailed(res)) + } + } + + /// Create and throw a new exception from a class descriptor and an error + /// message. + /// + /// # Example + /// ```rust,ignore + /// let _ = env.throw_new("java/lang/Exception", "something bad happened"); + /// ``` + pub fn throw_new<'c, S, T>(&self, class: T, msg: S) -> Result<()> + where + S: Into, + T: Desc<'a, JClass<'c>>, + { + let class = class.lookup(self)?; + let msg = msg.into(); + let res: i32 = jni_unchecked!(self.internal, ThrowNew, class.into_raw(), msg.as_ptr()); + if res == 0 { + Ok(()) + } else { + Err(Error::ThrowFailed(res)) + } + } + + /// Check whether or not an exception is currently in the process of being + /// thrown. An exception is in this state from the time it gets thrown and + /// not caught in a java function until `exception_clear` is called. + pub fn exception_occurred(&self) -> Result> { + let throwable = jni_unchecked!(self.internal, ExceptionOccurred); + Ok(unsafe { JThrowable::from_raw(throwable) }) + } + + /// Print exception information to the console. + pub fn exception_describe(&self) -> Result<()> { + jni_unchecked!(self.internal, ExceptionDescribe); + Ok(()) + } + + /// Clear an exception in the process of being thrown. If this is never + /// called, the exception will continue being thrown when control is + /// returned to java. + pub fn exception_clear(&self) -> Result<()> { + jni_unchecked!(self.internal, ExceptionClear); + Ok(()) + } + + /// Abort the JVM with an error message. + #[allow(unused_variables, unreachable_code)] + pub fn fatal_error>(&self, msg: S) -> ! { + let msg = msg.into(); + let res: Result<()> = catch!({ + jni_unchecked!(self.internal, FatalError, msg.as_ptr()); + unreachable!() + }); + + panic!("{:?}", res.unwrap_err()); + } + + /// Check to see if an exception is being thrown. This only differs from + /// `exception_occurred` in that it doesn't return the actual thrown + /// exception. + pub fn exception_check(&self) -> Result { + let check = jni_unchecked!(self.internal, ExceptionCheck) == sys::JNI_TRUE; + Ok(check) + } + + /// 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> { + non_null!(data, "new_direct_byte_buffer data argument"); + let obj = jni_non_null_call!( + self.internal, + NewDirectByteBuffer, + data as *mut c_void, + len as jlong + ); + 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> { + non_null!(buf, "get_direct_buffer_address argument"); + let ptr = jni_unchecked!(self.internal, GetDirectBufferAddress, buf.into_raw()); + non_null!(ptr, "get_direct_buffer_address return value"); + Ok(ptr as _) + } + + /// 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 { + 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 as usize), + } + } + + /// Turns an object into a global ref. This has the benefit of removing the + /// lifetime bounds since it's guaranteed to not get GC'd by java. It + /// releases the GC pin upon being dropped. + pub fn new_global_ref(&self, obj: O) -> Result + where + O: Into>, + { + 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 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; + /// # + /// /// 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 { + /// let throwable: JObject = match self { + /// ExampleError::Exception(exception) => { + /// // The error was caused by a Java exception. + /// + /// // 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> + where + O: Into>, + { + let local = jni_unchecked!(self.internal, NewLocalRef, obj.into().into_raw()); + Ok(unsafe { JObject::from_raw(local) }) + } + + /// Creates a new auto-deleted local reference. + /// + /// See also [`with_local_frame`](struct.JNIEnv.html#method.with_local_frame) method that + /// can be more convenient when you create a _bounded_ number of local references + /// but cannot rely on automatic de-allocation (e.g., in case of recursion, deep call stacks, + /// [permanently-attached](struct.JavaVM.html#attaching-native-threads) native threads, etc.). + pub fn auto_local<'b, O>(&'b self, obj: O) -> AutoLocal<'a, 'b> + where + O: Into>, + { + AutoLocal::new(self, obj.into()) + } + + /// Deletes the local reference. + /// + /// Local references are valid for the duration of a native method call. + /// They are + /// freed automatically after the native method returns. Each local + /// reference costs + /// some amount of Java Virtual Machine resource. Programmers need to make + /// sure that + /// native methods do not excessively allocate local references. Although + /// local + /// references are automatically freed after the native method returns to + /// Java, + /// excessive allocation of local references may cause the VM to run out of + /// memory + /// during the execution of a native method. + /// + /// In most cases it is better to use `AutoLocal` (see `auto_local` method) + /// or `with_local_frame` instead of direct `delete_local_ref` calls. + pub fn delete_local_ref(&self, obj: JObject) -> Result<()> { + jni_unchecked!(self.internal, DeleteLocalRef, obj.into_raw()); + Ok(()) + } + + /// Creates a new local reference frame, in which at least a given number + /// of local references can be created. + /// + /// Returns `Err` on failure, with a pending `OutOfMemoryError`. + /// + /// Prefer to use [`with_local_frame`](struct.JNIEnv.html#method.with_local_frame) instead of + /// direct `push_local_frame`/`pop_local_frame` calls. + /// + /// See also [`auto_local`](struct.JNIEnv.html#method.auto_local) method + /// and `AutoLocal` type — that approach can be more convenient in loops. + pub fn push_local_frame(&self, capacity: i32) -> Result<()> { + // This method is safe to call in case of pending exceptions (see chapter 2 of the spec) + let res = jni_unchecked!(self.internal, PushLocalFrame, capacity); + jni_error_code_to_result(res) + } + + /// Pops off the current local reference frame, frees all the local + /// references allocated on the current stack frame, except the `result`, + /// which is returned from this function and remains valid. + /// + /// The resulting `JObject` will be `NULL` iff `result` is `NULL`. + pub fn pop_local_frame(&self, result: JObject<'a>) -> Result> { + // This method is safe to call in case of pending exceptions (see chapter 2 of the spec) + Ok(unsafe { + JObject::from_raw(jni_unchecked!( + self.internal, + PopLocalFrame, + result.into_raw() + )) + }) + } + + /// Executes the given function in a new local reference frame, in which at least a given number + /// of references can be created. Once this method returns, all references allocated + /// in the frame are freed, except the one that the function returns, which remains valid. + /// + /// If _no_ new frames can be allocated, returns `Err` with a pending `OutOfMemoryError`. + /// + /// See also [`auto_local`](struct.JNIEnv.html#method.auto_local) method + /// and `AutoLocal` type - that approach can be more convenient in loops. + pub fn with_local_frame(&self, capacity: i32, f: F) -> Result> + where + F: FnOnce() -> Result>, + { + self.push_local_frame(capacity)?; + let res = f(); + match res { + Ok(obj) => self.pop_local_frame(obj), + Err(e) => { + self.pop_local_frame(JObject::null())?; + Err(e) + } + } + } + + /// Allocates a new object from a class descriptor without running a + /// constructor. + pub fn alloc_object<'c, T>(&self, class: T) -> Result> + where + T: Desc<'a, JClass<'c>>, + { + let class = class.lookup(self)?; + let obj = jni_non_null_call!(self.internal, AllocObject, class.into_raw()); + Ok(unsafe { JObject::from_raw(obj) }) + } + + /// Common functionality for finding methods. + #[allow(clippy::redundant_closure_call)] + fn get_method_id_base<'c, T, U, V, C, R>( + &self, + class: T, + name: U, + sig: V, + get_method: C, + ) -> Result + where + T: Desc<'a, JClass<'c>>, + U: Into, + V: Into, + C: for<'d> Fn(&JClass<'d>, &JNIString, &JNIString) -> Result, + { + let class = class.lookup(self)?; + let ffi_name = name.into(); + let sig = sig.into(); + + let res: Result = catch!({ get_method(&class, &ffi_name, &sig) }); + + match res { + Ok(m) => Ok(m), + Err(e) => match e { + Error::NullPtr(_) => { + let name: String = ffi_name.into(); + let sig: String = sig.into(); + Err(Error::MethodNotFound { name, sig }) + } + _ => Err(e), + }, + } + } + + /// Look up a method by class descriptor, name, and + /// signature. + /// + /// # Example + /// ```rust,ignore + /// 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 + where + T: Desc<'a, JClass<'c>>, + U: Into, + V: Into, + { + self.get_method_id_base(class, name, sig, |class, name, sig| { + let method_id = jni_non_null_call!( + self.internal, + GetMethodID, + class.into_raw(), + name.as_ptr(), + sig.as_ptr() + ); + Ok(unsafe { JMethodID::from_raw(method_id) }) + }) + } + + /// Look up a static method by class descriptor, name, and + /// signature. + /// + /// # Example + /// ```rust,ignore + /// let method_id: JMethodID = + /// env.get_static_method_id("java/lang/String", "valueOf", "(I)Ljava/lang/String;"); + /// ``` + pub fn get_static_method_id<'c, T, U, V>( + &self, + class: T, + name: U, + sig: V, + ) -> Result + where + T: Desc<'a, JClass<'c>>, + U: Into, + V: Into, + { + self.get_method_id_base(class, name, sig, |class, name, sig| { + let method_id = jni_non_null_call!( + self.internal, + GetStaticMethodID, + class.into_raw(), + name.as_ptr(), + sig.as_ptr() + ); + Ok(unsafe { JStaticMethodID::from_raw(method_id) }) + }) + } + + /// Look up the field ID for a class/name/type combination. + /// + /// # Example + /// ```rust,ignore + /// let field_id = env.get_field_id("com/my/Class", "intField", "I"); + /// ``` + pub fn get_field_id<'c, T, U, V>(&self, class: T, name: U, sig: V) -> Result + where + T: Desc<'a, JClass<'c>>, + U: Into, + V: Into, + { + let class = class.lookup(self)?; + let ffi_name = name.into(); + let ffi_sig = sig.into(); + + let res: Result = catch!({ + let field_id = jni_non_null_call!( + self.internal, + GetFieldID, + class.into_raw(), + ffi_name.as_ptr(), + ffi_sig.as_ptr() + ); + Ok(unsafe { JFieldID::from_raw(field_id) }) + }); + + match res { + Ok(m) => Ok(m), + Err(e) => match e { + Error::NullPtr(_) => { + let name: String = ffi_name.into(); + let sig: String = ffi_sig.into(); + Err(Error::FieldNotFound { name, sig }) + } + _ => Err(e), + }, + } + } + + /// Look up the static field ID for a class/name/type combination. + /// + /// # Example + /// ```rust,ignore + /// let field_id = env.get_static_field_id("com/my/Class", "intField", "I"); + /// ``` + pub fn get_static_field_id<'c, T, U, V>( + &self, + class: T, + name: U, + sig: V, + ) -> Result + where + T: Desc<'a, JClass<'c>>, + U: Into, + V: Into, + { + let class = class.lookup(self)?; + let ffi_name = name.into(); + let ffi_sig = sig.into(); + + let res: Result = catch!({ + let field_id = jni_non_null_call!( + self.internal, + GetStaticFieldID, + class.into_raw(), + ffi_name.as_ptr(), + ffi_sig.as_ptr() + ); + Ok(unsafe { JStaticFieldID::from_raw(field_id) }) + }); + + match res { + Ok(m) => Ok(m), + Err(e) => match e { + Error::NullPtr(_) => { + let name: String = ffi_name.into(); + let sig: String = ffi_sig.into(); + Err(Error::FieldNotFound { name, sig }) + } + _ => Err(e), + }, + } + } + + /// Get the class for an object. + pub fn get_object_class<'b, O>(&self, obj: O) -> Result> + where + O: Into>, + { + let obj = obj.into(); + non_null!(obj, "get_object_class"); + 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 + /// whether the method is valid to call on the class, whether the return + /// type is correct, or whether the number of args is valid for the method. + /// + /// Under the hood, this simply calls the `CallStaticMethodA` method + /// with the provided arguments. + pub fn call_static_method_unchecked<'c, T, U>( + &self, + class: T, + method_id: U, + ret: ReturnType, + args: &[jvalue], + ) -> Result> + where + T: Desc<'a, JClass<'c>>, + U: Desc<'a, JStaticMethodID>, + { + let class = class.lookup(self)?; + + let method_id = method_id.lookup(self)?.into_raw(); + + let class = class.into_raw(); + let jni_args = args.as_ptr(); + + // TODO clean this up + Ok(match ret { + ReturnType::Object | ReturnType::Array => { + let obj = jni_non_void_call!( + self.internal, + CallStaticObjectMethodA, + class, + method_id, + jni_args + ); + let obj = unsafe { JObject::from_raw(obj) }; + obj.into() + } + ReturnType::Primitive(p) => match p { + Primitive::Boolean => jni_non_void_call!( + self.internal, + CallStaticBooleanMethodA, + class, + method_id, + jni_args + ) + .into(), + Primitive::Char => jni_non_void_call!( + self.internal, + CallStaticCharMethodA, + class, + method_id, + jni_args + ) + .into(), + Primitive::Short => jni_non_void_call!( + self.internal, + CallStaticShortMethodA, + class, + method_id, + jni_args + ) + .into(), + Primitive::Int => jni_non_void_call!( + self.internal, + CallStaticIntMethodA, + class, + method_id, + jni_args + ) + .into(), + Primitive::Long => jni_non_void_call!( + self.internal, + CallStaticLongMethodA, + class, + method_id, + jni_args + ) + .into(), + Primitive::Float => jni_non_void_call!( + self.internal, + CallStaticFloatMethodA, + class, + method_id, + jni_args + ) + .into(), + Primitive::Double => jni_non_void_call!( + self.internal, + CallStaticDoubleMethodA, + class, + method_id, + jni_args + ) + .into(), + Primitive::Byte => jni_non_void_call!( + self.internal, + CallStaticByteMethodA, + class, + method_id, + jni_args + ) + .into(), + Primitive::Void => { + jni_void_call!( + self.internal, + CallStaticVoidMethodA, + class, + method_id, + jni_args + ); + return Ok(JValue::Void); + } + }, // JavaType::Primitive + }) // match parsed.ret + } + + /// Call an object method in an unsafe manner. This does nothing to check + /// whether the method is valid to call on the object, whether the return + /// type is correct, or whether the number of args is valid for the method. + /// + /// Under the hood, this simply calls the `CallMethodA` method with + /// the provided arguments. + pub fn call_method_unchecked( + &self, + obj: O, + method_id: T, + ret: ReturnType, + args: &[jvalue], + ) -> Result> + where + O: Into>, + T: Desc<'a, JMethodID>, + { + let method_id = method_id.lookup(self)?.into_raw(); + + let obj = obj.into().into_raw(); + + let jni_args = args.as_ptr(); + + // TODO clean this up + Ok(match ret { + 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() + } + ReturnType::Primitive(p) => match p { + Primitive::Boolean => { + jni_non_void_call!(self.internal, CallBooleanMethodA, obj, method_id, jni_args) + .into() + } + Primitive::Char => { + jni_non_void_call!(self.internal, CallCharMethodA, obj, method_id, jni_args) + .into() + } + Primitive::Short => { + jni_non_void_call!(self.internal, CallShortMethodA, obj, method_id, jni_args) + .into() + } + Primitive::Int => { + jni_non_void_call!(self.internal, CallIntMethodA, obj, method_id, jni_args) + .into() + } + Primitive::Long => { + jni_non_void_call!(self.internal, CallLongMethodA, obj, method_id, jni_args) + .into() + } + Primitive::Float => { + jni_non_void_call!(self.internal, CallFloatMethodA, obj, method_id, jni_args) + .into() + } + Primitive::Double => { + jni_non_void_call!(self.internal, CallDoubleMethodA, obj, method_id, jni_args) + .into() + } + Primitive::Byte => { + jni_non_void_call!(self.internal, CallByteMethodA, obj, method_id, jni_args) + .into() + } + Primitive::Void => { + jni_void_call!(self.internal, CallVoidMethodA, obj, method_id, jni_args); + return Ok(JValue::Void); + } + }, // JavaType::Primitive + }) // match parsed.ret + } + + /// Calls an object method safely. This comes with a number of + /// lookups/checks. It + /// + /// * Parses the type signature to find the number of arguments and return + /// type + /// * Looks up the JClass for the given object. + /// * Looks up the JMethodID for the class/name/signature combination + /// * Ensures that the number of args matches the signature + /// * Calls `call_method_unchecked` with the verified safe arguments. + /// + /// Note: this may cause a java exception if the arguments are the wrong + /// type, in addition to if the method itself throws. + pub fn call_method( + &self, + obj: O, + name: S, + sig: T, + args: &[JValue], + ) -> Result> + where + O: Into>, + S: Into, + T: Into + AsRef, + { + let obj = obj.into(); + non_null!(obj, "call_method obj argument"); + + // parse the signature + let parsed = TypeSignature::from_str(sig.as_ref())?; + if parsed.args.len() != args.len() { + return Err(Error::InvalidArgList(parsed)); + } + + let class = self.auto_local(self.get_object_class(obj)?); + + let args: Vec = 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 + /// lookups/checks. It + /// + /// * Parses the type signature to find the number of arguments and return + /// type + /// * Looks up the JMethodID for the class/name/signature combination + /// * Ensures that the number of args matches the signature + /// * Calls `call_method_unchecked` with the verified safe arguments. + /// + /// Note: this may cause a java exception if the arguments are the wrong + /// type, in addition to if the method itself throws. + pub fn call_static_method<'c, T, U, V>( + &self, + class: T, + name: U, + sig: V, + args: &[JValue], + ) -> Result> + where + T: Desc<'a, JClass<'c>>, + U: Into, + V: Into + AsRef, + { + let parsed = TypeSignature::from_str(&sig)?; + if parsed.args.len() != args.len() { + return Err(Error::InvalidArgList(parsed)); + } + + // go ahead and look up the class since it's already Copy, + // and we'll need that for the next call. + let class = class.lookup(self)?; + + let args: Vec = 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 + /// checks similar to those in `call_static_method`. + pub fn new_object<'c, T, U>( + &self, + class: T, + ctor_sig: U, + ctor_args: &[JValue], + ) -> Result> + where + T: Desc<'a, JClass<'c>>, + U: Into + AsRef, + { + // parse the signature + let parsed = TypeSignature::from_str(&ctor_sig)?; + + if parsed.args.len() != ctor_args.len() { + return Err(Error::InvalidArgList(parsed)); + } + + if parsed.ret != ReturnType::Primitive(Primitive::Void) { + return Err(Error::InvalidCtorReturn); + } + + // build strings + let class = class.lookup(self)?; + + let method_id: JMethodID = (class, ctor_sig).lookup(self)?; + + self.new_object_unchecked(class, method_id, ctor_args) + } + + /// Create a new object using a constructor. Arguments aren't checked + /// because + /// of the `JMethodID` usage. + pub fn new_object_unchecked<'c, T>( + &self, + class: T, + ctor_id: JMethodID, + ctor_args: &[JValue], + ) -> Result> + where + T: Desc<'a, JClass<'c>>, + { + let class = class.lookup(self)?; + + let jni_args: Vec = ctor_args.iter().map(|v| v.to_jni()).collect(); + let jni_args = jni_args.as_ptr(); + + let obj = jni_non_null_call!( + self.internal, + NewObjectA, + 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 + /// in the event that the object isn't actually a list, but the methods on + /// the resulting map object will. + pub fn get_list(&self, obj: JObject<'a>) -> Result> { + non_null!(obj, "get_list obj argument"); + JList::from_env(self, obj) + } + + /// Cast a JObject to a JMap. This won't throw exceptions or return errors + /// in the event that the object isn't actually a map, but the methods on + /// the resulting map object will. + pub fn get_map(&self, obj: JObject<'a>) -> Result> { + non_null!(obj, "get_map obj argument"); + JMap::from_env(self, obj) + } + + /// Get a JavaStr from a JString. This allows conversions from java string + /// objects to rust strings. + /// + /// This entails a call to `GetStringUTFChars` and only decodes java's + /// modified UTF-8 format on conversion to a rust-compatible string. + /// + /// # Panics + /// + /// This call panics when given an Object that is not a java.lang.String + pub fn get_string(&self, obj: JString<'a>) -> Result> { + non_null!(obj, "get_string obj argument"); + JavaStr::from_env(self, obj) + } + + /// Get a pointer to the character array beneath a JString. + /// + /// Array contains Java's modified UTF-8. + /// + /// # Attention + /// This will leak memory if `release_string_utf_chars` is never called. + pub fn get_string_utf_chars(&self, obj: JString) -> Result<*const c_char> { + non_null!(obj, "get_string_utf_chars obj argument"); + let ptr: *const c_char = jni_non_null_call!( + self.internal, + GetStringUTFChars, + obj.into_raw(), + ::std::ptr::null::() as *mut jboolean + ); + Ok(ptr) + } + + /// Unpin the array returned by `get_string_utf_chars`. + /// + /// # 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_raw(), arr); + Ok(()) + } + + /// Create a new java string object from a rust string. This requires a + /// re-encoding of rusts *real* UTF-8 strings to java's modified UTF-8 + /// format. + pub fn new_string>(&self, from: S) -> Result> { + let ffi_str = from.into(); + let s = jni_non_null_call!(self.internal, NewStringUTF, ffi_str.as_ptr()); + Ok(unsafe { JString::from_raw(s) }) + } + + /// Get the length of a java array + pub fn get_array_length(&self, array: jarray) -> Result { + non_null!(array, "get_array_length array argument"); + let len: jsize = jni_unchecked!(self.internal, GetArrayLength, array); + Ok(len) + } + + /// Construct a new array holding objects in class `element_class`. + /// All elements are initially set to `initial_element`. + /// + /// This function returns a local reference, that must not be allocated + /// excessively. + /// See [Java documentation][1] for details. + /// + /// [1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#global_and_local_references + pub fn new_object_array<'c, T, U>( + &self, + length: jsize, + element_class: T, + initial_element: U, + ) -> Result + where + T: Desc<'a, JClass<'c>>, + U: Into>, + { + let class = element_class.lookup(self)?; + Ok(jni_non_null_call!( + self.internal, + NewObjectArray, + length, + class.into_raw(), + initial_element.into().into_raw() + )) + } + + /// Returns an element of the `jobjectArray` array. + pub fn get_object_array_element( + &self, + array: jobjectArray, + index: jsize, + ) -> Result> { + non_null!(array, "get_object_array_element array argument"); + Ok(unsafe { + JObject::from_raw(jni_non_void_call!( + self.internal, + GetObjectArrayElement, + array, + index + )) + }) + } + + /// Sets an element of the `jobjectArray` array. + pub fn set_object_array_element( + &self, + array: jobjectArray, + index: jsize, + value: O, + ) -> Result<()> + where + O: Into>, + { + non_null!(array, "set_object_array_element array argument"); + jni_void_call!( + self.internal, + SetObjectArrayElement, + array, + index, + value.into().into_raw() + ); + Ok(()) + } + + /// Create a new java byte array from a rust byte slice. + pub fn byte_array_from_slice(&self, buf: &[u8]) -> Result { + let length = buf.len() as i32; + let bytes: jbyteArray = self.new_byte_array(length)?; + jni_unchecked!( + self.internal, + SetByteArrayRegion, + bytes, + 0, + length, + buf.as_ptr() as *const i8 + ); + Ok(bytes) + } + + /// Converts a java byte array to a rust vector of bytes. + pub fn convert_byte_array(&self, array: jbyteArray) -> Result> { + non_null!(array, "convert_byte_array array argument"); + let length = jni_non_void_call!(self.internal, GetArrayLength, array); + let mut vec = vec![0u8; length as usize]; + jni_unchecked!( + self.internal, + GetByteArrayRegion, + array, + 0, + length, + vec.as_mut_ptr() as *mut i8 + ); + Ok(vec) + } + + /// Create a new java boolean array of supplied length. + pub fn new_boolean_array(&self, length: jsize) -> Result { + let array: jbooleanArray = jni_non_null_call!(self.internal, NewBooleanArray, length); + Ok(array) + } + + /// Create a new java byte array of supplied length. + pub fn new_byte_array(&self, length: jsize) -> Result { + let array: jbyteArray = jni_non_null_call!(self.internal, NewByteArray, length); + Ok(array) + } + + /// Create a new java char array of supplied length. + pub fn new_char_array(&self, length: jsize) -> Result { + let array: jcharArray = jni_non_null_call!(self.internal, NewCharArray, length); + Ok(array) + } + + /// Create a new java short array of supplied length. + pub fn new_short_array(&self, length: jsize) -> Result { + let array: jshortArray = jni_non_null_call!(self.internal, NewShortArray, length); + Ok(array) + } + + /// Create a new java int array of supplied length. + pub fn new_int_array(&self, length: jsize) -> Result { + let array: jintArray = jni_non_null_call!(self.internal, NewIntArray, length); + Ok(array) + } + + /// Create a new java long array of supplied length. + pub fn new_long_array(&self, length: jsize) -> Result { + let array: jlongArray = jni_non_null_call!(self.internal, NewLongArray, length); + Ok(array) + } + + /// Create a new java float array of supplied length. + pub fn new_float_array(&self, length: jsize) -> Result { + let array: jfloatArray = jni_non_null_call!(self.internal, NewFloatArray, length); + Ok(array) + } + + /// Create a new java double array of supplied length. + pub fn new_double_array(&self, length: jsize) -> Result { + let array: jdoubleArray = jni_non_null_call!(self.internal, NewDoubleArray, length); + Ok(array) + } + + /// Copy elements of the java boolean array from the `start` index to the + /// `buf` slice. The number of copied elements is equal to the `buf` length. + /// + /// # Errors + /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`] + /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown, + /// and `Err` is returned. + /// + /// [`array.length`]: struct.JNIEnv.html#method.get_array_length + pub fn get_boolean_array_region( + &self, + array: jbooleanArray, + start: jsize, + buf: &mut [jboolean], + ) -> Result<()> { + non_null!(array, "get_boolean_array_region array argument"); + jni_void_call!( + self.internal, + GetBooleanArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_mut_ptr() + ); + Ok(()) + } + + /// Copy elements of the java byte array from the `start` index to the `buf` + /// slice. The number of copied elements is equal to the `buf` length. + /// + /// # Errors + /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`] + /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown, + /// and `Err` is returned. + /// + /// [`array.length`]: struct.JNIEnv.html#method.get_array_length + pub fn get_byte_array_region( + &self, + array: jbyteArray, + start: jsize, + buf: &mut [jbyte], + ) -> Result<()> { + non_null!(array, "get_byte_array_region array argument"); + jni_void_call!( + self.internal, + GetByteArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_mut_ptr() + ); + Ok(()) + } + + /// Copy elements of the java char array from the `start` index to the + /// `buf` slice. The number of copied elements is equal to the `buf` length. + /// + /// # Errors + /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`] + /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown, + /// and `Err` is returned. + /// + /// [`array.length`]: struct.JNIEnv.html#method.get_array_length + pub fn get_char_array_region( + &self, + array: jcharArray, + start: jsize, + buf: &mut [jchar], + ) -> Result<()> { + non_null!(array, "get_char_array_region array argument"); + jni_void_call!( + self.internal, + GetCharArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_mut_ptr() + ); + Ok(()) + } + + /// Copy elements of the java short array from the `start` index to the + /// `buf` slice. The number of copied elements is equal to the `buf` length. + /// + /// # Errors + /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`] + /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown, + /// and `Err` is returned. + /// + /// [`array.length`]: struct.JNIEnv.html#method.get_array_length + pub fn get_short_array_region( + &self, + array: jshortArray, + start: jsize, + buf: &mut [jshort], + ) -> Result<()> { + non_null!(array, "get_short_array_region array argument"); + jni_void_call!( + self.internal, + GetShortArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_mut_ptr() + ); + Ok(()) + } + + /// Copy elements of the java int array from the `start` index to the + /// `buf` slice. The number of copied elements is equal to the `buf` length. + /// + /// # Errors + /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`] + /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown, + /// and `Err` is returned. + /// + /// [`array.length`]: struct.JNIEnv.html#method.get_array_length + pub fn get_int_array_region( + &self, + array: jintArray, + start: jsize, + buf: &mut [jint], + ) -> Result<()> { + non_null!(array, "get_int_array_region array argument"); + jni_void_call!( + self.internal, + GetIntArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_mut_ptr() + ); + Ok(()) + } + + /// Copy elements of the java long array from the `start` index to the + /// `buf` slice. The number of copied elements is equal to the `buf` length. + /// + /// # Errors + /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`] + /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown, + /// and `Err` is returned. + /// + /// [`array.length`]: struct.JNIEnv.html#method.get_array_length + pub fn get_long_array_region( + &self, + array: jlongArray, + start: jsize, + buf: &mut [jlong], + ) -> Result<()> { + non_null!(array, "get_long_array_region array argument"); + jni_void_call!( + self.internal, + GetLongArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_mut_ptr() + ); + Ok(()) + } + + /// Copy elements of the java float array from the `start` index to the + /// `buf` slice. The number of copied elements is equal to the `buf` length. + /// + /// # Errors + /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`] + /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown, + /// and `Err` is returned. + /// + /// [`array.length`]: struct.JNIEnv.html#method.get_array_length + pub fn get_float_array_region( + &self, + array: jfloatArray, + start: jsize, + buf: &mut [jfloat], + ) -> Result<()> { + non_null!(array, "get_float_array_region array argument"); + jni_void_call!( + self.internal, + GetFloatArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_mut_ptr() + ); + Ok(()) + } + + /// Copy elements of the java double array from the `start` index to the + /// `buf` slice. The number of copied elements is equal to the `buf` length. + /// + /// # Errors + /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`] + /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown, + /// and `Err` is returned. + /// + /// [`array.length`]: struct.JNIEnv.html#method.get_array_length + pub fn get_double_array_region( + &self, + array: jdoubleArray, + start: jsize, + buf: &mut [jdouble], + ) -> Result<()> { + non_null!(array, "get_double_array_region array argument"); + jni_void_call!( + self.internal, + GetDoubleArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_mut_ptr() + ); + Ok(()) + } + + /// Copy the contents of the `buf` slice to the java boolean array at the + /// `start` index. + pub fn set_boolean_array_region( + &self, + array: jbooleanArray, + start: jsize, + buf: &[jboolean], + ) -> Result<()> { + non_null!(array, "set_boolean_array_region array argument"); + jni_void_call!( + self.internal, + SetBooleanArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_ptr() + ); + Ok(()) + } + + /// Copy the contents of the `buf` slice to the java byte array at the + /// `start` index. + pub fn set_byte_array_region( + &self, + array: jbyteArray, + start: jsize, + buf: &[jbyte], + ) -> Result<()> { + non_null!(array, "set_byte_array_region array argument"); + jni_void_call!( + self.internal, + SetByteArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_ptr() + ); + Ok(()) + } + + /// Copy the contents of the `buf` slice to the java char array at the + /// `start` index. + pub fn set_char_array_region( + &self, + array: jcharArray, + start: jsize, + buf: &[jchar], + ) -> Result<()> { + non_null!(array, "set_char_array_region array argument"); + jni_void_call!( + self.internal, + SetCharArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_ptr() + ); + Ok(()) + } + + /// Copy the contents of the `buf` slice to the java short array at the + /// `start` index. + pub fn set_short_array_region( + &self, + array: jshortArray, + start: jsize, + buf: &[jshort], + ) -> Result<()> { + non_null!(array, "set_short_array_region array argument"); + jni_void_call!( + self.internal, + SetShortArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_ptr() + ); + Ok(()) + } + + /// Copy the contents of the `buf` slice to the java int array at the + /// `start` index. + pub fn set_int_array_region(&self, array: jintArray, start: jsize, buf: &[jint]) -> Result<()> { + non_null!(array, "set_int_array_region array argument"); + jni_void_call!( + self.internal, + SetIntArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_ptr() + ); + Ok(()) + } + + /// Copy the contents of the `buf` slice to the java long array at the + /// `start` index. + pub fn set_long_array_region( + &self, + array: jlongArray, + start: jsize, + buf: &[jlong], + ) -> Result<()> { + non_null!(array, "set_long_array_region array argument"); + jni_void_call!( + self.internal, + SetLongArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_ptr() + ); + Ok(()) + } + + /// Copy the contents of the `buf` slice to the java float array at the + /// `start` index. + pub fn set_float_array_region( + &self, + array: jfloatArray, + start: jsize, + buf: &[jfloat], + ) -> Result<()> { + non_null!(array, "set_float_array_region array argument"); + jni_void_call!( + self.internal, + SetFloatArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_ptr() + ); + Ok(()) + } + + /// Copy the contents of the `buf` slice to the java double array at the + /// `start` index. + pub fn set_double_array_region( + &self, + array: jdoubleArray, + start: jsize, + buf: &[jdouble], + ) -> Result<()> { + non_null!(array, "set_double_array_region array argument"); + jni_void_call!( + self.internal, + SetDoubleArrayRegion, + array, + start, + buf.len() as jsize, + buf.as_ptr() + ); + Ok(()) + } + + /// Get a field without checking the provided type against the actual field. + pub fn get_field_unchecked(&self, obj: O, field: T, ty: ReturnType) -> Result> + where + O: Into>, + T: Desc<'a, JFieldID>, + { + let obj = obj.into(); + non_null!(obj, "get_field_typed obj argument"); + + let field = field.lookup(self)?.into_raw(); + let obj = obj.into_raw(); + + // TODO clean this up + Ok(match ty { + ReturnType::Object | ReturnType::Array => { + let obj = jni_non_void_call!(self.internal, GetObjectField, obj, field); + let obj = unsafe { JObject::from_raw(obj) }; + obj.into() + } + ReturnType::Primitive(p) => match p { + Primitive::Boolean => { + jni_unchecked!(self.internal, GetBooleanField, obj, field).into() + } + Primitive::Char => jni_unchecked!(self.internal, GetCharField, obj, field).into(), + Primitive::Short => jni_unchecked!(self.internal, GetShortField, obj, field).into(), + Primitive::Int => jni_unchecked!(self.internal, GetIntField, obj, field).into(), + Primitive::Long => jni_unchecked!(self.internal, GetLongField, obj, field).into(), + Primitive::Float => jni_unchecked!(self.internal, GetFloatField, obj, field).into(), + Primitive::Double => { + jni_unchecked!(self.internal, GetDoubleField, obj, field).into() + } + Primitive::Byte => jni_unchecked!(self.internal, GetByteField, obj, field).into(), + Primitive::Void => { + return Err(Error::WrongJValueType("void", "see java field")); + } + }, + }) + } + + /// Set a field without any type checking. + pub fn set_field_unchecked(&self, obj: O, field: T, val: JValue) -> Result<()> + where + O: Into>, + T: Desc<'a, JFieldID>, + { + let obj = obj.into(); + non_null!(obj, "set_field_typed obj argument"); + + 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_raw()); + } + // JavaType::Object + JValue::Bool(b) => { + jni_unchecked!(self.internal, SetBooleanField, obj, field, b); + } + JValue::Char(c) => { + jni_unchecked!(self.internal, SetCharField, obj, field, c); + } + JValue::Short(s) => { + jni_unchecked!(self.internal, SetShortField, obj, field, s); + } + JValue::Int(i) => { + jni_unchecked!(self.internal, SetIntField, obj, field, i); + } + JValue::Long(l) => { + jni_unchecked!(self.internal, SetLongField, obj, field, l); + } + JValue::Float(f) => { + jni_unchecked!(self.internal, SetFloatField, obj, field, f); + } + JValue::Double(d) => { + jni_unchecked!(self.internal, SetDoubleField, obj, field, d); + } + JValue::Byte(b) => { + jni_unchecked!(self.internal, SetByteField, obj, field, b); + } + JValue::Void => { + return Err(Error::WrongJValueType("void", "see java field")); + } + }; + + Ok(()) + } + + /// Get a field. Requires an object class lookup and a field id lookup + /// internally. + pub fn get_field(&self, obj: O, name: S, ty: T) -> Result> + where + O: Into>, + S: Into, + T: Into + AsRef, + { + let obj = obj.into(); + let class = self.auto_local(self.get_object_class(obj)?); + + let parsed = ReturnType::from_str(ty.as_ref())?; + + let field_id: JFieldID = (&class, name, ty).lookup(self)?; + + self.get_field_unchecked(obj, field_id, parsed) + } + + /// Set a field. Does the same lookups as `get_field` and ensures that the + /// type matches the given value. + pub fn set_field(&self, obj: O, name: S, ty: T, val: JValue) -> Result<()> + where + O: Into>, + S: Into, + T: Into + AsRef, + { + let obj = obj.into(); + let parsed = JavaType::from_str(ty.as_ref())?; + let in_type = val.primitive_type(); + + match parsed { + JavaType::Object(_) | JavaType::Array(_) => { + if in_type.is_some() { + return Err(Error::WrongJValueType(val.type_name(), "see java field")); + } + } + JavaType::Primitive(p) => { + if let Some(in_p) = in_type { + if in_p == p { + // good + } else { + return Err(Error::WrongJValueType(val.type_name(), "see java field")); + } + } else { + return Err(Error::WrongJValueType(val.type_name(), "see java field")); + } + } + JavaType::Method(_) => unimplemented!(), + } + + let class = self.auto_local(self.get_object_class(obj)?); + + self.set_field_unchecked(obj, (&class, name, ty), val) + } + + /// Get a static field without checking the provided type against the actual + /// field. + pub fn get_static_field_unchecked<'c, T, U>( + &self, + class: T, + field: U, + ty: JavaType, + ) -> Result> + where + T: Desc<'a, JClass<'c>>, + U: Desc<'a, JStaticFieldID>, + { + use JavaType::Primitive as JP; + + let class = class.lookup(self)?.into_raw(); + let field = field.lookup(self)?.into_raw(); + + let result = match ty { + JavaType::Object(_) | JavaType::Array(_) => { + 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) => { + jni_unchecked!(self.internal, GetStaticBooleanField, class, field).into() + } + JP(Primitive::Char) => { + jni_unchecked!(self.internal, GetStaticCharField, class, field).into() + } + JP(Primitive::Short) => { + jni_unchecked!(self.internal, GetStaticShortField, class, field).into() + } + JP(Primitive::Int) => { + jni_unchecked!(self.internal, GetStaticIntField, class, field).into() + } + JP(Primitive::Long) => { + jni_unchecked!(self.internal, GetStaticLongField, class, field).into() + } + JP(Primitive::Float) => { + jni_unchecked!(self.internal, GetStaticFloatField, class, field).into() + } + JP(Primitive::Double) => { + jni_unchecked!(self.internal, GetStaticDoubleField, class, field).into() + } + JP(Primitive::Byte) => { + jni_unchecked!(self.internal, GetStaticByteField, class, field).into() + } + JP(Primitive::Void) => return Err(Error::WrongJValueType("void", "see java field")), + }; + Ok(result) + } + + /// Get a static field. Requires a class lookup and a field id lookup + /// internally. + pub fn get_static_field<'c, T, U, V>(&self, class: T, field: U, sig: V) -> Result> + where + T: Desc<'a, JClass<'c>>, + U: Into, + V: Into + AsRef, + { + let ty = JavaType::from_str(sig.as_ref())?; + + // go ahead and look up the class since it's already Copy, + // and we'll need that for the next call. + let class = class.lookup(self)?; + + self.get_static_field_unchecked(class, (class, field, sig), ty) + } + + /// Set a static field. Requires a class lookup and a field id lookup internally. + pub fn set_static_field<'c, T, U>(&self, class: T, field: U, value: JValue) -> Result<()> + where + T: Desc<'a, JClass<'c>>, + U: Desc<'a, JStaticFieldID>, + { + let class = class.lookup(self)?.into_raw(); + let field = field.lookup(self)?.into_raw(); + + match value { + JValue::Object(v) => jni_unchecked!( + self.internal, + SetStaticObjectField, + class, + field, + 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), + JValue::Short(v) => jni_unchecked!(self.internal, SetStaticShortField, class, field, v), + JValue::Int(v) => jni_unchecked!(self.internal, SetStaticIntField, class, field, v), + JValue::Long(v) => jni_unchecked!(self.internal, SetStaticLongField, class, field, v), + JValue::Bool(v) => { + jni_unchecked!(self.internal, SetStaticBooleanField, class, field, v) + } + JValue::Float(v) => jni_unchecked!(self.internal, SetStaticFloatField, class, field, v), + JValue::Double(v) => { + jni_unchecked!(self.internal, SetStaticDoubleField, class, field, v) + } + JValue::Void => return Err(Error::WrongJValueType("void", "?")), + } + + Ok(()) + } + + /// 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>`. + /// + /// 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. + /// + /// 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 unsafe fn set_rust_field(&self, obj: O, field: S, rust_object: T) -> Result<()> + where + O: Into>, + S: AsRef, + T: Send + 'static, + { + let obj = obj.into(); + let class = self.auto_local(self.get_object_class(obj)?); + let field_id: JFieldID = (&class, &field, "J").lookup(self)?; + + let guard = self.lock_obj(obj)?; + + // 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, ReturnType::Primitive(Primitive::Long))? + .j()? as *mut Mutex; + if !field_ptr.is_null() { + return Err(Error::FieldAlreadySet(field.as_ref().to_owned())); + } + + let mbox = Box::new(::std::sync::Mutex::new(rust_object)); + let ptr: *mut Mutex = Box::into_raw(mbox); + + 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 [`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 unsafe fn get_rust_field(&self, obj: O, field: S) -> Result> + where + O: Into>, + S: Into, + T: Send + 'static, + { + let obj = obj.into(); + let guard = self.lock_obj(obj)?; + + let ptr = self.get_field(obj, field, "J")?.j()? as *mut Mutex; + non_null!(ptr, "rust value from Java"); + // dereferencing is safe, because we checked it for null + Ok((*ptr).lock().unwrap()) + } + + /// 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 make sure that the pointer is non-null, but still assumes that + /// the data it points to is valid for T. + #[allow(unused_variables)] + pub unsafe fn take_rust_field(&self, obj: O, field: S) -> Result + where + O: Into>, + S: AsRef, + T: Send + 'static, + { + let obj = obj.into(); + let class = self.auto_local(self.get_object_class(obj)?); + let field_id: JFieldID = (&class, &field, "J").lookup(self)?; + + let mbox = { + let guard = self.lock_obj(obj)?; + + let ptr = self + .get_field_unchecked(obj, field_id, ReturnType::Primitive(Primitive::Long))? + .j()? as *mut Mutex; + + non_null!(ptr, "rust value from Java"); + + 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 + // get a new one as long as we're in the guarded scope. + drop(mbox.try_lock()?); + + self.set_field_unchecked( + obj, + field_id, + (::std::ptr::null_mut::<()>() as sys::jlong).into(), + )?; + + mbox + }; + + Ok(mbox.into_inner().unwrap()) + } + + /// Lock a Java object. The MonitorGuard that this returns is responsible + /// for ensuring that it gets unlocked. + pub fn lock_obj(&self, obj: O) -> Result> + where + O: Into>, + { + let inner = obj.into().into_raw(); + let _ = jni_unchecked!(self.internal, MonitorEnter, inner); + + Ok(MonitorGuard { + obj: inner, + env: self.internal, + life: Default::default(), + }) + } + + /// Returns underlying `sys::JNIEnv` interface. + pub fn get_native_interface(&self) -> *mut sys::JNIEnv { + self.internal + } + + /// Returns the Java VM interface. + pub fn get_java_vm(&self) -> Result { + let mut raw = ptr::null_mut(); + let res = jni_unchecked!(self.internal, GetJavaVM, &mut raw); + jni_error_code_to_result(res)?; + unsafe { JavaVM::from_raw(raw) } + } + + /// Ensures that at least a given number of local references can be created + /// in the current thread. + pub fn ensure_local_capacity(&self, capacity: jint) -> Result<()> { + jni_void_call!(self.internal, EnsureLocalCapacity, capacity); + Ok(()) + } + + /// Bind function pointers to native methods of class + /// according to method name and signature. + /// For details see [documentation](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives). + pub fn register_native_methods<'c, T>(&self, class: T, methods: &[NativeMethod]) -> Result<()> + where + T: Desc<'a, JClass<'c>>, + { + let class = class.lookup(self)?; + let jni_native_methods: Vec = methods + .iter() + .map(|nm| JNINativeMethod { + name: nm.name.as_ptr() as *mut c_char, + signature: nm.sig.as_ptr() as *mut c_char, + fnPtr: nm.fn_ptr, + }) + .collect(); + let res = jni_non_void_call!( + self.internal, + RegisterNatives, + class.into_raw(), + jni_native_methods.as_ptr(), + jni_native_methods.len() as jint + ); + jni_error_code_to_result(res) + } + + /// Unbind all native methods of class. + pub fn unregister_native_methods<'c, T>(&self, class: T) -> Result<()> + where + T: Desc<'a, JClass<'c>>, + { + let class = class.lookup(self)?; + let res = jni_non_void_call!(self.internal, UnregisterNatives, class.into_raw()); + jni_error_code_to_result(res) + } + + /// Return an AutoArray of the given Java array. + /// + /// The result is valid until the AutoArray object goes out of scope, when the + /// release happens automatically according to the mode parameter. + /// + /// Since the returned array may be a copy of the Java array, changes made to the + /// returned array will not necessarily be reflected in the original array until + /// the corresponding Release*ArrayElements JNI method is called. + /// AutoArray has a commit() method, to force a copy of the array if needed (and without + /// releasing it). + + /// Prefer to use the convenience wrappers: + /// [`get_int_array_elements`](struct.JNIEnv.html#method.get_int_array_elements) + /// [`get_long_array_elements`](struct.JNIEnv.html#method.get_long_array_elements) + /// [`get_byte_array_elements`](struct.JNIEnv.html#method.get_byte_array_elements) + /// [`get_boolean_array_elements`](struct.JNIEnv.html#method.get_boolean_array_elements) + /// [`get_char_array_elements`](struct.JNIEnv.html#method.get_char_array_elements) + /// [`get_short_array_elements`](struct.JNIEnv.html#method.get_short_array_elements) + /// [`get_float_array_elements`](struct.JNIEnv.html#method.get_float_array_elements) + /// [`get_double_array_elements`](struct.JNIEnv.html#method.get_double_array_elements) + /// And the associated [`AutoArray`](struct.objects.AutoArray) struct. + pub fn get_array_elements( + &self, + array: jarray, + mode: ReleaseMode, + ) -> Result> { + non_null!(array, "get_array_elements array argument"); + AutoArray::new(self, unsafe { JObject::from_raw(array) }, mode) + } + + /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) + pub fn get_int_array_elements( + &self, + array: jintArray, + mode: ReleaseMode, + ) -> Result> { + self.get_array_elements(array, mode) + } + + /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) + pub fn get_long_array_elements( + &self, + array: jlongArray, + mode: ReleaseMode, + ) -> Result> { + self.get_array_elements(array, mode) + } + + /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) + pub fn get_byte_array_elements( + &self, + array: jbyteArray, + mode: ReleaseMode, + ) -> Result> { + self.get_array_elements(array, mode) + } + + /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) + pub fn get_boolean_array_elements( + &self, + array: jbooleanArray, + mode: ReleaseMode, + ) -> Result> { + self.get_array_elements(array, mode) + } + + /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) + pub fn get_char_array_elements( + &self, + array: jcharArray, + mode: ReleaseMode, + ) -> Result> { + self.get_array_elements(array, mode) + } + + /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) + pub fn get_short_array_elements( + &self, + array: jshortArray, + mode: ReleaseMode, + ) -> Result> { + self.get_array_elements(array, mode) + } + + /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) + pub fn get_float_array_elements( + &self, + array: jfloatArray, + mode: ReleaseMode, + ) -> Result> { + self.get_array_elements(array, mode) + } + + /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements) + pub fn get_double_array_elements( + &self, + array: jdoubleArray, + mode: ReleaseMode, + ) -> Result> { + self.get_array_elements(array, mode) + } + + /// Return an AutoPrimitiveArray of the given Java primitive array. + /// + /// The result is valid until the corresponding AutoPrimitiveArray object goes out of scope, + /// when the release happens automatically according to the mode parameter. + /// + /// Given that Critical sections must be as short as possible, and that they come with a + /// number of important restrictions (see GetPrimitiveArrayCritical JNI doc), use this + /// wrapper wisely, to avoid holding the array longer that strictly necessary. + /// In any case, you can: + /// - Use std::mem::drop explicitly, to force / anticipate resource release. + /// - Use a nested scope, to release the array at the nested scope's exit. + /// + /// Since the returned array may be a copy of the Java array, changes made to the + /// returned array will not necessarily be reflected in the original array until + /// ReleasePrimitiveArrayCritical is called; which happens at AutoPrimitiveArray + /// destruction. + /// + /// If the given array is `null`, an `Error::NullPtr` is returned. + /// + /// See also [`get_byte_array_elements`](struct.JNIEnv.html#method.get_array_elements) + pub fn get_primitive_array_critical( + &self, + array: jarray, + mode: ReleaseMode, + ) -> Result { + non_null!(array, "get_primitive_array_critical array argument"); + let mut is_copy: jboolean = 0xff; + // Even though this method may throw OoME, use `jni_unchecked` + // instead of `jni_non_null_call` to remove (a slight) overhead + // of exception checking. An error will still be detected as a `null` + // result inside AutoPrimitiveArray ctor; and, as this method is unlikely + // to create a copy, an OoME is highly unlikely. + let ptr = jni_unchecked!( + self.internal, + GetPrimitiveArrayCritical, + array, + &mut is_copy + ); + AutoPrimitiveArray::new( + self, + unsafe { JObject::from_raw(array) }, + ptr, + mode, + is_copy == sys::JNI_TRUE, + ) + } +} + +/// Native method descriptor. +pub struct NativeMethod { + /// Name of method. + pub name: JNIString, + /// Method signature. + pub sig: JNIString, + /// Pointer to native function with signature + /// `fn(env: JNIEnv, class: JClass, ...arguments according to sig) -> RetType` + /// for static methods or + /// `fn(env: JNIEnv, object: JObject, ...arguments according to sig) -> RetType` + /// for instance methods. + pub fn_ptr: *mut c_void, +} + +/// Guard for a lock on a java object. This gets returned from the `lock_obj` +/// method. +pub struct MonitorGuard<'a> { + obj: sys::jobject, + env: *mut sys::JNIEnv, + life: PhantomData<&'a ()>, +} + +impl<'a> Drop for MonitorGuard<'a> { + fn drop(&mut self) { + let res: Result<()> = catch!({ + jni_unchecked!(self.env, MonitorExit, self.obj); + Ok(()) + }); + + if let Err(e) = res { + warn!("error releasing java monitor: {}", e) + } + } +} diff --git a/0.20.0/src/wrapper/macros.rs b/0.20.0/src/wrapper/macros.rs new file mode 100644 index 0000000..68604ed --- /dev/null +++ b/0.20.0/src/wrapper/macros.rs @@ -0,0 +1,142 @@ +// A JNI call that is expected to return a non-null pointer when successful. +// If a null pointer is returned, it is converted to an Err. +// Returns Err if there is a pending exception after the call. +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")) + }) +} + +// A non-void JNI call. May return anything — primitives, references, error codes. +// Returns Err if there is a pending exception after the call. +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),*) + }; + + check_exception!($jnienv); + res + }) +} + +macro_rules! non_null { + ( $obj:expr, $ctx:expr ) => { + if $obj.is_null() { + return Err($crate::errors::Error::NullPtr($ctx)); + } else { + $obj + } + }; +} + +// A void JNI call. +// Returns Err if there is a pending exception after the call. +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),*) + }; + + check_exception!($jnienv); + }) +} + +// A JNI call that does not check for exceptions or verify +// error codes (if any). +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),*) + } + }) +} + +macro_rules! jni_method { + ( $jnienv:expr, $name:tt ) => {{ + log::trace!("looking up jni method {}", stringify!($name)); + let env = $jnienv; + match deref!(deref!(env, "JNIEnv"), "*JNIEnv").$name { + Some(method) => { + log::trace!("found jni method"); + method + } + None => { + log::trace!("jnienv method not defined, returning error"); + return Err($crate::errors::Error::JNIEnvMethodNotFound(stringify!( + $name + ))); + } + } + }}; +} + +macro_rules! check_exception { + ( $jnienv:expr ) => { + log::trace!("checking for exception"); + let check = { jni_unchecked!($jnienv, ExceptionCheck) } == $crate::sys::JNI_TRUE; + if check { + log::trace!("exception found, returning error"); + return Err($crate::errors::Error::JavaException); + } + log::trace!("no exception found"); + }; +} + +macro_rules! catch { + ( move $b:block ) => { + (move || $b)() + }; + ( $b:block ) => { + (|| $b)() + }; +} + +macro_rules! java_vm_unchecked { + ( $java_vm:expr, $name:tt $(, $args:expr )* ) => ({ + log::trace!("calling unchecked JavaVM method: {}", stringify!($name)); + java_vm_method!($java_vm, $name)($java_vm, $($args),*) + }) +} + +macro_rules! java_vm_method { + ( $jnienv:expr, $name:tt ) => {{ + log::trace!("looking up JavaVM method {}", stringify!($name)); + let env = $jnienv; + match deref!(deref!(env, "JavaVM"), "*JavaVM").$name { + Some(meth) => { + log::trace!("found JavaVM method"); + meth + } + None => { + log::trace!("JavaVM method not defined, returning error"); + return Err($crate::errors::Error::JavaVMMethodNotFound(stringify!( + $name + ))); + } + } + }}; +} + +macro_rules! deref { + ( $obj:expr, $ctx:expr ) => { + if $obj.is_null() { + return Err($crate::errors::Error::NullDeref($ctx)); + } else { + #[allow(unused_unsafe)] + unsafe { + *$obj + } + } + }; +} diff --git a/0.20.0/src/wrapper/objects/auto_array.rs b/0.20.0/src/wrapper/objects/auto_array.rs new file mode 100644 index 0000000..2c73fac --- /dev/null +++ b/0.20.0/src/wrapper/objects/auto_array.rs @@ -0,0 +1,137 @@ +use crate::sys::jsize; +use log::error; + +use std::ptr::NonNull; + +use crate::objects::release_mode::ReleaseMode; +use crate::sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort}; +use crate::{errors::*, objects::JObject, sys, JNIEnv}; + +/// Trait to define type array access/release +pub trait TypeArray { + /// getter + fn get(env: &JNIEnv, obj: JObject, is_copy: &mut jboolean) -> Result<*mut Self>; + + /// releaser + fn release(env: &JNIEnv, obj: JObject, ptr: NonNull, mode: i32) -> Result<()>; +} + +// TypeArray builder +macro_rules! type_array { + ( $jni_type:ty, $jni_get:tt, $jni_release:tt ) => { + /// $jni_type array access/release impl + impl TypeArray for $jni_type { + /// Get Java $jni_type array + fn get(env: &JNIEnv, obj: JObject, is_copy: &mut jboolean) -> Result<*mut Self> { + let internal = env.get_native_interface(); + // Even though this method may throw OoME, use `jni_unchecked` + // instead of `jni_non_null_call` to remove (a slight) overhead + // of exception checking. An error will still be detected as a `null` + // result inside AutoArray ctor. Also, modern Hotspot in case of lack + // of memory will return null and won't throw an exception: + // https://sourcegraph.com/github.com/openjdk/jdk/-/blob/src/hotspot/share/memory/allocation.hpp#L488-489 + let res = jni_unchecked!(internal, $jni_get, *obj, is_copy); + Ok(res) + } + + /// Release Java $jni_type array + fn release(env: &JNIEnv, obj: JObject, ptr: NonNull, mode: i32) -> Result<()> { + let internal = env.get_native_interface(); + jni_unchecked!(internal, $jni_release, *obj, ptr.as_ptr(), mode as i32); + Ok(()) + } + } + }; +} + +type_array!(jint, GetIntArrayElements, ReleaseIntArrayElements); +type_array!(jlong, GetLongArrayElements, ReleaseLongArrayElements); +type_array!(jbyte, GetByteArrayElements, ReleaseByteArrayElements); +type_array!( + jboolean, + GetBooleanArrayElements, + ReleaseBooleanArrayElements +); +type_array!(jchar, GetCharArrayElements, ReleaseCharArrayElements); +type_array!(jshort, GetShortArrayElements, ReleaseShortArrayElements); +type_array!(jfloat, GetFloatArrayElements, ReleaseFloatArrayElements); +type_array!(jdouble, GetDoubleArrayElements, ReleaseDoubleArrayElements); + +/// Auto-release wrapper for pointer-based generic arrays. +/// +/// This wrapper is used to wrap pointers returned by GetArrayElements. +/// While wrapped, the object can be accessed via the `From` impl. +/// +/// AutoArray provides automatic array release through a call to appropriate +/// ReleaseArrayElements when it goes out of scope. +pub struct AutoArray<'a, T: TypeArray> { + obj: JObject<'a>, + ptr: NonNull, + mode: ReleaseMode, + is_copy: bool, + env: JNIEnv<'a>, +} + +impl<'a, T: TypeArray> AutoArray<'a, T> { + pub(crate) fn new(env: &JNIEnv<'a>, obj: JObject<'a>, mode: ReleaseMode) -> Result { + let mut is_copy: jboolean = 0xff; + Ok(AutoArray { + obj, + ptr: { + let ptr = T::get(env, obj, &mut is_copy)?; + NonNull::new(ptr).ok_or(Error::NullPtr("Non-null ptr expected"))? + }, + mode, + is_copy: is_copy == sys::JNI_TRUE, + env: *env, + }) + } + + /// Get a reference to the wrapped pointer + pub fn as_ptr(&self) -> *mut T { + self.ptr.as_ptr() + } + + /// Commits the changes to the array, if it is a copy + pub fn commit(&self) -> Result<()> { + self.release_array_elements(sys::JNI_COMMIT) + } + + fn release_array_elements(&self, mode: i32) -> Result<()> { + T::release(&self.env, self.obj, self.ptr, mode) + } + + /// Don't commit the changes to the array on release (if it is a copy). + /// This has no effect if the array is not a copy. + /// This method is useful to change the release mode of an array originally created + /// with `ReleaseMode::CopyBack`. + pub fn discard(&mut self) { + self.mode = ReleaseMode::NoCopyBack; + } + + /// Indicates if the array is a copy or not + pub fn is_copy(&self) -> bool { + self.is_copy + } + + /// Returns the array size + pub fn size(&self) -> Result { + self.env.get_array_length(*self.obj) + } +} + +impl<'a, T: TypeArray> Drop for AutoArray<'a, T> { + fn drop(&mut self) { + let res = self.release_array_elements(self.mode as i32); + match res { + Ok(()) => {} + Err(e) => error!("error releasing array: {:#?}", e), + } + } +} + +impl<'a, T: TypeArray> From<&'a AutoArray<'a, T>> for *mut T { + fn from(other: &'a AutoArray) -> *mut T { + other.as_ptr() + } +} diff --git a/0.20.0/src/wrapper/objects/auto_local.rs b/0.20.0/src/wrapper/objects/auto_local.rs new file mode 100644 index 0000000..6d6cd2f --- /dev/null +++ b/0.20.0/src/wrapper/objects/auto_local.rs @@ -0,0 +1,80 @@ +use std::mem; + +use log::debug; + +use crate::{objects::JObject, JNIEnv}; + +/// Auto-delete wrapper for local refs. +/// +/// Anything passed to a foreign method _and_ returned from JNI methods is considered a local ref +/// unless it is specified otherwise. +/// These refs are automatically deleted once the foreign method exits, but it's possible that +/// they may reach the JVM-imposed limit before that happens. +/// +/// This wrapper provides automatic local ref deletion when it goes out of +/// scope. +/// +/// NOTE: This comes with some potential safety risks. DO NOT use this to wrap +/// something unless you're SURE it won't be used after this wrapper gets +/// dropped. Otherwise, you'll get a nasty JVM crash. +/// +/// See also the [JNI specification][spec-references] for details on referencing Java objects +/// and some [extra information][android-jni-references]. +/// +/// [spec-references]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/design.html#referencing-java-objects +/// [android-jni-references]: https://developer.android.com/training/articles/perf-jni#local-and-global-references +pub struct AutoLocal<'a: 'b, 'b> { + obj: JObject<'a>, + env: &'b JNIEnv<'a>, +} + +impl<'a, 'b> AutoLocal<'a, 'b> { + /// Creates a new auto-delete wrapper for a local ref. + /// + /// Once this wrapper goes out of scope, the `delete_local_ref` will be + /// called on the object. While wrapped, the object can be accessed via + /// the `Deref` impl. + pub fn new(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Self { + AutoLocal { obj, env } + } + + /// Forget the wrapper, returning the original object. + /// + /// This prevents `delete_local_ref` from being called when the `AutoLocal` + /// gets + /// dropped. You must either remember to delete the local ref manually, or + /// be + /// ok with it getting deleted once the foreign method returns. + pub fn forget(self) -> JObject<'a> { + let obj = self.obj; + mem::forget(self); + obj + } + + /// Get a reference to the wrapped object + /// + /// Unlike `forget`, this ensures the wrapper from being dropped while the + /// returned `JObject` is still live. + pub fn as_obj<'c>(&self) -> JObject<'c> + where + 'a: 'c, + { + self.obj + } +} + +impl<'a, 'b> Drop for AutoLocal<'a, 'b> { + fn drop(&mut self) { + let res = self.env.delete_local_ref(self.obj); + match res { + Ok(()) => {} + Err(e) => debug!("error dropping global ref: {:#?}", e), + } + } +} + +impl<'a> From<&'a AutoLocal<'a, '_>> for JObject<'a> { + fn from(other: &'a AutoLocal) -> JObject<'a> { + other.as_obj() + } +} diff --git a/0.20.0/src/wrapper/objects/auto_primitive_array.rs b/0.20.0/src/wrapper/objects/auto_primitive_array.rs new file mode 100644 index 0000000..d67b1d7 --- /dev/null +++ b/0.20.0/src/wrapper/objects/auto_primitive_array.rs @@ -0,0 +1,90 @@ +use log::debug; + +use crate::sys::jsize; +use crate::wrapper::objects::ReleaseMode; +use crate::{errors::*, objects::JObject, JNIEnv}; +use std::os::raw::c_void; +use std::ptr::NonNull; + +/// Auto-release wrapper for pointer-based primitive arrays. +/// +/// This wrapper is used to wrap pointers returned by GetPrimitiveArrayCritical. +/// While wrapped, the object can be accessed via the `From` impl. +/// +/// AutoPrimitiveArray provides automatic array release through a call to +/// ReleasePrimitiveArrayCritical when it goes out of scope. +pub struct AutoPrimitiveArray<'a: 'b, 'b> { + obj: JObject<'a>, + ptr: NonNull, + mode: ReleaseMode, + is_copy: bool, + env: &'b JNIEnv<'a>, +} + +impl<'a, 'b> AutoPrimitiveArray<'a, 'b> { + pub(crate) fn new( + env: &'b JNIEnv<'a>, + obj: JObject<'a>, + ptr: *mut c_void, + mode: ReleaseMode, + is_copy: bool, + ) -> Result { + Ok(AutoPrimitiveArray { + obj, + ptr: NonNull::new(ptr).ok_or(Error::NullPtr("Non-null ptr expected"))?, + mode, + is_copy, + env, + }) + } + + /// Get a reference to the wrapped pointer + pub fn as_ptr(&self) -> *mut c_void { + self.ptr.as_ptr() + } + + fn release_primitive_array_critical(&mut self, mode: i32) -> Result<()> { + jni_unchecked!( + self.env.get_native_interface(), + ReleasePrimitiveArrayCritical, + *self.obj, + self.ptr.as_mut(), + mode + ); + Ok(()) + } + + /// Don't copy the changes to the array on release (if it is a copy). + /// This has no effect if the array is not a copy. + /// This method is useful to change the release mode of an array originally created + /// with `ReleaseMode::CopyBack`. + pub fn discard(&mut self) { + self.mode = ReleaseMode::NoCopyBack; + } + + /// Indicates if the array is a copy or not + pub fn is_copy(&self) -> bool { + self.is_copy + } + + /// Returns the array size + pub fn size(&self) -> Result { + self.env.get_array_length(*self.obj) + } +} + +impl<'a, 'b> Drop for AutoPrimitiveArray<'a, 'b> { + fn drop(&mut self) { + let res = self.release_primitive_array_critical(self.mode as i32); + match res { + Ok(()) => {} + Err(e) => debug!("error releasing primitive array: {:#?}", e), + } + } +} + +impl<'a> From<&'a AutoPrimitiveArray<'a, '_>> for *mut c_void { + fn from(other: &'a AutoPrimitiveArray) -> *mut c_void { + other.as_ptr() + } +} diff --git a/0.20.0/src/wrapper/objects/global_ref.rs b/0.20.0/src/wrapper/objects/global_ref.rs new file mode 100644 index 0000000..7e8b81c --- /dev/null +++ b/0.20.0/src/wrapper/objects/global_ref.rs @@ -0,0 +1,107 @@ +use std::{convert::From, sync::Arc}; + +use log::{debug, warn}; + +use crate::{errors::Result, objects::JObject, sys, JNIEnv, JavaVM}; + +/// A global JVM reference. These are "pinned" by the garbage collector and are +/// guaranteed to not get collected until released. Thus, this is allowed to +/// outlive the `JNIEnv` that it came from and can be used in other threads. +/// +/// `GlobalRef` can be cloned to use _the same_ global reference in different +/// contexts. If you want to create yet another global ref to the same java object +/// you may call `JNIEnv#new_global_ref` just like you do when create `GlobalRef` +/// from a local reference. +/// +/// Underlying global reference will be dropped, when the last instance +/// of `GlobalRef` leaves its scope. +/// +/// It is _recommended_ that a native thread that drops the global reference is attached +/// to the Java thread (i.e., has an instance of `JNIEnv`). If the native thread is *not* attached, +/// the `GlobalRef#drop` will print a warning and implicitly `attach` and `detach` it, which +/// significantly affects performance. + +#[derive(Clone, Debug)] +pub struct GlobalRef { + inner: Arc, +} + +#[derive(Debug)] +struct GlobalRefGuard { + obj: JObject<'static>, + vm: JavaVM, +} + +unsafe impl Send for GlobalRef {} +unsafe impl Sync for GlobalRef {} + +impl<'a> From<&'a GlobalRef> for JObject<'a> { + fn from(other: &'a GlobalRef) -> JObject<'a> { + other.as_obj() + } +} + +impl GlobalRef { + /// Creates a new wrapper for a global reference. + /// + /// # Safety + /// + /// Expects a valid raw global reference that should be created with `NewGlobalRef` JNI function. + pub(crate) unsafe fn from_raw(vm: JavaVM, raw_global_ref: sys::jobject) -> Self { + GlobalRef { + inner: Arc::new(GlobalRefGuard::from_raw(vm, raw_global_ref)), + } + } + + /// Get the object from the global ref + /// + /// This borrows the ref and prevents it from being dropped as long as the + /// JObject sticks around. + pub fn as_obj(&self) -> JObject { + self.inner.as_obj() + } +} + +impl GlobalRefGuard { + /// Creates a new global reference guard. This assumes that `NewGlobalRef` + /// has already been called. + unsafe fn from_raw(vm: JavaVM, obj: sys::jobject) -> Self { + GlobalRefGuard { + obj: JObject::from_raw(obj), + vm, + } + } + + /// Get the object from the global ref + /// + /// This borrows the ref and prevents it from being dropped as long as the + /// JObject sticks around. + pub fn as_obj(&self) -> JObject { + self.obj + } +} + +impl Drop for GlobalRefGuard { + fn drop(&mut self) { + fn drop_impl(env: &JNIEnv, global_ref: JObject) -> Result<()> { + let internal = env.get_native_interface(); + // This method is safe to call in case of pending exceptions (see chapter 2 of the spec) + jni_unchecked!(internal, DeleteGlobalRef, global_ref.into_raw()); + Ok(()) + } + + let res = match self.vm.get_env() { + Ok(env) => drop_impl(&env, self.as_obj()), + Err(_) => { + warn!("Dropping a GlobalRef in a detached thread. Fix your code if this message appears frequently (see the GlobalRef docs)."); + self.vm + .attach_current_thread() + .and_then(|env| drop_impl(&env, self.as_obj())) + } + }; + + if let Err(err) = res { + debug!("error dropping global ref: {:#?}", err); + } + } +} diff --git a/0.20.0/src/wrapper/objects/jbytebuffer.rs b/0.20.0/src/wrapper/objects/jbytebuffer.rs new file mode 100644 index 0000000..fd6cb60 --- /dev/null +++ b/0.20.0/src/wrapper/objects/jbytebuffer.rs @@ -0,0 +1,49 @@ +use crate::{objects::JObject, sys::jobject}; + +/// Lifetime'd representation of a `jobject` that is an instance of the +/// ByteBuffer Java class. Just a `JObject` wrapped in a new class. +#[repr(transparent)] +#[derive(Clone, Copy, Debug)] +pub struct JByteBuffer<'a>(JObject<'a>); + +impl<'a> ::std::ops::Deref for JByteBuffer<'a> { + type Target = JObject<'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> From> for JObject<'a> { + fn from(other: JByteBuffer) -> JObject { + other.0 + } +} + +impl<'a> From> for JByteBuffer<'a> { + 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/0.20.0/src/wrapper/objects/jclass.rs b/0.20.0/src/wrapper/objects/jclass.rs new file mode 100644 index 0000000..aa7194b --- /dev/null +++ b/0.20.0/src/wrapper/objects/jclass.rs @@ -0,0 +1,53 @@ +use crate::{ + objects::JObject, + sys::{jclass, jobject}, +}; + +/// Lifetime'd representation of a `jclass`. Just a `JObject` wrapped in a new +/// class. +#[repr(transparent)] +#[derive(Clone, Copy, Debug)] +pub struct JClass<'a>(JObject<'a>); + +impl<'a> ::std::ops::Deref for JClass<'a> { + type Target = JObject<'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> From> for JObject<'a> { + fn from(other: JClass) -> JObject { + other.0 + } +} + +/// This conversion assumes that the `JObject` is a pointer to a class object. +impl<'a> From> for JClass<'a> { + 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/0.20.0/src/wrapper/objects/jfieldid.rs b/0.20.0/src/wrapper/objects/jfieldid.rs new file mode 100644 index 0000000..c445ec6 --- /dev/null +++ b/0.20.0/src/wrapper/objects/jfieldid.rs @@ -0,0 +1,48 @@ +use crate::sys::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 { + internal: jfieldID, +} + +// 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 } + } + + /// Unwrap to the internal jni type. + pub fn into_raw(self) -> jfieldID { + self.internal + } +} diff --git a/0.20.0/src/wrapper/objects/jlist.rs b/0.20.0/src/wrapper/objects/jlist.rs new file mode 100644 index 0000000..1f07f71 --- /dev/null +++ b/0.20.0/src/wrapper/objects/jlist.rs @@ -0,0 +1,202 @@ +use crate::{ + errors::*, + objects::{JMethodID, JObject, JValue}, + signature::{Primitive, ReturnType}, + sys::jint, + JNIEnv, +}; + +/// Wrapper for JObjects that implement `java/util/List`. Provides methods to get, +/// add, and remove elements. +/// +/// Looks up the class and method ids on creation rather than for every method +/// call. +pub struct JList<'a: 'b, 'b> { + internal: JObject<'a>, + get: JMethodID, + add: JMethodID, + add_idx: JMethodID, + remove: JMethodID, + size: JMethodID, + env: &'b JNIEnv<'a>, +} + +impl<'a: 'b, 'b> ::std::ops::Deref for JList<'a, 'b> { + type Target = JObject<'a>; + + fn deref(&self) -> &Self::Target { + &self.internal + } +} + +impl<'a: 'b, 'b> From> for JObject<'a> { + fn from(other: JList<'a, 'b>) -> JObject<'a> { + other.internal + } +} + +impl<'a: 'b, 'b> JList<'a, 'b> { + /// Create a map from the environment and an object. This looks up the + /// necessary class and method ids to call all of the methods on it so that + /// exra work doesn't need to be done on every method call. + pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result> { + let class = env.auto_local(env.find_class("java/util/List")?); + + let get = env.get_method_id(&class, "get", "(I)Ljava/lang/Object;")?; + let add = env.get_method_id(&class, "add", "(Ljava/lang/Object;)Z")?; + let add_idx = env.get_method_id(&class, "add", "(ILjava/lang/Object;)V")?; + let remove = env.get_method_id(&class, "remove", "(I)Ljava/lang/Object;")?; + let size = env.get_method_id(&class, "size", "()I")?; + + Ok(JList { + internal: obj, + get, + add, + add_idx, + remove, + size, + env, + }) + } + + /// Look up the value for a key. Returns `Some` if it's found and `None` if + /// a null pointer would be returned. + pub fn get(&self, idx: jint) -> Result>> { + let result = self.env.call_method_unchecked( + self.internal, + self.get, + ReturnType::Object, + &[JValue::from(idx).to_jni()], + ); + + match result { + Ok(val) => Ok(Some(val.l()?)), + Err(e) => match e { + Error::NullPtr(_) => Ok(None), + _ => Err(e), + }, + } + } + + /// Append an element to the list + pub fn add(&self, value: JObject<'a>) -> Result<()> { + let result = self.env.call_method_unchecked( + self.internal, + self.add, + ReturnType::Primitive(Primitive::Boolean), + &[JValue::from(value).to_jni()], + ); + + let _ = result?; + Ok(()) + } + + /// Insert an element at a specific index + pub fn insert(&self, idx: jint, value: JObject<'a>) -> Result<()> { + let result = self.env.call_method_unchecked( + self.internal, + self.add_idx, + ReturnType::Primitive(Primitive::Void), + &[JValue::from(idx).to_jni(), JValue::from(value).to_jni()], + ); + + let _ = result?; + Ok(()) + } + + /// Remove an element from the list by index + pub fn remove(&self, idx: jint) -> Result>> { + let result = self.env.call_method_unchecked( + self.internal, + self.remove, + ReturnType::Object, + &[JValue::from(idx).to_jni()], + ); + + match result { + Ok(val) => Ok(Some(val.l()?)), + Err(e) => match e { + Error::NullPtr(_) => Ok(None), + _ => Err(e), + }, + } + } + + /// Get the size of the list + pub fn size(&self) -> Result { + let result = self.env.call_method_unchecked( + self.internal, + self.size, + ReturnType::Primitive(Primitive::Int), + &[], + ); + + result.and_then(|v| v.i()) + } + + /// Pop the last element from the list + /// + /// Note that this calls `size()` to determine the last index. + pub fn pop(&self) -> Result>> { + let size = self.size()?; + if size == 0 { + return Ok(None); + } + + let result = self.env.call_method_unchecked( + self.internal, + self.remove, + ReturnType::Object, + &[JValue::from(size - 1).to_jni()], + ); + + match result { + Ok(val) => Ok(Some(val.l()?)), + Err(e) => match e { + Error::NullPtr(_) => Ok(None), + _ => Err(e), + }, + } + } + + /// Get key/value iterator for the map. This is done by getting the + /// `EntrySet` from java and iterating over it. + pub fn iter(&self) -> Result> { + Ok(JListIter { + list: self, + current: 0, + size: self.size()?, + }) + } +} + +/// An iterator over the keys and values in a map. +/// +/// TODO: make the iterator implementation for java iterators its own thing +/// and generic enough to use elsewhere. +pub struct JListIter<'a: 'b, 'b: 'c, 'c> { + list: &'c JList<'a, 'b>, + current: jint, + size: jint, +} + +impl<'a: 'b, 'b: 'c, 'c> Iterator for JListIter<'a, 'b, 'c> { + type Item = JObject<'a>; + + fn next(&mut self) -> Option { + if self.current == self.size { + return None; + } + let res = self.list.get(self.current); + match res { + Ok(elem) => { + self.current += 1; + elem + } + Err(_) => { + self.current = self.size; + None + } + } + } +} diff --git a/0.20.0/src/wrapper/objects/jmap.rs b/0.20.0/src/wrapper/objects/jmap.rs new file mode 100644 index 0000000..bc391e3 --- /dev/null +++ b/0.20.0/src/wrapper/objects/jmap.rs @@ -0,0 +1,246 @@ +use crate::{ + errors::*, + objects::{AutoLocal, JMethodID, JObject, JValue}, + signature::{Primitive, ReturnType}, + JNIEnv, +}; + +/// Wrapper for JObjects that implement `java/util/Map`. Provides methods to get +/// and set entries and a way to iterate over key/value pairs. +/// +/// Looks up the class and method ids on creation rather than for every method +/// call. +pub struct JMap<'a: 'b, 'b> { + internal: JObject<'a>, + class: AutoLocal<'a, 'b>, + get: JMethodID, + put: JMethodID, + remove: JMethodID, + env: &'b JNIEnv<'a>, +} + +impl<'a: 'b, 'b> ::std::ops::Deref for JMap<'a, 'b> { + type Target = JObject<'a>; + + fn deref(&self) -> &Self::Target { + &self.internal + } +} + +impl<'a: 'b, 'b> From> for JObject<'a> { + fn from(other: JMap<'a, 'b>) -> JObject<'a> { + other.internal + } +} + +impl<'a: 'b, 'b> JMap<'a, 'b> { + /// Create a map from the environment and an object. This looks up the + /// necessary class and method ids to call all of the methods on it so that + /// exra work doesn't need to be done on every method call. + pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result> { + let class = env.auto_local(env.find_class("java/util/Map")?); + + let get = env.get_method_id(&class, "get", "(Ljava/lang/Object;)Ljava/lang/Object;")?; + let put = env.get_method_id( + &class, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;\ + )Ljava/lang/Object;", + )?; + + let remove = + env.get_method_id(&class, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;")?; + + Ok(JMap { + internal: obj, + class, + get, + put, + remove, + env, + }) + } + + /// Look up the value for a key. Returns `Some` if it's found and `None` if + /// a null pointer would be returned. + pub fn get(&self, key: JObject<'a>) -> Result>> { + let result = self.env.call_method_unchecked( + self.internal, + self.get, + ReturnType::Object, + &[JValue::from(key).to_jni()], + ); + + match result { + Ok(val) => Ok(Some(val.l()?)), + Err(e) => match e { + Error::NullPtr(_) => Ok(None), + _ => Err(e), + }, + } + } + + /// Look up the value for a key. Returns `Some` with the old value if the + /// key already existed and `None` if it's a new key. + pub fn put(&self, key: JObject<'a>, value: JObject<'a>) -> Result>> { + let result = self.env.call_method_unchecked( + self.internal, + self.put, + ReturnType::Object, + &[JValue::from(key).to_jni(), JValue::from(value).to_jni()], + ); + + match result { + Ok(val) => Ok(Some(val.l()?)), + Err(e) => match e { + Error::NullPtr(_) => Ok(None), + _ => Err(e), + }, + } + } + + /// Remove a value from the map. Returns `Some` with the removed value and + /// `None` if there was no value for the key. + pub fn remove(&self, key: JObject<'a>) -> Result>> { + let result = self.env.call_method_unchecked( + self.internal, + self.remove, + ReturnType::Object, + &[JValue::from(key).to_jni()], + ); + + match result { + Ok(val) => Ok(Some(val.l()?)), + Err(e) => match e { + Error::NullPtr(_) => Ok(None), + _ => Err(e), + }, + } + } + + /// Get key/value iterator for the map. This is done by getting the + /// `EntrySet` from java and iterating over it. + pub fn iter(&self) -> Result> { + let iter_class = self + .env + .auto_local(self.env.find_class("java/util/Iterator")?); + + let has_next = self.env.get_method_id(&iter_class, "hasNext", "()Z")?; + + let next = self + .env + .get_method_id(&iter_class, "next", "()Ljava/lang/Object;")?; + + let entry_class = self + .env + .auto_local(self.env.find_class("java/util/Map$Entry")?); + + let get_key = self + .env + .get_method_id(&entry_class, "getKey", "()Ljava/lang/Object;")?; + + let get_value = self + .env + .get_method_id(&entry_class, "getValue", "()Ljava/lang/Object;")?; + + // Get the iterator over Map entries. + // Use the local frame till #109 is resolved, so that implicitly looked-up + // classes are freed promptly. + let iter = self.env.with_local_frame(16, || { + let entry_set = self + .env + .call_method_unchecked( + self.internal, + (&self.class, "entrySet", "()Ljava/util/Set;"), + ReturnType::Object, + &[], + )? + .l()?; + + let iter = self + .env + .call_method_unchecked( + entry_set, + ("java/util/Set", "iterator", "()Ljava/util/Iterator;"), + ReturnType::Object, + &[], + )? + .l()?; + + Ok(iter) + })?; + let iter = self.env.auto_local(iter); + + Ok(JMapIter { + map: self, + has_next, + next, + get_key, + get_value, + iter, + }) + } +} + +/// An iterator over the keys and values in a map. +/// +/// TODO: make the iterator implementation for java iterators its own thing +/// and generic enough to use elsewhere. +pub struct JMapIter<'a, 'b, 'c> { + map: &'c JMap<'a, 'b>, + has_next: JMethodID, + next: JMethodID, + get_key: JMethodID, + get_value: JMethodID, + iter: AutoLocal<'a, 'b>, +} + +impl<'a: 'b, 'b: 'c, 'c> JMapIter<'a, 'b, 'c> { + fn get_next(&self) -> Result, JObject<'a>)>> { + let iter = self.iter.as_obj(); + let has_next = self + .map + .env + .call_method_unchecked( + iter, + self.has_next, + ReturnType::Primitive(Primitive::Boolean), + &[], + )? + .z()?; + + if !has_next { + return Ok(None); + } + let next = self + .map + .env + .call_method_unchecked(iter, self.next, ReturnType::Object, &[])? + .l()?; + + let key = self + .map + .env + .call_method_unchecked(next, self.get_key, ReturnType::Object, &[])? + .l()?; + + let value = self + .map + .env + .call_method_unchecked(next, self.get_value, ReturnType::Object, &[])? + .l()?; + + Ok(Some((key, value))) + } +} + +impl<'a: 'b, 'b: 'c, 'c> Iterator for JMapIter<'a, 'b, 'c> { + type Item = (JObject<'a>, JObject<'a>); + + fn next(&mut self) -> Option { + match self.get_next() { + Ok(Some(n)) => Some(n), + _ => None, + } + } +} diff --git a/0.20.0/src/wrapper/objects/jmethodid.rs b/0.20.0/src/wrapper/objects/jmethodid.rs new file mode 100644 index 0000000..892fe43 --- /dev/null +++ b/0.20.0/src/wrapper/objects/jmethodid.rs @@ -0,0 +1,48 @@ +use crate::sys::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 { + internal: jmethodID, +} + +// 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 } + } + + /// Unwrap to the internal jni type. + pub fn into_raw(self) -> jmethodID { + self.internal + } +} diff --git a/0.20.0/src/wrapper/objects/jobject.rs b/0.20.0/src/wrapper/objects/jobject.rs new file mode 100644 index 0000000..1b55dae --- /dev/null +++ b/0.20.0/src/wrapper/objects/jobject.rs @@ -0,0 +1,56 @@ +use std::marker::PhantomData; + +use crate::sys::jobject; + +/// Wrapper around `sys::jobject` that adds a lifetime. This prevents it from +/// outliving the context in which it was acquired and getting GC'd out from +/// under us. It matches C's representation of the raw pointer, so it can be +/// used in any of the extern function argument positions that would take a +/// `jobject`. +/// +/// Most other types in the `objects` module deref to this, as they do in the C +/// representation. +#[repr(transparent)] +#[derive(Clone, Copy, Debug)] +pub struct JObject<'a> { + internal: jobject, + lifetime: PhantomData<&'a ()>, +} + +impl<'a> ::std::ops::Deref for JObject<'a> { + type Target = jobject; + + fn deref(&self) -> &Self::Target { + &self.internal + } +} + +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_raw(self) -> jobject { + self.internal + } + + /// Creates a new null object + pub fn null() -> JObject<'a> { + unsafe { Self::from_raw(std::ptr::null_mut() as jobject) } + } +} + +impl<'a> std::default::Default for JObject<'a> { + fn default() -> Self { + Self::null() + } +} diff --git a/0.20.0/src/wrapper/objects/jstaticfieldid.rs b/0.20.0/src/wrapper/objects/jstaticfieldid.rs new file mode 100644 index 0000000..4579b44 --- /dev/null +++ b/0.20.0/src/wrapper/objects/jstaticfieldid.rs @@ -0,0 +1,48 @@ +use crate::sys::jfieldID; + +/// 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, Debug)] +pub struct JStaticFieldID { + internal: jfieldID, +} + +// 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 } + } + + /// Unwrap to the internal jni type. + pub fn into_raw(self) -> jfieldID { + self.internal + } +} diff --git a/0.20.0/src/wrapper/objects/jstaticmethodid.rs b/0.20.0/src/wrapper/objects/jstaticmethodid.rs new file mode 100644 index 0000000..ada3fb9 --- /dev/null +++ b/0.20.0/src/wrapper/objects/jstaticmethodid.rs @@ -0,0 +1,48 @@ +use crate::sys::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 JStaticMethodID { + internal: jmethodID, +} + +// 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 } + } + + /// Unwrap to the internal jni type. + pub fn into_raw(self) -> jmethodID { + self.internal + } +} diff --git a/0.20.0/src/wrapper/objects/jstring.rs b/0.20.0/src/wrapper/objects/jstring.rs new file mode 100644 index 0000000..a5df7aa --- /dev/null +++ b/0.20.0/src/wrapper/objects/jstring.rs @@ -0,0 +1,52 @@ +use crate::{ + objects::JObject, + sys::{jobject, jstring}, +}; + +/// Lifetime'd representation of a `jstring`. Just a `JObject` wrapped in a new +/// class. +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct JString<'a>(JObject<'a>); + +impl<'a> ::std::ops::Deref for JString<'a> { + type Target = JObject<'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> From> for JObject<'a> { + fn from(other: JString) -> JObject { + other.0 + } +} + +impl<'a> From> for JString<'a> { + 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/0.20.0/src/wrapper/objects/jthrowable.rs b/0.20.0/src/wrapper/objects/jthrowable.rs new file mode 100644 index 0000000..f77f6e3 --- /dev/null +++ b/0.20.0/src/wrapper/objects/jthrowable.rs @@ -0,0 +1,52 @@ +use crate::{ + objects::JObject, + sys::{jobject, jthrowable}, +}; + +/// Lifetime'd representation of a `jthrowable`. Just a `JObject` wrapped in a +/// new class. +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct JThrowable<'a>(JObject<'a>); + +impl<'a> ::std::ops::Deref for JThrowable<'a> { + type Target = JObject<'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> From> for JObject<'a> { + fn from(other: JThrowable) -> JObject { + other.0 + } +} + +impl<'a> From> for JThrowable<'a> { + 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/0.20.0/src/wrapper/objects/jvalue.rs b/0.20.0/src/wrapper/objects/jvalue.rs new file mode 100644 index 0000000..54829c0 --- /dev/null +++ b/0.20.0/src/wrapper/objects/jvalue.rs @@ -0,0 +1,353 @@ +use std::convert::TryFrom; +use std::mem::transmute; + +use log::trace; + +use crate::{errors::*, objects::JObject, signature::Primitive, sys::*}; + +/// Rusty version of the JNI C `jvalue` enum. Used in Java method call arguments +/// and returns. +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug)] +pub enum JValue<'a> { + Object(JObject<'a>), + Byte(jbyte), + Char(jchar), + Short(jshort), + Int(jint), + Long(jlong), + Bool(jboolean), + Float(jfloat), + Double(jdouble), + Void, +} + +impl<'a> From> for jvalue { + fn from(other: JValue) -> jvalue { + other.to_jni() + } +} + +impl<'a> JValue<'a> { + /// Convert the enum to its jni-compatible equivalent. + pub fn to_jni(self) -> jvalue { + let val: jvalue = match self { + JValue::Object(obj) => jvalue { + l: unsafe { transmute(obj) }, + }, + JValue::Byte(byte) => jvalue { b: byte }, + JValue::Char(char) => jvalue { c: char }, + JValue::Short(short) => jvalue { s: short }, + JValue::Int(int) => jvalue { i: int }, + JValue::Long(long) => jvalue { j: long }, + JValue::Bool(boolean) => jvalue { b: boolean as i8 }, + JValue::Float(float) => jvalue { f: float }, + JValue::Double(double) => jvalue { d: double }, + JValue::Void => jvalue { + l: ::std::ptr::null_mut(), + }, + }; + trace!("converted {:?} to jvalue {:?}", self, unsafe { + ::std::mem::transmute::<_, u64>(val) + }); + val + } + + /// Get the type name for the enum variant. + pub fn type_name(&self) -> &'static str { + match *self { + JValue::Void => "void", + JValue::Object(_) => "object", + JValue::Byte(_) => "byte", + JValue::Char(_) => "char", + JValue::Short(_) => "short", + JValue::Int(_) => "int", + JValue::Long(_) => "long", + JValue::Bool(_) => "bool", + JValue::Float(_) => "float", + JValue::Double(_) => "double", + } + } + + /// Get the primitive type for the enum variant. If it's not a primitive + /// (i.e. an Object), returns None. + pub fn primitive_type(&self) -> Option { + Some(match *self { + JValue::Object(_) => return None, + JValue::Void => Primitive::Void, + JValue::Byte(_) => Primitive::Byte, + JValue::Char(_) => Primitive::Char, + JValue::Short(_) => Primitive::Short, + JValue::Int(_) => Primitive::Int, + JValue::Long(_) => Primitive::Long, + JValue::Bool(_) => Primitive::Boolean, + JValue::Float(_) => Primitive::Float, + JValue::Double(_) => Primitive::Double, + }) + } + + /// Try to unwrap to an Object. + pub fn l(self) -> Result> { + match self { + JValue::Object(obj) => Ok(obj), + _ => Err(Error::WrongJValueType("object", self.type_name())), + } + } + + /// Try to unwrap to a boolean. + pub fn z(self) -> Result { + match self { + JValue::Bool(b) => Ok(b == JNI_TRUE), + _ => Err(Error::WrongJValueType("bool", self.type_name())), + } + } + + /// Try to unwrap to a byte. + pub fn b(self) -> Result { + match self { + JValue::Byte(b) => Ok(b), + _ => Err(Error::WrongJValueType("jbyte", self.type_name())), + } + } + + /// Try to unwrap to a char. + pub fn c(self) -> Result { + match self { + JValue::Char(b) => Ok(b), + _ => Err(Error::WrongJValueType("jchar", self.type_name())), + } + } + + /// Try to unwrap to a double. + pub fn d(self) -> Result { + match self { + JValue::Double(b) => Ok(b), + _ => Err(Error::WrongJValueType("jdouble", self.type_name())), + } + } + + /// Try to unwrap to a float. + pub fn f(self) -> Result { + match self { + JValue::Float(b) => Ok(b), + _ => Err(Error::WrongJValueType("jfloat", self.type_name())), + } + } + + /// Try to unwrap to an int. + pub fn i(self) -> Result { + match self { + JValue::Int(b) => Ok(b), + _ => Err(Error::WrongJValueType("jint", self.type_name())), + } + } + + /// Try to unwrap to a long. + pub fn j(self) -> Result { + match self { + JValue::Long(b) => Ok(b), + _ => Err(Error::WrongJValueType("jlong", self.type_name())), + } + } + + /// Try to unwrap to a short. + pub fn s(self) -> Result { + match self { + JValue::Short(b) => Ok(b), + _ => Err(Error::WrongJValueType("jshort", self.type_name())), + } + } + + /// Try to unwrap to a void. + pub fn v(self) -> Result<()> { + match self { + JValue::Void => Ok(()), + _ => Err(Error::WrongJValueType("void", self.type_name())), + } + } +} + +impl<'a, T: Into>> From for JValue<'a> { + fn from(other: T) -> Self { + JValue::Object(other.into()) + } +} + +impl<'a> TryFrom> for JObject<'a> { + type Error = Error; + + fn try_from(value: JValue<'a>) -> Result { + match value { + JValue::Object(o) => Ok(o), + _ => Err(Error::WrongJValueType("object", value.type_name())), + } + } +} + +impl<'a> From for JValue<'a> { + fn from(other: bool) -> Self { + JValue::Bool(if other { JNI_TRUE } else { JNI_FALSE }) + } +} + +// jbool +impl<'a> From for JValue<'a> { + fn from(other: jboolean) -> Self { + JValue::Bool(other) + } +} + +impl<'a> TryFrom> for jboolean { + type Error = Error; + + fn try_from(value: JValue<'a>) -> Result { + match value { + JValue::Bool(b) => Ok(b), + _ => Err(Error::WrongJValueType("bool", value.type_name())), + } + } +} + +// jchar +impl<'a> From for JValue<'a> { + fn from(other: jchar) -> Self { + JValue::Char(other) + } +} + +impl<'a> TryFrom> for jchar { + type Error = Error; + + fn try_from(value: JValue<'a>) -> Result { + match value { + JValue::Char(c) => Ok(c), + _ => Err(Error::WrongJValueType("char", value.type_name())), + } + } +} + +// jshort +impl<'a> From for JValue<'a> { + fn from(other: jshort) -> Self { + JValue::Short(other) + } +} + +impl<'a> TryFrom> for jshort { + type Error = Error; + + fn try_from(value: JValue<'a>) -> Result { + match value { + JValue::Short(s) => Ok(s), + _ => Err(Error::WrongJValueType("short", value.type_name())), + } + } +} + +// jfloat +impl<'a> From for JValue<'a> { + fn from(other: jfloat) -> Self { + JValue::Float(other) + } +} + +impl<'a> TryFrom> for jfloat { + type Error = Error; + + fn try_from(value: JValue<'a>) -> Result { + match value { + JValue::Float(f) => Ok(f), + _ => Err(Error::WrongJValueType("float", value.type_name())), + } + } +} + +// jdouble +impl<'a> From for JValue<'a> { + fn from(other: jdouble) -> Self { + JValue::Double(other) + } +} + +impl<'a> TryFrom> for jdouble { + type Error = Error; + + fn try_from(value: JValue<'a>) -> Result { + match value { + JValue::Double(d) => Ok(d), + _ => Err(Error::WrongJValueType("double", value.type_name())), + } + } +} + +// jint +impl<'a> From for JValue<'a> { + fn from(other: jint) -> Self { + JValue::Int(other) + } +} + +impl<'a> TryFrom> for jint { + type Error = Error; + + fn try_from(value: JValue<'a>) -> Result { + match value { + JValue::Int(i) => Ok(i), + _ => Err(Error::WrongJValueType("int", value.type_name())), + } + } +} + +// jlong +impl<'a> From for JValue<'a> { + fn from(other: jlong) -> Self { + JValue::Long(other) + } +} + +impl<'a> TryFrom> for jlong { + type Error = Error; + + fn try_from(value: JValue<'a>) -> Result { + match value { + JValue::Long(l) => Ok(l), + _ => Err(Error::WrongJValueType("long", value.type_name())), + } + } +} + +// jbyte +impl<'a> From for JValue<'a> { + fn from(other: jbyte) -> Self { + JValue::Byte(other) + } +} + +impl<'a> TryFrom> for jbyte { + type Error = Error; + + fn try_from(value: JValue<'a>) -> Result { + match value { + JValue::Byte(b) => Ok(b), + _ => Err(Error::WrongJValueType("byte", value.type_name())), + } + } +} + +// jvoid +impl<'a> From<()> for JValue<'a> { + fn from(_: ()) -> Self { + JValue::Void + } +} + +impl<'a> TryFrom> for () { + type Error = Error; + + fn try_from(value: JValue<'a>) -> Result { + match value { + JValue::Void => Ok(()), + _ => Err(Error::WrongJValueType("void", value.type_name())), + } + } +} diff --git a/0.20.0/src/wrapper/objects/mod.rs b/0.20.0/src/wrapper/objects/mod.rs new file mode 100644 index 0000000..3209140 --- /dev/null +++ b/0.20.0/src/wrapper/objects/mod.rs @@ -0,0 +1,56 @@ +// wrappers arount jni pointer types that add lifetimes and other functionality. +mod jvalue; +pub use self::jvalue::*; + +mod jmethodid; +pub use self::jmethodid::*; + +mod jstaticmethodid; +pub use self::jstaticmethodid::*; + +mod jfieldid; +pub use self::jfieldid::*; + +mod jstaticfieldid; +pub use self::jstaticfieldid::*; + +mod jobject; +pub use self::jobject::*; + +mod jthrowable; +pub use self::jthrowable::*; + +mod jclass; +pub use self::jclass::*; + +mod jstring; +pub use self::jstring::*; + +mod jmap; +pub use self::jmap::*; + +mod jlist; +pub use self::jlist::*; + +mod jbytebuffer; +pub use self::jbytebuffer::*; + +// For storing a reference to a java object +mod global_ref; +pub use self::global_ref::*; + +// For automatic local ref deletion +mod auto_local; +pub use self::auto_local::*; + +mod release_mode; +pub use self::release_mode::*; + +// For automatic pointer-based generic array release +mod auto_array; +pub use self::auto_array::*; + +// For automatic pointer-based primitive array release +mod auto_primitive_array; + +pub use self::auto_primitive_array::*; diff --git a/0.20.0/src/wrapper/objects/release_mode.rs b/0.20.0/src/wrapper/objects/release_mode.rs new file mode 100644 index 0000000..ff81df7 --- /dev/null +++ b/0.20.0/src/wrapper/objects/release_mode.rs @@ -0,0 +1,15 @@ +use crate::sys::JNI_ABORT; + +/// ReleaseMode +/// +/// This defines the release mode of AutoArray (and AutoPrimitiveArray) resources, and +/// related release array functions. +#[derive(Clone, Copy, Debug)] +#[repr(i32)] +pub enum ReleaseMode { + /// Copy back the content and free the elems buffer. For read-only access, prefer + /// [`NoCopyBack`](ReleaseMode::NoCopyBack). + CopyBack = 0, + /// Free the buffer without copying back the possible changes. + NoCopyBack = JNI_ABORT, +} diff --git a/0.20.0/src/wrapper/signature.rs b/0.20.0/src/wrapper/signature.rs new file mode 100644 index 0000000..46444cc --- /dev/null +++ b/0.20.0/src/wrapper/signature.rs @@ -0,0 +1,274 @@ +use std::{fmt, str::FromStr}; + +use combine::{ + between, many, many1, parser, satisfy, token, ParseError, Parser, StdParseResult, Stream, +}; + +use crate::errors::*; + +/// A primitive java type. These are the things that can be represented without +/// an object. +#[allow(missing_docs)] +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub enum Primitive { + Boolean, // Z + Byte, // B + Char, // C + Double, // D + Float, // F + Int, // I + Long, // J + Short, // S + Void, // V +} + +impl fmt::Display for Primitive { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Primitive::Boolean => write!(f, "Z"), + Primitive::Byte => write!(f, "B"), + Primitive::Char => write!(f, "C"), + Primitive::Double => write!(f, "D"), + Primitive::Float => write!(f, "F"), + Primitive::Int => write!(f, "I"), + Primitive::Long => write!(f, "J"), + Primitive::Short => write!(f, "S"), + Primitive::Void => write!(f, "V"), + } + } +} + +/// Enum representing any java type in addition to method signatures. +#[allow(missing_docs)] +#[derive(Eq, PartialEq, Debug, Clone)] +pub enum JavaType { + Primitive(Primitive), + Object(String), + Array(Box), + Method(Box), +} + +impl FromStr for JavaType { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + parser(parse_type) + .parse(s) + .map(|res| res.0) + .map_err(|e| Error::ParseFailed(e, s.to_owned())) + } +} + +impl fmt::Display for JavaType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + JavaType::Primitive(ref ty) => ty.fmt(f), + JavaType::Object(ref name) => write!(f, "L{};", name), + JavaType::Array(ref ty) => write!(f, "[{}", ty), + JavaType::Method(ref m) => m.fmt(f), + } + } +} + +/// 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 { + 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. +#[allow(missing_docs)] +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct TypeSignature { + pub args: Vec, + pub ret: ReturnType, +} + +impl TypeSignature { + /// Parse a signature string into a TypeSignature enum. + // Clippy suggests implementing `FromStr` or renaming it which is not possible in our case. + #[allow(clippy::should_implement_trait)] + pub fn from_str>(s: S) -> Result { + Ok(match parser(parse_sig).parse(s.as_ref()).map(|res| res.0) { + Ok(JavaType::Method(sig)) => *sig, + Err(e) => return Err(Error::ParseFailed(e, s.as_ref().to_owned())), + _ => unreachable!(), + }) + } +} + +impl fmt::Display for TypeSignature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "(")?; + for a in &self.args { + write!(f, "{}", a)?; + } + write!(f, ")")?; + write!(f, "{}", self.ret)?; + Ok(()) + } +} + +fn parse_primitive>(input: &mut S) -> StdParseResult +where + S::Error: ParseError, +{ + let boolean = token('Z').map(|_| Primitive::Boolean); + let byte = token('B').map(|_| Primitive::Byte); + let char_type = token('C').map(|_| Primitive::Char); + let double = token('D').map(|_| Primitive::Double); + let float = token('F').map(|_| Primitive::Float); + let int = token('I').map(|_| Primitive::Int); + let long = token('J').map(|_| Primitive::Long); + let short = token('S').map(|_| Primitive::Short); + let void = token('V').map(|_| Primitive::Void); + + (boolean + .or(byte) + .or(char_type) + .or(double) + .or(float) + .or(int) + .or(long) + .or(short) + .or(void)) + .parse_stream(input) + .into() +} + +fn parse_array>(input: &mut S) -> StdParseResult +where + S::Error: ParseError, +{ + let marker = token('['); + (marker, parser(parse_type)) + .map(|(_, ty)| JavaType::Array(Box::new(ty))) + .parse_stream(input) + .into() +} + +fn parse_object>(input: &mut S) -> StdParseResult +where + S::Error: ParseError, +{ + let marker = token('L'); + let end = token(';'); + let obj = between(marker, end, many1(satisfy(|c| c != ';'))); + + obj.map(JavaType::Object).parse_stream(input).into() +} + +fn parse_type>(input: &mut S) -> StdParseResult +where + S::Error: ParseError, +{ + parser(parse_primitive) + .map(JavaType::Primitive) + .or(parser(parse_array)) + .or(parser(parse_object)) + .or(parser(parse_sig)) + .parse_stream(input) + .into() +} + +fn parse_return>(input: &mut S) -> StdParseResult +where + S::Error: ParseError, +{ + 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>(input: &mut S) -> StdParseResult, S> +where + S::Error: ParseError, +{ + between(token('('), token(')'), many(parser(parse_type))) + .parse_stream(input) + .into() +} + +fn parse_sig>(input: &mut S) -> StdParseResult +where + S::Error: ParseError, +{ + (parser(parse_args), parser(parse_return)) + .map(|(a, r)| TypeSignature { args: a, ret: r }) + .map(|sig| JavaType::Method(Box::new(sig))) + .parse_stream(input) + .into() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parser() { + let inputs = [ + "(Ljava/lang/String;I)V", + "[Lherp;", + // fails because the return type does not contain the class name: "(IBVZ)L;" + // "(IBVZ)Ljava/lang/String;", + ]; + + for each in inputs.iter() { + let res = JavaType::from_str(*each).unwrap(); + println!("{:#?}", res); + let s = format!("{}", res); + assert_eq!(s, *each); + let res2 = JavaType::from_str(*each).unwrap(); + println!("{:#?}", res2); + assert_eq!(res2, res); + } + } + + #[test] + fn test_parser_invalid_signature() { + let signature = "()Ljava/lang/List"; // no semicolon + let res = JavaType::from_str(signature); + + match res { + Ok(any) => { + panic!("Unexpected result: {}", any); + } + Err(err) => { + assert!(err.to_string().contains("input: ()Ljava/lang/List")); + } + } + } +} diff --git a/0.20.0/src/wrapper/strings/ffi_str.rs b/0.20.0/src/wrapper/strings/ffi_str.rs new file mode 100644 index 0000000..7af5e48 --- /dev/null +++ b/0.20.0/src/wrapper/strings/ffi_str.rs @@ -0,0 +1,109 @@ +use std::{ + borrow::{Borrow, Cow, ToOwned}, + ffi, + os::raw::c_char, +}; + +use cesu8::{from_java_cesu8, to_java_cesu8}; +use log::debug; + +use crate::wrapper::strings::ffi_str; + +/// Wrapper for `std::ffi::CString` that also takes care of encoding between +/// UTF-8 and Java's Modified UTF-8. As with `CString`, this implements `Deref` +/// to `&JNIStr`. +pub struct JNIString { + internal: ffi::CString, +} + +/// Wrapper for `std::ffi::CStr` that also takes care of encoding between +/// UTF-8 and Java's Modified UTF-8. +pub struct JNIStr { + internal: ffi::CStr, +} + +impl ::std::ops::Deref for JNIString { + type Target = JNIStr; + + fn deref(&self) -> &Self::Target { + unsafe { &*(self.internal.as_bytes_with_nul() as *const [u8] as *const ffi_str::JNIStr) } + } +} + +impl ::std::ops::Deref for JNIStr { + type Target = ffi::CStr; + + fn deref(&self) -> &Self::Target { + &self.internal + } +} + +impl From for JNIString +where + T: AsRef, +{ + fn from(other: T) -> Self { + let enc = to_java_cesu8(other.as_ref()).into_owned(); + JNIString { + internal: unsafe { ffi::CString::from_vec_unchecked(enc) }, + } + } +} + +impl<'a> From<&'a JNIStr> for Cow<'a, str> { + fn from(other: &'a JNIStr) -> Cow<'a, str> { + let bytes = other.to_bytes(); + match from_java_cesu8(bytes) { + Ok(s) => s, + Err(e) => { + debug!("error decoding java cesu8: {:#?}", e); + String::from_utf8_lossy(bytes) + } + } + } +} + +impl From for String { + fn from(other: JNIString) -> String { + Cow::from(other.borrowed()).into_owned() + } +} + +impl JNIString { + /// Get the borrowed version of the JNIString. Equivalent to + /// `CString::borrowed`. + pub fn borrowed(&self) -> &JNIStr { + self + } +} + +impl JNIStr { + /// Construct a reference to a `JNIStr` from a pointer. Equivalent to `CStr::from_ptr`. + /// + /// # Safety + /// + /// Expects a valid pointer to a null-terminated C string and does not perform any lifetime + /// checks for the resulting value. + pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a JNIStr { + &*(ffi::CStr::from_ptr(ptr) as *const ffi::CStr as *const ffi_str::JNIStr) + } +} + +// impls for CoW +impl Borrow for JNIString { + fn borrow(&self) -> &JNIStr { + self + } +} + +impl ToOwned for JNIStr { + type Owned = JNIString; + + fn to_owned(&self) -> JNIString { + unsafe { + JNIString { + internal: ffi::CString::from_vec_unchecked(self.to_bytes().to_vec()), + } + } + } +} diff --git a/0.20.0/src/wrapper/strings/java_str.rs b/0.20.0/src/wrapper/strings/java_str.rs new file mode 100644 index 0000000..f964df2 --- /dev/null +++ b/0.20.0/src/wrapper/strings/java_str.rs @@ -0,0 +1,71 @@ +use std::{borrow::Cow, os::raw::c_char}; + +use log::warn; + +use crate::{errors::*, objects::JString, strings::JNIStr, JNIEnv}; + +/// Reference to a string in the JVM. Holds a pointer to the array +/// returned by GetStringUTFChars. Calls ReleaseStringUTFChars on Drop. +/// Can be converted to a `&JNIStr` with the same cost as the `&CStr.from_ptr` +/// conversion. +pub struct JavaStr<'a: 'b, 'b> { + internal: *const c_char, + obj: JString<'a>, + env: &'b JNIEnv<'a>, +} + +impl<'a: 'b, 'b> JavaStr<'a, 'b> { + /// Build a `JavaStr` from an object and a reference to the environment. You + /// probably want to use `JNIEnv::get_string` instead. + pub fn from_env(env: &'b JNIEnv<'a>, obj: JString<'a>) -> Result { + let ptr = env.get_string_utf_chars(obj)?; + let java_str = JavaStr { + internal: ptr, + env, + obj, + }; + Ok(java_str) + } + + /// Extract the raw C string pointer from the JavaStr. This will be + /// encoded using the JVM internal `CESU-8`-style. + pub fn get_raw(&self) -> *const c_char { + self.internal + } +} + +impl<'a: 'b, 'b> ::std::ops::Deref for JavaStr<'a, 'b> { + type Target = JNIStr; + fn deref(&self) -> &Self::Target { + self.into() + } +} + +impl<'a: 'b, 'b: 'c, 'c> From<&'c JavaStr<'a, 'b>> for &'c JNIStr { + fn from(other: &'c JavaStr) -> &'c JNIStr { + unsafe { JNIStr::from_ptr(other.internal) } + } +} + +impl<'a: 'b, 'b: 'c, 'c> From<&'c JavaStr<'a, 'b>> for Cow<'c, str> { + fn from(other: &'c JavaStr) -> Cow<'c, str> { + let jni_str: &JNIStr = other; + jni_str.into() + } +} + +impl<'a: 'b, 'b> From> for String { + fn from(other: JavaStr) -> String { + let cow: Cow = (&other).into(); + cow.into_owned() + } +} + +impl<'a: 'b, 'b> Drop for JavaStr<'a, 'b> { + fn drop(&mut self) { + match unsafe { self.env.release_string_utf_chars(self.obj, self.internal) } { + Ok(()) => {} + Err(e) => warn!("error dropping java str: {}", e), + } + } +} diff --git a/0.20.0/src/wrapper/strings/mod.rs b/0.20.0/src/wrapper/strings/mod.rs new file mode 100644 index 0000000..2e03363 --- /dev/null +++ b/0.20.0/src/wrapper/strings/mod.rs @@ -0,0 +1,6 @@ +// String types for sending to/from the jvm +mod ffi_str; +pub use self::ffi_str::*; + +mod java_str; +pub use self::java_str::*; diff --git a/0.20.0/src/wrapper/version.rs b/0.20.0/src/wrapper/version.rs new file mode 100644 index 0000000..4629ea8 --- /dev/null +++ b/0.20.0/src/wrapper/version.rs @@ -0,0 +1,43 @@ +use crate::sys::{ + JNI_VERSION_1_1, JNI_VERSION_1_2, JNI_VERSION_1_4, JNI_VERSION_1_6, JNI_VERSION_1_8, +}; + +/// JNI Version +/// +/// This maps to the `jni_sys::JNI_VERSION_1_*` constants. +#[derive(Debug, Copy, Clone)] +#[allow(missing_docs)] +pub enum JNIVersion { + V1, + V2, + V4, + V6, + V8, + Invalid(i32), +} + +impl From for JNIVersion { + fn from(other: i32) -> Self { + match other { + JNI_VERSION_1_1 => JNIVersion::V1, + JNI_VERSION_1_2 => JNIVersion::V2, + JNI_VERSION_1_4 => JNIVersion::V4, + JNI_VERSION_1_6 => JNIVersion::V6, + JNI_VERSION_1_8 => JNIVersion::V8, + v => JNIVersion::Invalid(v), + } + } +} + +impl From for i32 { + fn from(other: JNIVersion) -> Self { + match other { + JNIVersion::V1 => JNI_VERSION_1_1, + JNIVersion::V2 => JNI_VERSION_1_2, + JNIVersion::V4 => JNI_VERSION_1_4, + JNIVersion::V6 => JNI_VERSION_1_6, + JNIVersion::V8 => JNI_VERSION_1_8, + JNIVersion::Invalid(v) => v, + } + } +} diff --git a/0.20.0/test_profile b/0.20.0/test_profile new file mode 100644 index 0000000..61dc8c8 --- /dev/null +++ b/0.20.0/test_profile @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Sets the LD_LIBRARY_PATH required for running ITs dependent on libjvm. +# See CONTRIBUTING.md for details. + +JAVA_HOME="${JAVA_HOME:-$(java -XshowSettings:properties -version \ + 2>&1 > /dev/null |\ + grep 'java.home' |\ + awk '{print $3}')}" + +if [[ "$(uname -s)" == "Darwin" ]]; then + LIB_NAME="libjli" +else + LIB_NAME="libjvm" +fi + +# As JDK 8 and 9+ use different relative paths for libjvm, find the library: +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/0.20.0/tests/executor.rs b/0.20.0/tests/executor.rs new file mode 100644 index 0000000..fbd02d4 --- /dev/null +++ b/0.20.0/tests/executor.rs @@ -0,0 +1,75 @@ +#![cfg(feature = "invocation")] + +use std::{ + sync::{Arc, Barrier}, + thread::spawn, +}; + +use jni::{sys::jint, Executor}; + +mod util; +use util::{jvm, AtomicIntegerProxy}; + +#[test] +fn single_thread() { + let executor = Executor::new(jvm().clone()); + test_single_thread(executor); +} + +#[test] +fn serialized_threads() { + let executor = Executor::new(jvm().clone()); + test_serialized_threads(executor); +} + +#[test] +fn concurrent_threads() { + let executor = Executor::new(jvm().clone()); + const THREAD_NUM: usize = 8; + test_concurrent_threads(executor, THREAD_NUM) +} + +fn test_single_thread(executor: Executor) { + let mut atomic = AtomicIntegerProxy::new(executor, 0).unwrap(); + assert_eq!(0, atomic.get().unwrap()); + assert_eq!(1, atomic.increment_and_get().unwrap()); + assert_eq!(3, atomic.add_and_get(2).unwrap()); + assert_eq!(3, atomic.get().unwrap()); +} + +fn test_serialized_threads(executor: Executor) { + let mut atomic = AtomicIntegerProxy::new(executor, 0).unwrap(); + assert_eq!(0, atomic.get().unwrap()); + let jh = spawn(move || { + assert_eq!(1, atomic.increment_and_get().unwrap()); + assert_eq!(3, atomic.add_and_get(2).unwrap()); + atomic + }); + let mut atomic = jh.join().unwrap(); + assert_eq!(3, atomic.get().unwrap()); +} + +fn test_concurrent_threads(executor: Executor, thread_num: usize) { + const ITERS_PER_THREAD: usize = 10_000; + + let mut atomic = AtomicIntegerProxy::new(executor, 0).unwrap(); + let barrier = Arc::new(Barrier::new(thread_num)); + let mut threads = Vec::new(); + + for _ in 0..thread_num { + let barrier = Arc::clone(&barrier); + let mut atomic = atomic.clone(); + let jh = spawn(move || { + barrier.wait(); + for _ in 0..ITERS_PER_THREAD { + atomic.increment_and_get().unwrap(); + } + }); + threads.push(jh); + } + for jh in threads { + jh.join().unwrap(); + } + let expected = (ITERS_PER_THREAD * thread_num) as jint; + assert_eq!(expected, atomic.get().unwrap()); +} diff --git a/0.20.0/tests/executor_nested_attach.rs b/0.20.0/tests/executor_nested_attach.rs new file mode 100644 index 0000000..b0ef6ae --- /dev/null +++ b/0.20.0/tests/executor_nested_attach.rs @@ -0,0 +1,62 @@ +#![cfg(feature = "invocation")] + +use std::{sync::Arc, thread::spawn}; + +use jni::{ + errors::{Error, JniError}, + Executor, JavaVM, +}; + +mod util; +use util::jvm; + +/// Checks if nested attaches are working properly and threads detach themselves +/// on exit. +#[test] +fn nested_attach() { + let executor = Executor::new(jvm().clone()); + + assert_eq!(jvm().threads_attached(), 0); + let thread = spawn(|| { + assert_eq!(jvm().threads_attached(), 0); + check_nested_attach(jvm(), executor); + assert_eq!(jvm().threads_attached(), 1); + }); + thread.join().unwrap(); + assert_eq!(jvm().threads_attached(), 0); +} + +/// Checks if nested `with_attached` calls does not detach the thread before the outer-most +/// call is finished. +fn check_nested_attach(vm: &Arc, executor: Executor) { + check_detached(vm); + executor + .with_attached(|_| { + check_attached(vm); + executor.with_attached(|_| { + check_attached(vm); + Ok(()) + })?; + check_attached(vm); + Ok(()) + }) + .unwrap(); +} + +fn check_attached(vm: &JavaVM) { + assert!(is_attached(vm)); +} + +fn check_detached(vm: &JavaVM) { + assert!(!is_attached(vm)); +} + +fn is_attached(vm: &JavaVM) -> bool { + vm.get_env() + .map(|_| true) + .or_else(|jni_err| match jni_err { + Error::JniCall(JniError::ThreadDetached) => Ok(false), + _ => Err(jni_err), + }) + .expect("An unexpected JNI error occurred") +} diff --git a/0.20.0/tests/java_integers.rs b/0.20.0/tests/java_integers.rs new file mode 100644 index 0000000..0d7c6fb --- /dev/null +++ b/0.20.0/tests/java_integers.rs @@ -0,0 +1,41 @@ +#![cfg(feature = "invocation")] + +use jni::{objects::JObject, objects::JValue}; + +mod util; +use util::{attach_current_thread, print_exception}; + +#[test] +fn test_java_integers() { + let env = attach_current_thread(); + + let array_length = 50; + + for value in -10..10 { + env.with_local_frame(16, || { + let integer_value = + env.new_object("java/lang/Integer", "(I)V", &[JValue::Int(value)])?; + + let values_array = + env.new_object_array(array_length, "java/lang/Integer", integer_value)?; + let values_array = unsafe { JObject::from_raw(values_array) }; + + let result = env + .call_static_method( + "java/util/Arrays", + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;)I", + &[JValue::Object(values_array), JValue::Object(integer_value)], + )? + .i()?; + + assert!(0 <= result && result < array_length); + + Ok(JObject::null()) + }) + .unwrap_or_else(|e| { + print_exception(&env); + panic!("{:#?}", e); + }); + } +} diff --git a/0.20.0/tests/jmap.rs b/0.20.0/tests/jmap.rs new file mode 100644 index 0000000..f19005c --- /dev/null +++ b/0.20.0/tests/jmap.rs @@ -0,0 +1,41 @@ +#![cfg(feature = "invocation")] + +use jni::objects::{JMap, JObject}; + +mod util; +use util::{attach_current_thread, unwrap}; + +#[test] +pub fn jmap_push_and_iterate() { + let env = attach_current_thread(); + let data = &["hello", "world", "from", "test"]; + + // Create a new map. Use LinkedHashMap to have predictable iteration order + let map_object = unwrap(&env, env.new_object("java/util/LinkedHashMap", "()V", &[])); + let map = unwrap(&env, JMap::from_env(&env, map_object)); + + // Push all strings + unwrap( + &env, + data.iter().try_for_each(|s| { + env.new_string(s) + .map(JObject::from) + .and_then(|s| map.put(s, s).map(|_| ())) + }), + ); + + // Collect the keys using the JMap iterator + let mut collected = Vec::new(); + unwrap( + &env, + map.iter().and_then(|mut iter| { + iter.try_for_each(|e| { + env.get_string(e.0.into()) + .map(|s| collected.push(String::from(s))) + }) + }), + ); + + let orig = data.to_vec(); + assert_eq!(orig, collected); +} diff --git a/0.20.0/tests/jni_api.rs b/0.20.0/tests/jni_api.rs new file mode 100644 index 0000000..050f8ec --- /dev/null +++ b/0.20.0/tests/jni_api.rs @@ -0,0 +1,958 @@ +#![cfg(feature = "invocation")] + +use std::str::FromStr; + +use jni::{ + descriptors::Desc, + errors::Error, + objects::{ + AutoArray, AutoLocal, JByteBuffer, JList, JObject, JString, JThrowable, JValue, ReleaseMode, + }, + signature::JavaType, + strings::JNIString, + sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jobject, jshort, jsize}, + JNIEnv, +}; + +mod util; +use util::{attach_current_thread, unwrap}; + +static ARRAYLIST_CLASS: &str = "java/util/ArrayList"; +static EXCEPTION_CLASS: &str = "java/lang/Exception"; +static ARITHMETIC_EXCEPTION_CLASS: &str = "java/lang/ArithmeticException"; +static RUNTIME_EXCEPTION_CLASS: &str = "java/lang/RuntimeException"; +static INTEGER_CLASS: &str = "java/lang/Integer"; +static MATH_CLASS: &str = "java/lang/Math"; +static STRING_CLASS: &str = "java/lang/String"; +static MATH_ABS_METHOD_NAME: &str = "abs"; +static MATH_TO_INT_METHOD_NAME: &str = "toIntExact"; +static MATH_ABS_SIGNATURE: &str = "(I)I"; +static MATH_TO_INT_SIGNATURE: &str = "(J)I"; +static TEST_EXCEPTION_MESSAGE: &str = "Default exception thrown"; +static TESTING_OBJECT_STR: &str = "TESTING OBJECT"; + +#[test] +pub fn call_method_returning_null() { + let env = attach_current_thread(); + // Create an Exception with no message + let obj = AutoLocal::new( + &env, + unwrap(&env, env.new_object(EXCEPTION_CLASS, "()V", &[])), + ); + // Call Throwable#getMessage must return null + let message = unwrap( + &env, + env.call_method(&obj, "getMessage", "()Ljava/lang/String;", &[]), + ); + let message_ref = env.auto_local(unwrap(&env, message.l())); + + assert!(message_ref.as_obj().is_null()); +} + +#[test] +pub fn is_instance_of_same_class() { + let env = attach_current_thread(); + let obj = AutoLocal::new( + &env, + unwrap(&env, env.new_object(EXCEPTION_CLASS, "()V", &[])), + ); + assert!(unwrap(&env, env.is_instance_of(&obj, EXCEPTION_CLASS))); +} + +#[test] +pub fn is_instance_of_superclass() { + let env = attach_current_thread(); + let obj = AutoLocal::new( + &env, + unwrap(&env, env.new_object(ARITHMETIC_EXCEPTION_CLASS, "()V", &[])), + ); + assert!(unwrap(&env, env.is_instance_of(&obj, EXCEPTION_CLASS))); +} + +#[test] +pub fn is_instance_of_subclass() { + let env = attach_current_thread(); + let obj = AutoLocal::new( + &env, + unwrap(&env, env.new_object(EXCEPTION_CLASS, "()V", &[])), + ); + assert!(!unwrap( + &env, + env.is_instance_of(&obj, ARITHMETIC_EXCEPTION_CLASS) + )); +} + +#[test] +pub fn is_instance_of_not_superclass() { + let env = attach_current_thread(); + let obj = AutoLocal::new( + &env, + unwrap(&env, env.new_object(ARITHMETIC_EXCEPTION_CLASS, "()V", &[])), + ); + assert!(!unwrap(&env, env.is_instance_of(&obj, ARRAYLIST_CLASS))); +} + +#[test] +pub fn is_instance_of_null() { + let env = attach_current_thread(); + let obj = JObject::null(); + assert!(unwrap(&env, env.is_instance_of(obj, ARRAYLIST_CLASS))); + assert!(unwrap(&env, env.is_instance_of(obj, EXCEPTION_CLASS))); + assert!(unwrap( + &env, + env.is_instance_of(obj, ARITHMETIC_EXCEPTION_CLASS) + )); +} + +#[test] +pub fn is_same_object_diff_references() { + let env = attach_current_thread(); + let string = env.new_string(TESTING_OBJECT_STR).unwrap(); + let ref_from_string = unwrap(&env, env.new_local_ref::(string.into())); + assert!(unwrap(&env, env.is_same_object(string, ref_from_string))); + unwrap(&env, env.delete_local_ref(ref_from_string)); +} + +#[test] +pub fn is_same_object_same_reference() { + let env = attach_current_thread(); + let string = env.new_string(TESTING_OBJECT_STR).unwrap(); + assert!(unwrap(&env, env.is_same_object(string, string))); +} + +#[test] +pub fn is_not_same_object() { + let env = attach_current_thread(); + let string = env.new_string(TESTING_OBJECT_STR).unwrap(); + let same_src_str = env.new_string(TESTING_OBJECT_STR).unwrap(); + assert!(!unwrap(&env, env.is_same_object(string, same_src_str))); +} + +#[test] +pub fn is_not_same_object_null() { + let env = attach_current_thread(); + assert!(unwrap( + &env, + env.is_same_object(JObject::null(), JObject::null()) + )); +} + +#[test] +pub fn get_static_public_field() { + let env = attach_current_thread(); + + let min_int_value = env + .get_static_field(INTEGER_CLASS, "MIN_VALUE", "I") + .unwrap() + .i() + .unwrap(); + + assert_eq!(min_int_value, i32::min_value()); +} + +#[test] +pub fn get_static_public_field_by_id() { + let env = attach_current_thread(); + + // One can't pass a JavaType::Primitive(Primitive::Int) to + // `get_static_field_id` unfortunately: #137 + let field_type = "I"; + let field_id = env + .get_static_field_id(INTEGER_CLASS, "MIN_VALUE", field_type) + .unwrap(); + + let field_type = JavaType::from_str(field_type).unwrap(); + let min_int_value = env + .get_static_field_unchecked(INTEGER_CLASS, field_id, field_type) + .unwrap() + .i() + .unwrap(); + + assert_eq!(min_int_value, i32::min_value()); +} + +#[test] +pub fn pop_local_frame_pending_exception() { + let env = attach_current_thread(); + + env.push_local_frame(16).unwrap(); + + env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception") + .unwrap(); + + // Pop the local frame with a pending exception + env.pop_local_frame(JObject::null()) + .expect("JNIEnv#pop_local_frame must work in case of pending exception"); + + env.exception_clear().unwrap(); +} + +#[test] +pub fn push_local_frame_pending_exception() { + let env = attach_current_thread(); + + env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception") + .unwrap(); + + // Push a new local frame with a pending exception + env.push_local_frame(16) + .expect("JNIEnv#push_local_frame must work in case of pending exception"); + + env.exception_clear().unwrap(); + + env.pop_local_frame(JObject::null()).unwrap(); +} + +#[test] +pub fn push_local_frame_too_many_refs() { + let env = attach_current_thread(); + + // Try to push a new local frame with a ridiculous size + let frame_size = i32::max_value(); + env.push_local_frame(frame_size) + .expect_err("push_local_frame(2B) must Err"); + + env.pop_local_frame(JObject::null()).unwrap(); +} + +#[test] +pub fn with_local_frame() { + let env = attach_current_thread(); + + let s = env + .with_local_frame(16, || { + let res = env.new_string("Test").unwrap(); + Ok(res.into()) + }) + .unwrap(); + + let s = env + .get_string(s.into()) + .expect("The object returned from the local frame must remain valid"); + assert_eq!(s.to_str().unwrap(), "Test"); +} + +#[test] +pub fn with_local_frame_pending_exception() { + let env = attach_current_thread(); + + env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception") + .unwrap(); + + // Try to allocate a frame of locals + env.with_local_frame(16, || Ok(JObject::null())) + .expect("JNIEnv#with_local_frame must work in case of pending exception"); + + env.exception_clear().unwrap(); +} + +#[test] +pub fn call_static_method_ok() { + let env = attach_current_thread(); + + let x = JValue::from(-10); + let val: jint = env + .call_static_method(MATH_CLASS, MATH_ABS_METHOD_NAME, MATH_ABS_SIGNATURE, &[x]) + .expect("JNIEnv#call_static_method_unsafe should return JValue") + .i() + .unwrap(); + + assert_eq!(val, 10); +} + +#[test] +pub fn call_static_method_throws() { + let env = attach_current_thread(); + + let x = JValue::Long(4_000_000_000); + let is_java_exception = env + .call_static_method( + MATH_CLASS, + MATH_TO_INT_METHOD_NAME, + MATH_TO_INT_SIGNATURE, + &[x], + ) + .map_err(|error| matches!(error, Error::JavaException)) + .expect_err("JNIEnv#call_static_method_unsafe should return error"); + + assert!( + is_java_exception, + "ErrorKind::JavaException expected as error" + ); + assert_pending_java_exception(&env); +} + +#[test] +pub fn call_static_method_wrong_arg() { + let env = attach_current_thread(); + + let x = JValue::Double(4.567_891_23); + env.call_static_method( + MATH_CLASS, + MATH_TO_INT_METHOD_NAME, + MATH_TO_INT_SIGNATURE, + &[x], + ) + .expect_err("JNIEnv#call_static_method_unsafe should return error"); + + assert_pending_java_exception(&env); +} + +#[test] +pub fn java_byte_array_from_slice() { + let env = attach_current_thread(); + let buf: &[u8] = &[1, 2, 3]; + let java_array = env + .byte_array_from_slice(buf) + .expect("JNIEnv#byte_array_from_slice must create a java array from slice"); + let obj = AutoLocal::new(&env, unsafe { JObject::from_raw(java_array) }); + + assert!(!obj.as_obj().is_null()); + let mut res: [i8; 3] = [0; 3]; + env.get_byte_array_region(java_array, 0, &mut res).unwrap(); + assert_eq!(res[0], 1); + assert_eq!(res[1], 2); + assert_eq!(res[2], 3); +} + +macro_rules! test_get_array_elements { + ( $jni_get:tt, $jni_type:ty, $new_array:tt, $get_array:tt, $set_array:tt ) => { + #[test] + pub fn $jni_get() { + let env = attach_current_thread(); + + // Create original Java array + let buf: &[$jni_type] = &[0 as $jni_type, 1 as $jni_type]; + let java_array = env + .$new_array(2) + .expect(stringify!(JNIEnv#$new_array must create a Java $jni_type array with given size)); + + // Insert array elements + let _ = env.$set_array(java_array, 0, buf); + + // Use a scope to test Drop + { + // Get byte array elements auto wrapper + 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); + + // Check pointer access + let ptr = auto_ptr.as_ptr(); + assert_eq!(unsafe { *ptr.offset(0) } as i32, 0); + assert_eq!(unsafe { *ptr.offset(1) } as i32, 1); + + // Check pointer From access + let ptr: *mut $jni_type = std::convert::From::from(&auto_ptr); + assert_eq!(unsafe { *ptr.offset(0) } as i32, 0); + assert_eq!(unsafe { *ptr.offset(1) } as i32, 1); + + // Check pointer into() access + let ptr: *mut $jni_type = (&auto_ptr).into(); + assert_eq!(unsafe { *ptr.offset(0) } as i32, 0); + assert_eq!(unsafe { *ptr.offset(1) } as i32, 1); + + // Modify + unsafe { + *ptr.offset(0) += 1 as $jni_type; + *ptr.offset(1) -= 1 as $jni_type; + } + + // Commit would be necessary here, if there were no closure + //auto_ptr.commit().unwrap(); + } + + // Confirm modification of original Java array + let mut res: [$jni_type; 2] = [0 as $jni_type; 2]; + env.$get_array(java_array, 0, &mut res).unwrap(); + assert_eq!(res[0] as i32, 1); + assert_eq!(res[1] as i32, 0); + } + }; +} + +// Test generic get_array_elements +test_get_array_elements!( + get_array_elements, + jint, + new_int_array, + get_int_array_region, + set_int_array_region +); + +// Test type-specific array accessors +test_get_array_elements!( + get_int_array_elements, + jint, + new_int_array, + get_int_array_region, + set_int_array_region +); + +test_get_array_elements!( + get_long_array_elements, + jlong, + new_long_array, + get_long_array_region, + set_long_array_region +); + +test_get_array_elements!( + get_byte_array_elements, + jbyte, + new_byte_array, + get_byte_array_region, + set_byte_array_region +); + +test_get_array_elements!( + get_boolean_array_elements, + jboolean, + new_boolean_array, + get_boolean_array_region, + set_boolean_array_region +); + +test_get_array_elements!( + get_char_array_elements, + jchar, + new_char_array, + get_char_array_region, + set_char_array_region +); + +test_get_array_elements!( + get_short_array_elements, + jshort, + new_short_array, + get_short_array_region, + set_short_array_region +); + +test_get_array_elements!( + get_float_array_elements, + jfloat, + new_float_array, + get_float_array_region, + set_float_array_region +); + +test_get_array_elements!( + get_double_array_elements, + jdouble, + new_double_array, + get_double_array_region, + set_double_array_region +); + +#[test] +#[ignore] // Disabled until issue #283 is resolved +pub fn get_long_array_elements_commit() { + let env = attach_current_thread(); + + // Create original Java array + let buf: &[i64] = &[1, 2, 3]; + let java_array = env + .new_long_array(3) + .expect("JNIEnv#new_long_array must create a java array with given size"); + + // Insert array elements + let _ = env.set_long_array_region(java_array, 0, buf); + + // Get long array elements auto wrapper + let auto_ptr = env + .get_long_array_elements(java_array, ReleaseMode::CopyBack) + .unwrap(); + + // Copying the array depends on the VM vendor/version/GC combinations. + // If the wrapped array is not being copied, we can skip the test. + if !auto_ptr.is_copy() { + return; + } + + // Check pointer access + let ptr = auto_ptr.as_ptr(); + + // Modify + unsafe { + *ptr.offset(0) += 1; + *ptr.offset(1) += 1; + *ptr.offset(2) += 1; + } + + // Check that original Java array is unmodified + let mut res: [i64; 3] = [0; 3]; + env.get_long_array_region(java_array, 0, &mut res).unwrap(); + assert_eq!(res[0], 1); + assert_eq!(res[1], 2); + assert_eq!(res[2], 3); + + auto_ptr.commit().unwrap(); + + // Confirm modification of original Java array + env.get_long_array_region(java_array, 0, &mut res).unwrap(); + assert_eq!(res[0], 2); + assert_eq!(res[1], 3); + assert_eq!(res[2], 4); +} + +#[test] +pub fn get_primitive_array_critical() { + let env = attach_current_thread(); + + // Create original Java array + let buf: &[u8] = &[1, 2, 3]; + let java_array = env + .byte_array_from_slice(buf) + .expect("JNIEnv#byte_array_from_slice must create a java array from slice"); + + // Use a scope to test Drop + { + // Get primitive array elements auto wrapper + let auto_ptr = env + .get_primitive_array_critical(java_array, ReleaseMode::CopyBack) + .unwrap(); + + // Check array size + assert_eq!(auto_ptr.size().unwrap(), 3); + + // Get pointer + let ptr = auto_ptr.as_ptr(); + + // Convert void pointer to an unsigned byte array, without copy + let mut vec; + unsafe { vec = Vec::from_raw_parts(ptr as *mut u8, 3, 3) } + + // Check + assert_eq!(vec[0], 1); + assert_eq!(vec[1], 2); + assert_eq!(vec[2], 3); + + // Modify + vec[0] += 1; + vec[1] += 1; + vec[2] += 1; + + // Release + // Make sure vec's destructor doesn't free the data it thinks it owns when it goes out + // of scope (avoid double free) + std::mem::forget(vec); + } + + // Confirm modification of original Java array + let mut res: [i8; 3] = [0; 3]; + env.get_byte_array_region(java_array, 0, &mut res).unwrap(); + assert_eq!(res[0], 2); + assert_eq!(res[1], 3); + assert_eq!(res[2], 4); +} + +#[test] +pub fn get_object_class() { + let env = attach_current_thread(); + let string = env.new_string("test").unwrap(); + let result = env.get_object_class(string); + assert!(result.is_ok()); + assert!(!result.unwrap().is_null()); +} + +#[test] +pub fn get_object_class_null_arg() { + let env = attach_current_thread(); + let null_obj = JObject::null(); + let result = env + .get_object_class(null_obj) + .map_err(|error| matches!(error, Error::NullPtr(_))) + .expect_err("JNIEnv#get_object_class should return error for null argument"); + assert!(result, "ErrorKind::NullPtr expected as error"); +} + +#[test] +pub fn new_direct_byte_buffer() { + let env = attach_current_thread(); + let vec: Vec = 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 vec: Vec = 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(); + assert_eq!(capacity, 4); +} + +#[test] +pub fn get_direct_buffer_capacity_wrong_arg() { + let env = attach_current_thread(); + let wrong_obj = unsafe { JByteBuffer::from_raw(env.new_string("wrong").unwrap().into_raw()) }; + let capacity = env.get_direct_buffer_capacity(wrong_obj); + 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 vec: Vec = 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!(addr, dest_buffer); +} + +#[test] +pub fn get_direct_buffer_address_wrong_arg() { + let env = attach_current_thread(); + let wrong_obj: JObject = env.new_string("wrong").unwrap().into(); + let result = env.get_direct_buffer_address(wrong_obj.into()); + assert!(result.is_err()); +} + +#[test] +pub fn get_direct_buffer_address_null_arg() { + let env = attach_current_thread(); + let result = env.get_direct_buffer_address(JObject::null().into()); + assert!(result.is_err()); +} + +// Group test for testing the family of new_PRIMITIVE_array functions with correct arguments +#[test] +pub fn new_primitive_array_ok() { + let env = attach_current_thread(); + const SIZE: jsize = 16; + + let result = env.new_boolean_array(SIZE); + assert!(result.is_ok()); + assert!(!result.unwrap().is_null()); + + let result = env.new_byte_array(SIZE); + assert!(result.is_ok()); + assert!(!result.unwrap().is_null()); + + let result = env.new_char_array(SIZE); + assert!(result.is_ok()); + assert!(!result.unwrap().is_null()); + + let result = env.new_short_array(SIZE); + assert!(result.is_ok()); + assert!(!result.unwrap().is_null()); + + let result = env.new_int_array(SIZE); + assert!(result.is_ok()); + assert!(!result.unwrap().is_null()); + + let result = env.new_long_array(SIZE); + assert!(result.is_ok()); + assert!(!result.unwrap().is_null()); + + let result = env.new_float_array(SIZE); + assert!(result.is_ok()); + assert!(!result.unwrap().is_null()); + + let result = env.new_double_array(SIZE); + assert!(result.is_ok()); + assert!(!result.unwrap().is_null()); +} + +// Group test for testing the family of new_PRIMITIVE_array functions with wrong arguments +#[test] +pub fn new_primitive_array_wrong() { + let env = attach_current_thread(); + const WRONG_SIZE: jsize = -1; + + let result = env.new_boolean_array(WRONG_SIZE); + assert_exception(&result, "JNIEnv#new_boolean_array should throw exception"); + assert_pending_java_exception(&env); + + let result = env.new_byte_array(WRONG_SIZE); + assert_exception(&result, "JNIEnv#new_byte_array should throw exception"); + assert_pending_java_exception(&env); + + let result = env.new_char_array(WRONG_SIZE); + assert_exception(&result, "JNIEnv#new_char_array should throw exception"); + assert_pending_java_exception(&env); + + let result = env.new_short_array(WRONG_SIZE); + assert_exception(&result, "JNIEnv#new_short_array should throw exception"); + assert_pending_java_exception(&env); + + let result = env.new_int_array(WRONG_SIZE); + assert_exception(&result, "JNIEnv#new_int_array should throw exception"); + assert_pending_java_exception(&env); + + let result = env.new_long_array(WRONG_SIZE); + assert_exception(&result, "JNIEnv#new_long_array should throw exception"); + assert_pending_java_exception(&env); + + let result = env.new_float_array(WRONG_SIZE); + assert_exception(&result, "JNIEnv#new_float_array should throw exception"); + assert_pending_java_exception(&env); + + let result = env.new_double_array(WRONG_SIZE); + assert_exception(&result, "JNIEnv#new_double_array should throw exception"); + assert_pending_java_exception(&env); +} + +#[test] +fn get_super_class_ok() { + let env = attach_current_thread(); + let result = env.get_superclass(ARRAYLIST_CLASS); + assert!(result.is_ok()); + assert!(!result.unwrap().is_null()); +} + +#[test] +fn get_super_class_null() { + let env = attach_current_thread(); + let result = env.get_superclass("java/lang/Object"); + assert!(result.is_ok()); + assert!(result.unwrap().is_null()); +} + +#[test] +fn convert_byte_array() { + let env = attach_current_thread(); + let src: Vec = vec![1, 2, 3, 4]; + let java_byte_array = env.byte_array_from_slice(&src).unwrap(); + + let dest = env.convert_byte_array(java_byte_array); + assert!(dest.is_ok()); + assert_eq!(dest.unwrap(), src); +} + +#[test] +fn local_ref_null() { + let env = attach_current_thread(); + let null_obj = JObject::null(); + + let result = env.new_local_ref::(null_obj); + assert!(result.is_ok()); + assert!(result.unwrap().is_null()); + + // try to delete null reference + let result = env.delete_local_ref(null_obj); + assert!(result.is_ok()); +} + +#[test] +fn new_global_ref_null() { + let env = attach_current_thread(); + let null_obj = JObject::null(); + let result = env.new_global_ref(null_obj); + assert!(result.is_ok()); + assert!(result.unwrap().as_obj().is_null()); +} + +#[test] +fn auto_local_null() { + let env = attach_current_thread(); + let null_obj = JObject::null(); + { + let auto_ref = AutoLocal::new(&env, null_obj); + assert!(auto_ref.as_obj().is_null()); + } + assert!(null_obj.is_null()); +} + +#[test] +fn short_lifetime_with_local_frame() { + let env = attach_current_thread(); + let object = short_lifetime_with_local_frame_sub_fn(&env); + assert!(object.is_ok()); +} + +fn short_lifetime_with_local_frame_sub_fn<'a>(env: &'_ JNIEnv<'a>) -> Result, Error> { + env.with_local_frame(16, || { + env.new_object(INTEGER_CLASS, "(I)V", &[JValue::from(5)]) + }) +} + +#[test] +fn short_lifetime_list() { + let env = attach_current_thread(); + let first_list_object = short_lifetime_list_sub_fn(&env).unwrap(); + let value = env.call_method(first_list_object, "intValue", "()I", &[]); + assert_eq!(value.unwrap().i().unwrap(), 1); +} + +fn short_lifetime_list_sub_fn<'a>(env: &'_ JNIEnv<'a>) -> Result, Error> { + let list_object = env.new_object(ARRAYLIST_CLASS, "()V", &[])?; + let list = JList::from_env(env, list_object)?; + let element = env.new_object(INTEGER_CLASS, "(I)V", &[JValue::from(1)])?; + list.add(element)?; + short_lifetime_list_sub_fn_get_first_element(&list) +} + +fn short_lifetime_list_sub_fn_get_first_element<'a>( + list: &'_ JList<'a, '_>, +) -> Result, Error> { + let mut iterator = list.iter()?; + Ok(iterator.next().unwrap()) +} + +#[test] +fn get_object_array_element() { + let env = attach_current_thread(); + let array = env + .new_object_array(1, STRING_CLASS, JObject::null()) + .unwrap(); + assert!(!array.is_null()); + assert!(env.get_object_array_element(array, 0).unwrap().is_null()); + let test_str = env.new_string("test").unwrap(); + env.set_object_array_element(array, 0, test_str).unwrap(); + assert!(!env.get_object_array_element(array, 0).unwrap().is_null()); +} + +#[test] +pub fn throw_new() { + let env = attach_current_thread(); + + let result = env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception"); + assert!(result.is_ok()); + assert_pending_java_exception_detailed( + &env, + Some(RUNTIME_EXCEPTION_CLASS), + Some("Test Exception"), + ); +} + +#[test] +pub fn throw_new_fail() { + let env = attach_current_thread(); + + let result = env.throw_new("java/lang/NonexistentException", "Test Exception"); + assert!(result.is_err()); + // Just to clear the java.lang.NoClassDefFoundError + assert_pending_java_exception(&env); +} + +#[test] +pub fn throw_defaults() { + let env = attach_current_thread(); + + test_throwable_descriptor_with_default_type(&env, TEST_EXCEPTION_MESSAGE); + test_throwable_descriptor_with_default_type(&env, TEST_EXCEPTION_MESSAGE.to_owned()); + test_throwable_descriptor_with_default_type(&env, JNIString::from(TEST_EXCEPTION_MESSAGE)); +} + +#[test] +pub fn test_conversion() { + let env = attach_current_thread(); + let orig_obj: JObject = env.new_string("Hello, world!").unwrap().into(); + + let string = JString::from(orig_obj); + let actual = JObject::from(string); + assert!(unwrap(&env, env.is_same_object(orig_obj, actual))); + + let global_ref = env.new_global_ref(orig_obj).unwrap(); + let actual = JObject::from(&global_ref); + assert!(unwrap(&env, env.is_same_object(orig_obj, actual))); + + let auto_local = env.auto_local(orig_obj); + let actual = JObject::from(&auto_local); + assert!(unwrap(&env, env.is_same_object(orig_obj, actual))); +} + +fn test_throwable_descriptor_with_default_type<'a, D>(env: &JNIEnv<'a>, descriptor: D) +where + D: Desc<'a, JThrowable<'a>>, +{ + let result = descriptor.lookup(env); + assert!(result.is_ok()); + let exception = result.unwrap(); + + assert_exception_type(env, exception, RUNTIME_EXCEPTION_CLASS); + assert_exception_message(env, exception, TEST_EXCEPTION_MESSAGE); +} + +// Helper method that asserts that result is Error and the cause is JavaException. +fn assert_exception(res: &Result, expect_message: &str) { + assert!(res.is_err()); + assert!(res + .as_ref() + .map_err(|error| matches!(error, Error::JavaException)) + .expect_err(expect_message)); +} + +// Shortcut to `assert_pending_java_exception_detailed()` without checking for expected type and +// message of exception. +fn assert_pending_java_exception(env: &JNIEnv) { + assert_pending_java_exception_detailed(env, None, None) +} + +// Helper method that asserts there is a pending Java exception of `expected_type` with +// `expected_message` and clears it if any. +fn assert_pending_java_exception_detailed( + env: &JNIEnv, + expected_type: Option<&str>, + expected_message: Option<&str>, +) { + assert!(env.exception_check().unwrap()); + let exception = env.exception_occurred().expect("Unable to get exception"); + env.exception_clear().unwrap(); + + if let Some(expected_type) = expected_type { + assert_exception_type(env, exception, expected_type); + } + + if let Some(expected_message) = expected_message { + assert_exception_message(env, exception, expected_message); + } +} + +// Asserts that exception is of `expected_type` type. +fn assert_exception_type(env: &JNIEnv, exception: JThrowable, expected_type: &str) { + assert!(env.is_instance_of(exception, expected_type).unwrap()); +} + +// Asserts that exception's message is `expected_message`. +fn assert_exception_message(env: &JNIEnv, exception: JThrowable, expected_message: &str) { + let message = env + .call_method(exception, "getMessage", "()Ljava/lang/String;", &[]) + .unwrap() + .l() + .unwrap(); + let msg_rust: String = env.get_string(message.into()).unwrap().into(); + assert_eq!(msg_rust, expected_message); +} diff --git a/0.20.0/tests/jni_global_refs.rs b/0.20.0/tests/jni_global_refs.rs new file mode 100644 index 0000000..2adc628 --- /dev/null +++ b/0.20.0/tests/jni_global_refs.rs @@ -0,0 +1,80 @@ +#![cfg(feature = "invocation")] + +use std::{ + sync::{Arc, Barrier}, + thread::spawn, +}; + +use jni::{ + objects::{AutoLocal, JValue}, + sys::jint, +}; + +mod util; +use util::{attach_current_thread, unwrap}; + +#[test] +pub fn global_ref_works_in_other_threads() { + const ITERS_PER_THREAD: usize = 10_000; + + let env = attach_current_thread(); + let mut join_handlers = Vec::new(); + + let atomic_integer = { + let local_ref = AutoLocal::new( + &env, + unwrap( + &env, + env.new_object( + "java/util/concurrent/atomic/AtomicInteger", + "(I)V", + &[JValue::from(0)], + ), + ), + ); + unwrap(&env, env.new_global_ref(&local_ref)) + }; + + // Test with a different number of threads (from 2 to 8) + for thread_num in 2..9 { + let barrier = Arc::new(Barrier::new(thread_num)); + + for _ in 0..thread_num { + let barrier = barrier.clone(); + let atomic_integer = atomic_integer.clone(); + + let jh = spawn(move || { + let env = attach_current_thread(); + barrier.wait(); + for _ in 0..ITERS_PER_THREAD { + unwrap( + &env, + unwrap( + &env, + env.call_method(&atomic_integer, "incrementAndGet", "()I", &[]), + ) + .i(), + ); + } + }); + join_handlers.push(jh); + } + + for jh in join_handlers.drain(..) { + jh.join().unwrap(); + } + + let expected = (ITERS_PER_THREAD * thread_num) as jint; + assert_eq!( + expected, + unwrap( + &env, + unwrap( + &env, + env.call_method(&atomic_integer, "getAndSet", "(I)I", &[JValue::from(0)]) + ) + .i() + ) + ); + } +} diff --git a/0.20.0/tests/threads_attach_guard.rs b/0.20.0/tests/threads_attach_guard.rs new file mode 100644 index 0000000..d43f807 --- /dev/null +++ b/0.20.0/tests/threads_attach_guard.rs @@ -0,0 +1,18 @@ +#![cfg(feature = "invocation")] + +mod util; +use util::{attach_current_thread, call_java_abs, jvm}; + +#[test] +fn thread_attach_guard_detaches_on_drop() { + assert_eq!(jvm().threads_attached(), 0); + { + let guard = attach_current_thread(); + assert_eq!(jvm().threads_attached(), 1); + let val = call_java_abs(&guard, -1); + assert_eq!(val, 1); + } + assert_eq!(jvm().threads_attached(), 0); + // Verify that this thread is really detached. + assert!(jvm().get_env().is_err()); +} diff --git a/0.20.0/tests/threads_detach.rs b/0.20.0/tests/threads_detach.rs new file mode 100644 index 0000000..922bca7 --- /dev/null +++ b/0.20.0/tests/threads_detach.rs @@ -0,0 +1,19 @@ +#![cfg(feature = "invocation")] + +use std::thread::spawn; + +mod util; +use util::{attach_current_thread_permanently, call_java_abs, jvm}; + +#[test] +fn thread_detaches_when_finished() { + let thread = spawn(|| { + let env = attach_current_thread_permanently(); + let val = call_java_abs(&env, -2); + assert_eq!(val, 2); + assert_eq!(jvm().threads_attached(), 1); + }); + + thread.join().unwrap(); + assert_eq!(jvm().threads_attached(), 0); +} diff --git a/0.20.0/tests/threads_detach_daemon.rs b/0.20.0/tests/threads_detach_daemon.rs new file mode 100644 index 0000000..9531a35 --- /dev/null +++ b/0.20.0/tests/threads_detach_daemon.rs @@ -0,0 +1,19 @@ +#![cfg(feature = "invocation")] + +use std::thread::spawn; + +mod util; +use util::{attach_current_thread_as_daemon, call_java_abs, jvm}; + +#[test] +fn daemon_thread_detaches_when_finished() { + let thread = spawn(|| { + let env = attach_current_thread_as_daemon(); + let val = call_java_abs(&env, -3); + assert_eq!(val, 3); + assert_eq!(jvm().threads_attached(), 1); + }); + + thread.join().unwrap(); + assert_eq!(jvm().threads_attached(), 0); +} diff --git a/0.20.0/tests/threads_explicit_detach.rs b/0.20.0/tests/threads_explicit_detach.rs new file mode 100644 index 0000000..3010ca3 --- /dev/null +++ b/0.20.0/tests/threads_explicit_detach.rs @@ -0,0 +1,17 @@ +#![cfg(feature = "invocation")] + +mod util; +use util::{attach_current_thread, call_java_abs, detach_current_thread, jvm}; + +#[test] +pub fn explicit_detach_detaches_thread_attached_locally() { + assert_eq!(jvm().threads_attached(), 0); + let guard = attach_current_thread(); + let val = call_java_abs(&guard, -1); + assert_eq!(val, 1); + assert_eq!(jvm().threads_attached(), 1); + + detach_current_thread(); + assert_eq!(jvm().threads_attached(), 0); + assert!(jvm().get_env().is_err()); +} diff --git a/0.20.0/tests/threads_explicit_detach_daemon.rs b/0.20.0/tests/threads_explicit_detach_daemon.rs new file mode 100644 index 0000000..76d4f86 --- /dev/null +++ b/0.20.0/tests/threads_explicit_detach_daemon.rs @@ -0,0 +1,17 @@ +#![cfg(feature = "invocation")] + +mod util; +use util::{attach_current_thread_as_daemon, call_java_abs, detach_current_thread, jvm}; + +#[test] +pub fn explicit_detach_detaches_thread_attached_as_daemon() { + assert_eq!(jvm().threads_attached(), 0); + let guard = attach_current_thread_as_daemon(); + let val = call_java_abs(&guard, -1); + assert_eq!(val, 1); + assert_eq!(jvm().threads_attached(), 1); + + detach_current_thread(); + assert_eq!(jvm().threads_attached(), 0); + assert!(jvm().get_env().is_err()); +} diff --git a/0.20.0/tests/threads_explicit_detach_permanent.rs b/0.20.0/tests/threads_explicit_detach_permanent.rs new file mode 100644 index 0000000..a58937f --- /dev/null +++ b/0.20.0/tests/threads_explicit_detach_permanent.rs @@ -0,0 +1,17 @@ +#![cfg(feature = "invocation")] + +mod util; +use util::{attach_current_thread_permanently, call_java_abs, detach_current_thread, jvm}; + +#[test] +pub fn explicit_detach_detaches_thread_attached_permanently() { + assert_eq!(jvm().threads_attached(), 0); + let guard = attach_current_thread_permanently(); + let val = call_java_abs(&guard, -1); + assert_eq!(val, 1); + assert_eq!(jvm().threads_attached(), 1); + + detach_current_thread(); + assert_eq!(jvm().threads_attached(), 0); + assert!(jvm().get_env().is_err()); +} diff --git a/0.20.0/tests/threads_nested_attach_daemon.rs b/0.20.0/tests/threads_nested_attach_daemon.rs new file mode 100644 index 0000000..2515f61 --- /dev/null +++ b/0.20.0/tests/threads_nested_attach_daemon.rs @@ -0,0 +1,48 @@ +#![cfg(feature = "invocation")] + +mod util; +use util::{ + attach_current_thread, attach_current_thread_as_daemon, attach_current_thread_permanently, + call_java_abs, jvm, +}; + +#[test] +pub fn nested_attaches_should_not_detach_daemon_thread() { + assert_eq!(jvm().threads_attached(), 0); + let env = attach_current_thread_as_daemon(); + let val = call_java_abs(&env, -1); + assert_eq!(val, 1); + assert_eq!(jvm().threads_attached(), 1); + + // Create nested AttachGuard. + { + let env_nested = attach_current_thread(); + let val = call_java_abs(&env_nested, -2); + assert_eq!(val, 2); + assert_eq!(jvm().threads_attached(), 1); + } + + // Call a Java method after nested guard has been dropped to check that + // this thread has not been detached. + let val = call_java_abs(&env, -3); + assert_eq!(val, 3); + assert_eq!(jvm().threads_attached(), 1); + + // Nested attach_permanently is a no-op. + { + let env_nested = attach_current_thread_permanently(); + let val = call_java_abs(&env_nested, -4); + assert_eq!(val, 4); + assert_eq!(jvm().threads_attached(), 1); + } + assert_eq!(jvm().threads_attached(), 1); + + // Nested attach_as_daemon is a no-op. + { + let env_nested = attach_current_thread_as_daemon(); + let val = call_java_abs(&env_nested, -5); + assert_eq!(val, 5); + assert_eq!(jvm().threads_attached(), 1); + } + assert_eq!(jvm().threads_attached(), 1); +} diff --git a/0.20.0/tests/threads_nested_attach_guard.rs b/0.20.0/tests/threads_nested_attach_guard.rs new file mode 100644 index 0000000..c9d4aca --- /dev/null +++ b/0.20.0/tests/threads_nested_attach_guard.rs @@ -0,0 +1,54 @@ +#![cfg(feature = "invocation")] + +mod util; +use util::{ + attach_current_thread, attach_current_thread_as_daemon, attach_current_thread_permanently, + call_java_abs, jvm, +}; + +#[test] +pub fn nested_attaches_should_not_detach_guarded_thread() { + assert_eq!(jvm().threads_attached(), 0); + let env = attach_current_thread(); + let val = call_java_abs(&env, -1); + assert_eq!(val, 1); + assert_eq!(jvm().threads_attached(), 1); + + // Create nested AttachGuard. + { + let env_nested = attach_current_thread(); + let val = call_java_abs(&env_nested, -2); + assert_eq!(val, 2); + assert_eq!(jvm().threads_attached(), 1); + } + + // Call a Java method after nested guard has been dropped to check that + // this thread has not been detached. + let val = call_java_abs(&env, -3); + assert_eq!(val, 3); + assert_eq!(jvm().threads_attached(), 1); + + // Nested attach_permanently is a no-op. + { + let env_nested = attach_current_thread_permanently(); + let val = call_java_abs(&env_nested, -4); + assert_eq!(val, 4); + assert_eq!(jvm().threads_attached(), 1); + } + assert_eq!(jvm().threads_attached(), 1); + + // Nested attach_as_daemon is a no-op. + { + let env_nested = attach_current_thread_as_daemon(); + let val = call_java_abs(&env_nested, -5); + assert_eq!(val, 5); + assert_eq!(jvm().threads_attached(), 1); + } + assert_eq!(jvm().threads_attached(), 1); + + // Check that after guard is dropped the thread is properly detached + // despite nested "permanent" attaches. + drop(env); + assert_eq!(jvm().threads_attached(), 0); + assert!(jvm().get_env().is_err()); +} diff --git a/0.20.0/tests/threads_nested_attach_permanently.rs b/0.20.0/tests/threads_nested_attach_permanently.rs new file mode 100644 index 0000000..42ad9ec --- /dev/null +++ b/0.20.0/tests/threads_nested_attach_permanently.rs @@ -0,0 +1,49 @@ +#![cfg(feature = "invocation")] + +mod util; +use util::{ + attach_current_thread, attach_current_thread_as_daemon, attach_current_thread_permanently, + call_java_abs, jvm, +}; + +#[test] +pub fn nested_attaches_should_not_detach_permanent_thread() { + assert_eq!(jvm().threads_attached(), 0); + let env = attach_current_thread_permanently(); + let val = call_java_abs(&env, -1); + assert_eq!(val, 1); + assert_eq!(jvm().threads_attached(), 1); + + // Create nested AttachGuard. + { + let env_nested = attach_current_thread(); + let val = call_java_abs(&env_nested, -2); + assert_eq!(val, 2); + assert_eq!(jvm().threads_attached(), 1); + } + + // Call a Java method after nested guard has been dropped to check that + // this thread has not been detached. + let val = call_java_abs(&env, -3); + assert_eq!(val, 3); + assert_eq!(jvm().threads_attached(), 1); + + // Nested attach_permanently is a no-op. + { + let env_nested = attach_current_thread_permanently(); + let val = call_java_abs(&env_nested, -4); + assert_eq!(val, 4); + assert_eq!(jvm().threads_attached(), 1); + } + assert_eq!(jvm().threads_attached(), 1); + + // Nested attach_as_daemon is a no-op. + { + let env_nested = attach_current_thread_as_daemon(); + let val = call_java_abs(&env_nested, -5); + assert_eq!(val, 5); + assert_eq!(jvm().threads_attached(), 1); + } + assert_eq!(jvm().threads_attached(), 1); + assert!(jvm().get_env().is_ok()); +} diff --git a/0.20.0/tests/util/example_proxy.rs b/0.20.0/tests/util/example_proxy.rs new file mode 100644 index 0000000..05f7d6a --- /dev/null +++ b/0.20.0/tests/util/example_proxy.rs @@ -0,0 +1,52 @@ +#![allow(dead_code)] + +use jni::{ + errors::*, + objects::{GlobalRef, JValue}, + sys::jint, + Executor, JNIEnv, +}; + +/// A test example of a native-to-JNI proxy +#[derive(Clone)] +pub struct AtomicIntegerProxy { + exec: Executor, + obj: GlobalRef, +} + +impl AtomicIntegerProxy { + /// Creates a new instance of `AtomicIntegerProxy` + pub fn new(exec: Executor, init_value: jint) -> Result { + let obj = exec.with_attached(|env: &JNIEnv| { + env.new_global_ref(env.new_object( + "java/util/concurrent/atomic/AtomicInteger", + "(I)V", + &[JValue::from(init_value)], + )?) + })?; + Ok(AtomicIntegerProxy { exec, obj }) + } + + /// Gets a current value from java object + pub fn get(&mut self) -> Result { + self.exec + .with_attached(|env| env.call_method(&self.obj, "get", "()I", &[])?.i()) + } + + /// Increments a value of java object and then gets it + pub fn increment_and_get(&mut self) -> Result { + self.exec.with_attached(|env| { + env.call_method(&self.obj, "incrementAndGet", "()I", &[])? + .i() + }) + } + + /// Adds some value to the value of java object and then gets a resulting value + pub fn add_and_get(&mut self, delta: jint) -> Result { + let delta = JValue::from(delta); + self.exec.with_attached(|env| { + env.call_method(&self.obj, "addAndGet", "(I)I", &[delta])? + .i() + }) + } +} diff --git a/0.20.0/tests/util/mod.rs b/0.20.0/tests/util/mod.rs new file mode 100644 index 0000000..fb07386 --- /dev/null +++ b/0.20.0/tests/util/mod.rs @@ -0,0 +1,85 @@ +use std::sync::{Arc, Once}; + +use jni::{ + errors::Result, objects::JValue, sys::jint, AttachGuard, InitArgsBuilder, JNIEnv, JNIVersion, + JavaVM, +}; + +mod example_proxy; +pub use self::example_proxy::AtomicIntegerProxy; + +pub fn jvm() -> &'static Arc { + static mut JVM: Option> = None; + static INIT: Once = Once::new(); + + INIT.call_once(|| { + let jvm_args = InitArgsBuilder::new() + .version(JNIVersion::V8) + .option("-Xcheck:jni") + .build() + .unwrap_or_else(|e| panic!("{:#?}", e)); + + let jvm = JavaVM::new(jvm_args).unwrap_or_else(|e| panic!("{:#?}", e)); + + unsafe { + JVM = Some(Arc::new(jvm)); + } + }); + + unsafe { JVM.as_ref().unwrap() } +} + +#[allow(dead_code)] +pub fn call_java_abs(env: &JNIEnv, value: i32) -> i32 { + env.call_static_method( + "java/lang/Math", + "abs", + "(I)I", + &[JValue::from(value as jint)], + ) + .unwrap() + .i() + .unwrap() +} + +#[allow(dead_code)] +pub fn attach_current_thread() -> AttachGuard<'static> { + jvm() + .attach_current_thread() + .expect("failed to attach jvm thread") +} + +#[allow(dead_code)] +pub fn attach_current_thread_as_daemon() -> JNIEnv<'static> { + jvm() + .attach_current_thread_as_daemon() + .expect("failed to attach jvm daemon thread") +} + +#[allow(dead_code)] +pub fn attach_current_thread_permanently() -> JNIEnv<'static> { + jvm() + .attach_current_thread_permanently() + .expect("failed to attach jvm thread permanently") +} + +#[allow(dead_code)] +pub fn detach_current_thread() { + jvm().detach_current_thread() +} + +pub fn print_exception(env: &JNIEnv) { + let exception_occurred = env.exception_check().unwrap_or_else(|e| panic!("{:?}", e)); + if exception_occurred { + env.exception_describe() + .unwrap_or_else(|e| panic!("{:?}", e)); + } +} + +#[allow(dead_code)] +pub fn unwrap(env: &JNIEnv, res: Result) -> T { + res.unwrap_or_else(|e| { + print_exception(env); + panic!("{:#?}", e); + }) +} -- cgit v1.2.3