Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

1
vendor/jni/.cargo-checksum.json vendored Normal file

File diff suppressed because one or more lines are too long

333
vendor/jni/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,333 @@
# 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).
<!-- Use the following sections from the spec: http://keepachangelog.com/en/1.0.0/
- Added for new features.
- Changed for changes in existing functionality.
- Deprecated for soon-to-be removed features.
- Removed for now removed features.
- Fixed for any bug fixes.
- Security in case of vulnerabilities. -->
## [Unreleased]
## [0.21.1] — 2023-03-08
### Fixes
- Compilation is fixed for architectures with a C ABI that has unsigned `char` types. ([#419](https://github.com/jni-rs/jni-rs/pull/419))
## [0.21.0] — 2023-02-13
This release makes extensive breaking changes in order to improve safety. Most projects that use this library will need to be changed. Please see [the migration guide](docs/0.21-MIGRATION.md).
### Added
- `JavaStr::into_raw()` which drops the `JavaStr` and releases ownership of the raw string pointer ([#374](https://github.com/jni-rs/jni-rs/pull/374))
- `JavaStr::from_raw()` which takes ownership of a raw string pointer to create a `JavaStr` ([#374](https://github.com/jni-rs/jni-rs/pull/374))
- `JNIEnv::get_string_unchecked` is a cheaper, `unsafe` alternative to `get_string` that doesn't check the given object is a `java.lang.String` instance. ([#328](https://github.com/jni-rs/jni-rs/issues/328))
- `WeakRef` and `JNIEnv#new_weak_ref`. ([#304](https://github.com/jni-rs/jni-rs/pull/304))
- `define_class_bytearray` method that takes an `AutoElements<jbyte>` rather than a `&[u8]` ([#244](https://github.com/jni-rs/jni-rs/pull/244))
- `JObject` now has an `as_raw` method that borrows the `JObject` instead of taking ownership like `into_raw`. Needed because `JObject` no longer has the `Copy` trait. ([#392](https://github.com/jni-rs/jni-rs/issues/392))
- `JavaVM::destroy()` (unsafe) as a way to try and unload a `JavaVM` on supported platforms ([#391](https://github.com/jni-rs/jni-rs/issues/391))
- `JavaVM::detach_current_thread()` (unsafe) as a way to explicitly detach a thread (normally this is automatic on thread exit). Needed to detach daemon threads manually if using `JavaVM::destroy()` ([#391](https://github.com/jni-rs/jni-rs/issues/391))
- `JPrimitiveArray<T: TypeArray>` and type-specific aliases like `JByteArray`, `JIntArray` etc now provide safe, reference wrappers for the `sys` types `jarray` and `jbyteArray` etc with a lifetime like `JObject` ([#400](https://github.com/jni-rs/jni-rs/pull/400))
- `JObjectArray` provides a reference wrapper for a `jobjectArray` with a lifetime like `JObject`. ([#400](https://github.com/jni-rs/jni-rs/pull/400))
- `AutoElements` and `AutoElementsCritical` (previously `AutoArray`/`AutoPrimitiveArray`) implement `Deref<Target=[T]>` and `DerefMut` so array elements can be accessed via slices without needing additional `unsafe` code. ([#400](https://github.com/jni-rs/jni-rs/pull/400))
- `AsJArrayRaw` trait which enables `JNIEnv::get_array_length()` to work with `JPrimitiveArray` or `JObjectArray` types ([#400](https://github.com/jni-rs/jni-rs/pull/400))
- `InitArgsBuilder` now has `try_option` and `option_encoded` methods. ([#414](https://github.com/jni-rs/jni-rs/pull/414))
### Changed
- `JNIEnv::get_string` checks that the given object is a `java.lang.String` instance to avoid undefined behaviour from the JNI implementation potentially aborting the program. ([#328](https://github.com/jni-rs/jni-rs/issues/328))
- `JNIEnv::call_*method_unchecked` was marked `unsafe`, as passing improper argument types, or a bad number of arguments, can cause a JVM crash. ([#385](https://github.com/jni-rs/jni-rs/issues/385))
- The `JNIEnv::new_object_unchecked` function now takes arguments as `&[jni::sys::jvalue]` to avoid allocating, putting it inline with changes to `JniEnv::call_*_unchecked` from 0.20.0 ([#382](https://github.com/jni-rs/jni-rs/pull/382))
- The `get_superclass` function now returns an Option instead of a null pointer if the class has no superclass ([#151](https://github.com/jni-rs/jni-rs/issues/151))
- The `invocation` feature now locates the JVM implementation dynamically at runtime (via the `java-locator` crate by default) instead of linking with the JVM at build time ([#293](https://github.com/jni-rs/jni-rs/pull/293))
- Most `JNIEnv` methods now require `&mut self`. This improves safety by preventing `JObject`s from getting an invalid lifetime. Most native method implementations (that is, `#[no_mangle] extern "system" fn`s) must now make the `JNIEnv` parameter `mut`. See the example on the crate documentation. ([#392](https://github.com/jni-rs/jni-rs/issues/392))
- `JByteBuffer`, `JClass`, `JNIEnv`, `JObject`, `JString`, and `JThrowable` no longer have the `Clone` or `Copy` traits. This improves safety by preventing object references from being used after the JVM deletes them. Most functions that take one of these types as a parameter (except `extern fn`s that are directly called by the JVM) should now borrow it instead, e.g. `&JObject` instead of `JObject`. ([#392](https://github.com/jni-rs/jni-rs/issues/392))
- `AutoLocal` is now generic in the type of object reference (`JString`, etc). ([#392](https://github.com/jni-rs/jni-rs/issues/392))
- The closure passed to `JNIEnv::with_local_frame` must now take a `&mut JNIEnv` parameter, which has a different lifetime. This improves safety by preventing local references from escaping the closure, which would cause a use-after-free bug. `Executor::with_attached` and `Executor::with_attached_capacity` have been similarly changed. ([#392](https://github.com/jni-rs/jni-rs/issues/392))
- The closure passed to `JNIEnv::with_local_frame` can now return a generic `Result<T, E>` so long as the error implements `From<jni::errors::Error>` ([#399](https://github.com/jni-rs/jni-rs/issues/399))
- `JNIEnv::with_local_frame` now returns the same type that the given closure returns ([#399](https://github.com/jni-rs/jni-rs/issues/399))
- `JNIEnv::with_local_frame` no longer supports returning a local reference directly to the calling scope (see `with_local_frame_returning_local`) ([#399](https://github.com/jni-rs/jni-rs/issues/399))
- `Executor::with_attached` and `Executor::with_attached_capacity` have been changed in the same way as `JNIEnv::with_local_frame` (they are thin wrappers) ([#399](https://github.com/jni-rs/jni-rs/issues/399))
- `Desc`, `JNIEnv::pop_local_frame`, and `TypeArray` are now `unsafe`. ([#392](https://github.com/jni-rs/jni-rs/issues/392))
- The `Desc` trait now has an associated type `Output`. Many implementations now return `AutoLocal`, so if you call `Desc::lookup` yourself and then call `as_raw` on the returned object, make sure the `AutoLocal` isn't dropped too soon (see the `Desc::lookup` documentation for examples). ([#392](https://github.com/jni-rs/jni-rs/issues/392))
- The `Desc<JClass>` trait is no longer implemented for `JObject` or `&JObject`. The previous implementation that called `.get_object_class()` was surprising and a simpler cast would make it easy to mistakenly pass instances where a class is required. ([#118](https://github.com/jni-rs/jni-rs/issues/118))
- Named lifetimes in the documentation have more descriptive names (like `'local` instead of `'a`). The new naming convention is explained in the `JNIEnv` documentation. ([#392](https://github.com/jni-rs/jni-rs/issues/392))
- Object reference types (`JObject`, `JClass`, `AutoLocal`, `GlobalRef`, etc) now implement `AsRef<JObject>` and `Deref<Target = JObject>`. Typed wrappers like `JClass` also implement `Into<JObject>`, but `GlobalRef` does not. ([#392](https://github.com/jni-rs/jni-rs/issues/392))
- Most `JList` and `JMap` methods now require a `&mut JNIEnv` parameter. `JListIter` and `JMapIter` no longer implement `Iterator`, and instead have a `next` method that requires a `&mut JNIEnv` parameter (use `while let` loops instead of `for`). ([#392](https://github.com/jni-rs/jni-rs/issues/392))
- `JValue` has been changed in several ways: ([#392](https://github.com/jni-rs/jni-rs/issues/392))
- It is now a generic type named `JValueGen`. `JValue` is now a type alias for `JValueGen<&JObject>`, that is, it borrows an object reference. `JValueOwned` is a type alias for `JValueGen<JObject>`, that is, it owns an object reference.
- `JValueOwned` does not have the `Copy` trait.
- The `to_jni` method is now named `as_jni`, and it borrows the `JValueGen` instead of taking ownership.
- `JObject` can no longer be converted directly to `JValue`, which was commonly done when calling Java methods or constructors. Instead of `obj.into()`, use `(&obj).into()`.
- All `JNIEnv` array APIs now work in terms of `JPrimitiveArray` and `JObjectArray` (reference wrappers with a lifetime) instead of `sys` types like `jarray` and `jbyteArray` ([#400](https://github.com/jni-rs/jni-rs/pull/400))
- `AutoArray` and `AutoPrimitiveArray` have been renamed `AutoElements` and `AutoElementsCritical` to show their connection and differentiate from new `JPrimitiveArray` API ([#400](https://github.com/jni-rs/jni-rs/pull/400))
- `get_primitive_array_critical` is now `unsafe` and has been renamed to `get_array_elements_critical` (consistent with the rename of `AutoPrimitiveArray`) with more detailed safety documentation ([#400](https://github.com/jni-rs/jni-rs/pull/400))
- `get_array_elements` is now also `unsafe` (for many of the same reasons as `get_array_elements_critical`) and has detailed safety documentation ([#400](https://github.com/jni-rs/jni-rs/pull/400))
- `AutoArray/AutoArrayCritical::size()` has been replaced with `.len()` which can't fail and returns a `usize` ([#400](https://github.com/jni-rs/jni-rs/pull/400))
- The `TypeArray` trait is now a private / sealed trait, that is considered to be an implementation detail for the `AutoArray` API.
- `JvmError` has several more variants and is now `non_exhaustive`. ([#414](https://github.com/jni-rs/jni-rs/pull/414))
- `InitArgsBuilder::option` raises an error on Windows if the string is too long. The limit is currently 1048576 bytes. ([#414](https://github.com/jni-rs/jni-rs/pull/414))
### Fixed
- Trying to use an object reference after it has been deleted now causes a compile error instead of undefined behavior. As a result, it is now safe to use `AutoLocal`, `JNIEnv::delete_local_ref`, and `JNIEnv::with_local_frame`. (Most of the limitations added in #392, listed above, were needed to make this work.) ([#381](https://github.com/jni-rs/jni-rs/issues/381), [#392](https://github.com/jni-rs/jni-rs/issues/392))
- Class lookups via the `Desc` trait now return `AutoLocal`s, which prevents them from leaking. ([#109](https://github.com/jni-rs/jni-rs/issues/109), [#392](https://github.com/jni-rs/jni-rs/issues/392))
- `InitArgsBuilder::option` properly encodes non-ASCII characters on Windows. ([#414](https://github.com/jni-rs/jni-rs/pull/414))
### Removed
- `get_string_utf_chars` and `release_string_utf_chars` from `JNIEnv` (See `JavaStr::into_raw()` and `JavaStr::from_raw()` instead) ([#372](https://github.com/jni-rs/jni-rs/pull/372))
- All `JNIEnv::get_<type>_array_elements()` methods have been removed as redundant since they would all be equivalent to `get_array_elements()` with the introduction of `JPrimitiveArray` ([#400](https://github.com/jni-rs/jni-rs/pull/400))
## [0.20.0] — 2022-10-17
### Added
- `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_<type>_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)
- `SetStatic<type>Field`. (#248)
- `TryFrom<JValue>` 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<JObject>` 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<jboolean>` 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.21.1...HEAD
[0.21.1]: https://github.com/jni-rs/jni-rs/compare/v0.21.0...v0.21.1
[0.21.0]: https://github.com/jni-rs/jni-rs/compare/v0.20.0...v0.21.0
[0.20.0]: https://github.com/jni-rs/jni-rs/compare/v0.19.0...v0.20.0
[0.19.0]: https://github.com/jni-rs/jni-rs/compare/v0.18.0...v0.19.0
[0.18.0]: https://github.com/jni-rs/jni-rs/compare/v0.17.0...v0.18.0
[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

73
vendor/jni/CODE_OF_CONDUCT.md vendored Normal file
View File

@@ -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/

165
vendor/jni/CONTRIBUTING.md vendored Normal file
View File

@@ -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.21.1/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: <https://github.com/jni-rs/jni-rs/issues/new>
## 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.
Its 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
<http://www.apache.org/licenses/LICENSE-2.0>)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
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.

82
vendor/jni/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,82 @@
# 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.21.1"
authors = ["Josh Chase <josh@prevoty.com>"]
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.cfg-if]
version = "1.0.0"
[dependencies.combine]
version = "4.1.0"
[dependencies.java-locator]
version = "0.1"
optional = true
[dependencies.jni-sys]
version = "0.3.0"
[dependencies.libloading]
version = "0.7"
optional = true
[dependencies.log]
version = "0.4.4"
[dependencies.thiserror]
version = "1.0.20"
[dev-dependencies.assert_matches]
version = "1.5.0"
[dev-dependencies.lazy_static]
version = "1"
[dev-dependencies.rusty-fork]
version = "0.3.0"
[build-dependencies.walkdir]
version = "2"
[features]
default = []
invocation = [
"java-locator",
"libloading",
]
[target."cfg(windows)".dependencies.windows-sys]
version = "0.45.0"
features = ["Win32_Globalization"]
[target."cfg(windows)".dev-dependencies.bytemuck]
version = "1.13.0"

201
vendor/jni/LICENSE-APACHE vendored Normal file
View File

@@ -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.

21
vendor/jni/LICENSE-MIT vendored Normal file
View File

@@ -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.

34
vendor/jni/README.md vendored Normal file
View File

@@ -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.

436
vendor/jni/benches/api_calls.rs vendored Normal file
View File

@@ -0,0 +1,436 @@
#![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 = "<init>";
static METHOD_LOCAL_DATE_TIME_OF: &str = "of";
static SIG_OBJECT_CTOR: &str = "()V";
static SIG_MATH_ABS: &str = "(I)I";
static SIG_OBJECT_HASH_CODE: &str = "()I";
static SIG_LOCAL_DATE_TIME_OF: &str = "(IIIIIII)Ljava/time/LocalDateTime;";
// 32 characters
static TEST_STRING_UNICODE: &str = "_񍷕㳧~δ򗊁᪘׷ġ˥쩽|ņ/򖕡ٶԦ萴퀉֒ٞHy󢕒%ӓ娎񢞊ăꊦȮ񳗌";
#[inline(never)]
fn native_abs(x: i32) -> i32 {
x.abs()
}
fn jni_abs_safe(env: &mut JNIEnv, x: jint) -> jint {
let x = JValue::from(x);
let v = env
.call_static_method(CLASS_MATH, METHOD_MATH_ABS, SIG_MATH_ABS, &[x])
.unwrap();
v.i().unwrap()
}
fn jni_hash_safe(env: &mut JNIEnv, obj: &JObject) -> jint {
let v = env
.call_method(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE, &[])
.unwrap();
v.i().unwrap()
}
fn jni_local_date_time_of_safe<'local>(
env: &mut JNIEnv<'local>,
year: jint,
month: jint,
day_of_month: jint,
hour: jint,
minute: jint,
second: jint,
nanosecond: jint,
) -> JObject<'local> {
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<'local, C>(
env: &mut JNIEnv<'local>,
class: C,
method_id: JStaticMethodID,
x: jint,
) -> jint
where
C: Desc<'local, JClass<'local>>,
{
let x = JValue::from(x);
let ret = ReturnType::Primitive(Primitive::Int);
let v =
unsafe { env.call_static_method_unchecked(class, method_id, ret, &[x.as_jni()]) }.unwrap();
v.i().unwrap()
}
fn jni_int_call_unchecked<'local, M>(
env: &mut JNIEnv<'local>,
obj: &JObject<'local>,
method_id: M,
) -> jint
where
M: Desc<'local, JMethodID>,
{
let ret = ReturnType::Primitive(Primitive::Int);
// SAFETY: Caller retrieved method ID + class specifically for this use: Object.hashCode()I
let v = unsafe { env.call_method_unchecked(obj, method_id, ret, &[]) }.unwrap();
v.i().unwrap()
}
fn jni_object_call_static_unchecked<'local, C>(
env: &mut JNIEnv<'local>,
class: C,
method_id: JStaticMethodID,
args: &[jvalue],
) -> JObject<'local>
where
C: Desc<'local, JClass<'local>>,
{
// SAFETY: Caller retrieved method ID and constructed arguments
let v = unsafe { env.call_static_method_unchecked(class, method_id, ReturnType::Object, args) }
.unwrap();
v.l().unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
use jni::objects::GlobalRef;
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 mut env = VM.attach_current_thread().unwrap();
b.iter(|| jni_abs_safe(&mut env, -3));
}
#[bench]
fn jni_call_static_abs_method_unchecked_str(b: &mut Bencher) {
let mut env = VM.attach_current_thread().unwrap();
let class = CLASS_MATH;
let method_id = env
.get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS)
.unwrap();
b.iter(|| jni_int_call_static_unchecked(&mut env, class, method_id, -3));
}
#[bench]
fn jni_call_static_abs_method_unchecked_jclass(b: &mut Bencher) {
let mut env = VM.attach_current_thread().unwrap();
let class = Desc::<JClass>::lookup(CLASS_MATH, &mut env).unwrap();
let method_id = env
.get_static_method_id(&class, METHOD_MATH_ABS, SIG_MATH_ABS)
.unwrap();
b.iter(|| jni_int_call_static_unchecked(&mut env, &class, method_id, -3));
}
#[bench]
fn jni_call_static_date_time_method_safe(b: &mut Bencher) {
let mut env = VM.attach_current_thread().unwrap();
b.iter(|| {
let obj = jni_local_date_time_of_safe(&mut env, 1, 1, 1, 1, 1, 1, 1);
env.delete_local_ref(obj).unwrap();
});
}
#[bench]
fn jni_call_static_date_time_method_unchecked_jclass(b: &mut Bencher) {
let mut env = VM.attach_current_thread().unwrap();
let class = Desc::<JClass>::lookup(CLASS_LOCAL_DATE_TIME, &mut env).unwrap();
let method_id = env
.get_static_method_id(&class, METHOD_LOCAL_DATE_TIME_OF, SIG_LOCAL_DATE_TIME_OF)
.unwrap();
b.iter(|| {
let obj = jni_object_call_static_unchecked(
&mut env,
&class,
method_id,
&[
JValue::Int(1).as_jni(),
JValue::Int(1).as_jni(),
JValue::Int(1).as_jni(),
JValue::Int(1).as_jni(),
JValue::Int(1).as_jni(),
JValue::Int(1).as_jni(),
JValue::Int(1).as_jni(),
],
);
env.delete_local_ref(obj).unwrap();
});
}
#[bench]
fn jni_call_object_hash_method_safe(b: &mut Bencher) {
let mut env = VM.attach_current_thread().unwrap();
let s = env.new_string("").unwrap();
let obj = black_box(JObject::from(s));
b.iter(|| jni_hash_safe(&mut env, &obj));
}
#[bench]
fn jni_call_object_hash_method_unchecked(b: &mut Bencher) {
let mut env = VM.attach_current_thread().unwrap();
let s = env.new_string("").unwrap();
let obj = black_box(JObject::from(s));
let obj_class = env.get_object_class(&obj).unwrap();
let method_id = env
.get_method_id(&obj_class, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE)
.unwrap();
b.iter(|| jni_int_call_unchecked(&mut env, &obj, method_id));
}
#[bench]
fn jni_new_object_str(b: &mut Bencher) {
let mut 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 mut env = VM.attach_current_thread().unwrap();
let class = CLASS_OBJECT;
let ctor_id = env
.get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR)
.unwrap();
b.iter(|| {
let obj = unsafe { env.new_object_unchecked(class, ctor_id, &[]) }.unwrap();
env.delete_local_ref(obj).unwrap();
});
}
#[bench]
fn jni_new_object_jclass(b: &mut Bencher) {
let mut env = VM.attach_current_thread().unwrap();
let class = Desc::<JClass>::lookup(CLASS_OBJECT, &mut env).unwrap();
b.iter(|| {
let obj = env.new_object(&class, SIG_OBJECT_CTOR, &[]).unwrap();
env.delete_local_ref(obj).unwrap();
});
}
#[bench]
fn jni_new_object_by_id_jclass(b: &mut Bencher) {
let mut env = VM.attach_current_thread().unwrap();
let class = Desc::<JClass>::lookup(CLASS_OBJECT, &mut env).unwrap();
let ctor_id = env
.get_method_id(&class, METHOD_CTOR, SIG_OBJECT_CTOR)
.unwrap();
b.iter(|| {
let obj = unsafe { env.new_object_unchecked(&class, ctor_id, &[]) }.unwrap();
env.delete_local_ref(obj).unwrap();
});
}
#[bench]
fn jni_new_global_ref(b: &mut Bencher) {
let mut env = VM.attach_current_thread().unwrap();
let class = CLASS_OBJECT;
let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
let global_ref = env.new_global_ref(&obj).unwrap();
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();
});
}
#[bench]
fn jni_get_string(b: &mut Bencher) {
let mut env = VM.attach_current_thread().unwrap();
let string = env.new_string(TEST_STRING_UNICODE).unwrap();
b.iter(|| {
let s: String = env.get_string(&string).unwrap().into();
assert_eq!(s, TEST_STRING_UNICODE);
});
}
#[bench]
fn jni_get_string_unchecked(b: &mut Bencher) {
let env = VM.attach_current_thread().unwrap();
let string = env.new_string(TEST_STRING_UNICODE).unwrap();
b.iter(|| {
let s: String = unsafe { env.get_string_unchecked(&string) }.unwrap().into();
assert_eq!(s, TEST_STRING_UNICODE);
});
}
/// A benchmark measuring Push/PopLocalFrame overhead.
///
/// Such operations are *required* if one attaches a long-running
/// 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 mut env = VM.attach_current_thread().unwrap();
b.iter(|| {
env.with_local_frame(LOCAL_FRAME_SIZE, |_| -> Result<_, jni::errors::Error> {
Ok(())
})
.unwrap()
});
}
/// A benchmark measuring Push/PopLocalFrame overhead while retuning a local reference
#[bench]
fn jni_with_local_frame_returning_local(b: &mut Bencher) {
// Local frame size actually doesn't matter since JVM does not preallocate anything.
const LOCAL_FRAME_SIZE: i32 = 32;
let mut env = VM.attach_current_thread().unwrap();
let class = env.find_class(CLASS_OBJECT).unwrap();
b.iter(|| {
env.with_local_frame_returning_local(LOCAL_FRAME_SIZE, |env| {
env.new_object(&class, SIG_OBJECT_CTOR, &[])
})
});
}
/// A benchmark measuring Push/PopLocalFrame overhead while retuning a global
/// object reference that then gets converted into a local reference before
/// dropping the global
#[bench]
fn jni_with_local_frame_returning_global_to_local(b: &mut Bencher) {
// Local frame size actually doesn't matter since JVM does not preallocate anything.
const LOCAL_FRAME_SIZE: i32 = 32;
let mut env = VM.attach_current_thread().unwrap();
let class = env.find_class(CLASS_OBJECT).unwrap();
b.iter(|| {
let global = env
.with_local_frame::<_, GlobalRef, jni::errors::Error>(LOCAL_FRAME_SIZE, |env| {
let local = env.new_object(&class, SIG_OBJECT_CTOR, &[])?;
let global = env.new_global_ref(local)?;
Ok(global)
})
.unwrap();
let _local = env.new_local_ref(global).unwrap();
});
}
/// 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 mut env = VM.attach_current_thread().unwrap();
let class = CLASS_OBJECT;
let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
let global_ref = env.new_global_ref(&obj).unwrap();
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));
});
}
}

2
vendor/jni/clippy.toml vendored Normal file
View File

@@ -0,0 +1,2 @@
# Temporary increased until https://github.com/jni-rs/jni-rs/issues/184
cognitive-complexity-threshold = 120

326
vendor/jni/docs/0.21-MIGRATION.md vendored Normal file
View File

@@ -0,0 +1,326 @@
# Migrating to 0.21
Version 0.21 makes extensive breaking changes in order to improve safety. Most projects that use this library will need to be changed accordingly.
This is a guide for migrating to 0.21. For a full list of changes in this release, please see the [changelog](../CHANGELOG.md).
Most of these changes are needed to ensure that all local references (`JObject` and the like) have the correct lifetime and can't be used after they're deleted, which would cause undefined behavior. See [issue #392](https://github.com/jni-rs/jni-rs/issues/392) for a discussion of the problem these changes solve.
## `JNIEnv` parameter of `extern fn`s should now be `mut`
In `extern fn`s that are directly called by the JVM, the `JNIEnv` parameter will usually need to be `mut`.
```rust
#[no_mangle]
pub extern "system" fn Java_HelloWorld_hello<'local>(mut env: JNIEnv<'local>,
class: JClass<'local>,
input: JString<'local>)
-> jstring {
}
```
This is needed because most `JNIEnv` methods now take a `&mut self` parameter.
## `JNIEnv`, `JObject`, etc are now `!Copy` and should be borrowed
Functions that are *not* directly called by the JVM should, in most cases, borrow local references (`JObject`, `JString`, and so on) and mutably borrow `JNIEnv`s.
```rust
pub fn print_string(env: &mut JNIEnv,
string: &JString)
-> Result<()> {
println!("{}", env.get_string(string)?.to_string_lossy());
Ok(())
}
```
This is needed because these types no longer have the `Copy` or `Clone` traits.
## `JNIEnv::with_local_frame` closure now takes a `&mut JNIEnv` parameter
When using `JNIEnv::with_local_frame`, `Executor::with_attached`, or `Executor::with_attached_capacity`, the closure must now take a parameter of type `&mut JNIEnv`.
```rust
env.with_local_frame(16, |env| {
})
```
The closure must only use the `JNIEnv` passed to it in that parameter, and not the `JNIEnv` that `with_local_frame` was called on.
## `JNIEnv::with_local_frame` closure can return a generic `Result`
_Note: This also applies to `Executor::with_attached` and `Executor::with_attached_capacity` which are thin wrappers over `with_local_frame`_
The closure passed to `with_local_frame` is now free to return a generic `Result` as long as the error type implements `From<jni::errors::Error>`.
This can be particularly beneficial when running large amounts of code within a local frame in a crate that defines its own `Result` and `Error` types which need to be propagated to the caller.
There are a few trade offs with this change though:
1. Sometimes the compiler won't be able to infer the generic error type (E.g. for code that simply returns `Ok(())`) and so it has to be explicitly specified
2. Since it's no longer assumed that code always wants to return a single local reference this special case has to be handled differently
Two options for clarifying the error type for the compiler if it's ambiguous would be:
1. Specify the type of the return value as part of an assignment:
```rust
let result: MyResult<()> = env.with_local_frame(10, |env| { Ok(()) });
```
2. Specify the generic `E` error parameter:
```rust
env.with_local_frame::<_, _, MyError>(10, |env| { Ok(()) })?;
```
Code that returns a local reference to the calling frame can either use `JNIEnv::with_local_frame_returning_local` (which is marginally optimized for that special case) or else return a `GlobalRef` instead. (This approach works reasonably well in Rust, compared to C, because a global reference will be automatically deleted once it is dropped so it doesn't introduce a memory leak hazard like it would in C)
## Passing object reference parameters to Java methods
When passing an object reference as a parameter to a Java method or constructor, it needs to be explicitly borrowed, as in `(&obj).into()`, instead of simply `obj.into()`.
```rust
env.call_static_method(
"com/example/SomeClass",
"someStaticMethod",
"(Ljava/lang/Object;)V",
&[
(&obj).into(),
],
)
```
## `JList` and `JMap` methods all take a `&mut JNIEnv` parameter
All methods of `JList` and `JMap` now require a parameter of type `&mut JNIEnv`. They no longer store the `JNIEnv` that was used to construct them.
This is needed because most `JNIEnv` methods now take a `&mut self` parameter, and if the `JList` or `JMap` did store a `&mut JNIEnv`, then the caller would be unable to use the `JNIEnv` for anything else without first dropping the `JList` or `JMap`.
## `JList` and `JMap` iterators no longer implement `Iterator`
Just like the methods of `JList` and `JMap`, their iterator now also requires a `&mut JNIEnv` in order to get the next element.
For this reason, the iterator returned by `JList::iter` and `JMap::iter` no longer implements `std::iter::Iterator` and can no longer be used with a `for` loop. Instead, it has a `next` method that takes a `&mut JNIEnv` parameter, and can be used with a `while let` loop.
```rust
let mut iterator = list.iter(env)?;
while let Some(obj) = iterator.next(env)? {
let obj: AutoLocal<JObject> = env.auto_local(obj);
// Do something with `obj` here.
}
```
## Local references no longer leak
`JNIEnv` methods that look up Java classes no longer leak local references ([#109](https://github.com/jni-rs/jni-rs/issues/109)), so it is no longer necessary to wrap calls to these methods inside `JNIEnv::with_local_frame`.
## `cannot borrow as mutable`
If your project has a function that takes two or more parameters, one of them is `JNIEnv`, and another is something returned by a `JNIEnv` method (like `JObject`), then calls to that function may not compile.
```rust
fn example_function(
env: &mut JNIEnv,
obj: &JObject,
) {
// …
}
example_function(
env,
// ERROR: cannot borrow `*env` as mutable more than once at a time
&env.new_object(
"com/example/SomeClass",
"()V",
&[],
)?,
)
```
To fix this, the `JNIEnv` parameter needs to come *last*.
```rust
fn example_function(
obj: &JObject,
env: &mut JNIEnv,
) {
// …
}
example_function(
&env.new_object(
"com/example/SomeClass",
"()V",
&[],
)?,
env,
);
```
## `invocation` feature now finds and loads the JVM dynamically; `JavaVM::new` returns different error
The `invocation` feature has been changed to locate and load the Java Virtual Machine at run time, using the [java-locator](https://crates.io/crates/java-locator) and [libloading](https://crates.io/crates/libloading) libraries.
`JavaVM::new` now returns a different error type, `StartJvmError`, when it fails. This new error type covers failures to locate and load the JVM library as well as failures to initialize the JVM.
If you need to load a specific JVM library instead of automatically discovering one, use `JavaVM::with_libjvm` instead.
On non-Windows platforms, it is no longer necessary for the Java runtime's `lib` folder to be on the shared library search path (`LD_LIBRARY_PATH` or equivalent). Unfortunately, this is *not* true on Windows, where the Java runtime's DLLs must still be on the `PATH` (or added to the DLL search path with [`SetDllDirectory`](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw)).
## `Desc` has been redesigned
The `Desc` trait has been redesigned. If your project contains any implementations of `Desc` and/or calls to `Desc::lookup`, they will need to be updated.
If your project uses `Desc` directly like this, please post a comment on [issue #403](https://github.com/jni-rs/jni-rs/issues/403) explaining your situation. We view `Desc` as an implementation detail, and are considering sealing it and hiding its contents in a future release.
Changes to `Desc` in this release are as follows:
- `Desc` is now an `unsafe trait`. The safety requirements haven't actually changed, but they were undocumented until now.
- `Desc` now has an associated type, `Output`, which is now the type returned by the `lookup` method.
Please see the documentation for the `Desc` trait for more information.
## `JNIEnv::call_*method_unchecked` is now `unsafe`
The `JNIEnv::call_*method_unchecked` methods are now `unsafe`. Their safety requirements haven't changed, but they were undocumented until now.
## `AutoLocal` type and lifetime parameters changed
`AutoLocal` now has one lifetime parameter, representing the local reference frame it belongs to, and one type parameter, representing the type of object reference it contains (`JObject`, `JClass`, and so on).
This means that the object reference stored in an `AutoLocal` no longer has its type erased. If it was a `JClass` before wrapping it in `AutoLocal`, it will still be a `JClass` after.
```rust
// `AutoLocal` type parameters now include the type of object reference…
let auto_local: AutoLocal<'local, JClass<'local>>;
// …so the type of the object reference is now kept instead of being erased.
let local: &JClass<'local> = &*auto_local;
```
## `JObject` no longer implements `From<&AutoLocal> + From<&GlobalRef>`
It is no longer possible to directly convert an `&AutoLocal` or `&GlobalRef` into a `JObject`. Instead, a `JObject` can be *borrowed* from `AutoLocal` or `GlobalRef` through their implementations of `Deref` and/or `AsRef<JObject>`.
```rust
let global_ref: GlobalRef;
// You can get a `JObject` from a `GlobalRef` two ways:
let object_ref: &JObject<'static> = &*global_ref;
let object_ref: &JObject<'static> = global_ref.as_ref();
```
## `JNIEnv::get_superclass` now returns `Option`
The `JNIEnv::get_superclass` method previously returned a `JClass`, which would be null if the class in question doesn't have a superclass. It now returns `Option<JClass>` instead, and when it's `Some`, the `JClass` inside is never null.
## `JNIEnv::{get,release}_string_utf_chars` removed
The methods `JNIEnv::get_string_utf_chars` and `JNIEnv::release_string_utf_chars` have been removed. These methods have been replaced with `JavaStr::into_raw` and `JavaStr::from_raw`. To get a `JavaStr`, use `JNIEnv::get_string` or `JNIEnv::get_string_unchecked`. See [issue #372](https://github.com/jni-rs/jni-rs/pull/372) for discussion.
## `JPrimitiveArray<T>` and `JObjectArray` provide safe reference wrappers (like `JObject`) for `jarray`
This affects all `JNIEnv` array APIs including:
- `new_<type>_array`
- `get_array_length`
- `get_object_array_element`
- `set_object_array_element`
- `get_<type>_array_region`
- `set_<type>_array_region`
- `get_array_elements`
- `get_<type>_array_elements` (all removed)
- `get_primitive_array_elements` (also renamed to `get_array_elements_critical`)
With the exception of `get_array_length` these APIs now take or return a
reference to a `JPrimitiveArray` or a `JObjectArray` instead of a `sys` type
like `jarray` or `jbyteArray`. This improves safety since the sys types can be
copied and easily read as invalid pointers after a reference has been deleted.
`get_array_length` will accept a reference to a `JPrimitiveArray` or a `JObjectArray`
since these both implement the `AsJArrayRaw` trait. You can also query the `.len()`
of an array via the `AutoElements`/`AutoElementsCritical` APIs if you use those.
There are the following type-specific aliases for `JPrimitiveArray<T>`:
- `JBooleanArray`
- `JByteArray`
- `JCharArray`
- `JShortArray`
- `JIntArray`
- `JLongArray`
- `JFloatArray`
- `JDoubleArray`
## `AutoArray` and `AutoPrimitiveArray` renamed to `AutoElements` and `AutoElementsCritical` respectively
This rename was done to:
1. Clarify the connection between these APIs (they both provide temporary access to the elements of an array)
2. Clarify their differences (`AutoElementsCritical` is an alternative that defines a restricted "critical" section that helps JNI avoid the need to copy the data for the array)
3. Differentiate these from the new `JPrimitiveArray`/`JObjectArray` types and aliases like `JByteArray` that represent the array itself (not the elements)
## `AutoElements<T>` and `AutoElementsCritical<T>` now implement `Deref<Target=[T]>` and `DerefMut`
Previously accessing array elements required the use of `unsafe` code to access array elements via `AutoArray::as_ptr()` after acquiring an `AutoArray` guard.
It's now possible to read and write elements via the `Deref` and `DerefMut` traits that will deref into a `[T]` slice like:
```rust
let byte_array = env.new_byte_array(100)?;
{
let mut elements = unsafe { env.get_array_elements(&byte_array, ReleaseMode::CopyBack) }?;
elements[0] = 0xff;
assert_eq!(elements[0], 0xff);
// elements released (copied back to Java) here on Drop
}
```
If you are accessing a `JPrimitiveArray<jbyte>`/`JByteArray` you may find it helpful
to utilize the [bytemuck](https://crates.io/crates/bytemuck) crate in case you
need to access the elements as `u8` unsigned bytes instead of `i8`.
Although this hasn't changed, it seems worth highlighting that if you are
accessing a `JPrimitiveArray<jboolean>`/`JBooleanArray` you are responsible for
only storing values of `0` or `1`, since other values could lead to undefined
behaviour for the JVM.
## `AutoArray/AutoPrimitiveArray::size()` replace by `AutoElements/AutoElementsCritical::len()`
Previously `AutoArray::size()` was a wrapper for calling `JNIEnv::get_array_length()` which could
fail, where as `AutoElements` and `AutoElementsCritical` now need to know the array length to
be constructed, and this constant is accessible via the `.len()` method.
This change was required to support being able to `Deref` to a `[T]` slice.
## `get_array_elements[_critical]` are `unsafe`
Previously `get_array_elements` and `get_primitive_array_critical` could be called by safe code
but there were multiple ways in which these APIs could quite easily lead to undefined behaviour.
Since it is only possible to acquire an `AutoElements` and `AutoElementsCritical` instance via
`get_array_elements` and `get_primitive_array_critical`, that's where we now document
the safety rules that need to be followed to avoid any undefined behaviour.

69
vendor/jni/example/HelloWorld.h vendored Normal file
View File

@@ -0,0 +1,69 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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

49
vendor/jni/example/HelloWorld.java vendored Normal file
View File

@@ -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 + "%");
}
}

10
vendor/jni/example/Makefile vendored Normal file
View File

@@ -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

231
vendor/jni/src/lib.rs vendored Normal file
View File

@@ -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 <jni.h>
//! /* 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.21.1"`
//! * Add a new `[lib]` section and under it, `crate_type = ["cdylib"]`.
//!
//! Now, if you run `cargo build` from inside the crate directory, you should
//! 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,no_run
//! // 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<'local>(mut env: JNIEnv<'local>,
//! // This is the class that owns our static method. It's not going to be used,
//! // but still must be present to match the expected signature of a static
//! // native method.
//! class: JClass<'local>,
//! input: JString<'local>)
//! -> jstring {
//! // First, we have to get the string out of Java. Check out the `strings`
//! // module for more info on how this works.
//! let input: String =
//! env.get_string(&input).expect("Couldn't get java string!").into();
//!
//! // 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_raw()
//! }
//! ```
//!
//! 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::*;

1
vendor/jni/src/sys.rs vendored Normal file
View File

@@ -0,0 +1 @@
pub use jni_sys::*;

View File

@@ -0,0 +1,43 @@
use crate::{
descriptors::Desc,
errors::*,
objects::{AutoLocal, GlobalRef, JClass, JObject},
strings::JNIString,
JNIEnv,
};
unsafe impl<'local, T> Desc<'local, JClass<'local>> for T
where
T: Into<JNIString>,
{
type Output = AutoLocal<'local, JClass<'local>>;
fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> {
Ok(AutoLocal::new(env.find_class(self)?, env))
}
}
// Note: We don't implement `Desc<JClass>` for `&JObject` as a transmute like for `GlobalRef`
//
// Considering that the APIs that return a class return a `JClass` it shouldn't
// usually be necessary unless the `JClass` got type erased (like with GlobalRef)
//
// Implementing `Desc<JClass>` for `&JObject` as a simple cast would also make
// it a lot easier to mistakenly pass an object instance in places where a class
// is required.
/// This conversion assumes that the `GlobalRef` is a pointer to a class object.
// TODO: Generify `GlobalRef` and get rid of this `impl`. The transmute is
// sound-ish at the moment (`JClass` is currently `repr(transparent)`
// around `JObject`), but that may change in the future. Moreover, this
// doesn't check if the global reference actually refers to a
// `java.lang.Class` object.
unsafe impl<'local, 'obj_ref> Desc<'local, JClass<'static>> for &'obj_ref GlobalRef {
type Output = &'obj_ref JClass<'static>;
fn lookup(self, _: &mut JNIEnv<'local>) -> Result<Self::Output> {
let obj: &JObject<'static> = self.as_ref();
Ok(unsafe { std::mem::transmute(obj) })
}
}

View File

@@ -0,0 +1,134 @@
use crate::{
errors::*,
objects::{AutoLocal, JObject},
JNIEnv,
};
#[cfg(doc)]
use crate::objects::{JClass, JMethodID};
/// Trait for things that can be looked up through the JNI via a descriptor.
/// This will be something like the fully-qualified class name
/// `java/lang/String` or a tuple containing a class descriptor, method name,
/// and method signature. For convenience, this is also implemented for the
/// concrete types themselves in addition to their descriptors.
///
/// # Safety
///
/// Implementations of this trait must return the correct value from the
/// `lookup` method. It must not, for example, return a random [`JMethodID`] or
/// the [`JClass`] of a class other than the one requested. Returning such an
/// incorrect value results in undefined behavior. This requirement also
/// applies to the returned value's implementation of `AsRef<T>`.
pub unsafe trait Desc<'local, T> {
/// The type that this `Desc` returns.
type Output: AsRef<T>;
/// Look up the concrete type from the JVM.
///
/// Note that this method does not return exactly `T`. Instead, it returns
/// some type that implements `AsRef<T>`. For this reason, it is often
/// necessary to use turbofish syntax when calling this method:
///
/// ```rust,no_run
/// # use jni::{descriptors::Desc, errors::Result, JNIEnv, objects::JClass};
/// #
/// # fn example(env: &mut JNIEnv) -> Result<()> {
/// // The value returned by `lookup` is not exactly `JClass`.
/// let class/*: impl AsRef<JClass> */ =
/// Desc::<JClass>::lookup("java/lang/Object", env)?;
///
/// // But `&JClass` can be borrowed from it.
/// let class: &JClass = class.as_ref();
/// # Ok(())
/// # }
/// ```
///
/// **Warning:** Many built-in implementations of this trait return
/// [`AutoLocal`] from this method. If you then call [`JObject::as_raw`] on
/// the returned object reference, this may result in the reference being
/// [deleted][JNIEnv::delete_local_ref] before it is used, causing
/// undefined behavior.
///
/// For example, don't do this:
///
/// ```rust,no_run
/// # use jni::{descriptors::Desc, errors::Result, JNIEnv, objects::JClass};
/// #
/// # fn some_function<T>(ptr: *mut T) {}
/// #
/// # fn example(env: &mut JNIEnv) -> Result<()> {
/// // Undefined behavior: the `JClass` is dropped before the raw pointer
/// // is passed to `some_function`!
/// some_function(Desc::<JClass>::lookup("java/lang/Object", env)?.as_raw());
/// # Ok(())
/// # }
/// ```
///
/// Instead, do this:
///
/// ```rust,no_run
/// # use jni::{descriptors::Desc, errors::Result, JNIEnv, objects::JClass};
/// #
/// # fn some_function<T>(ptr: *mut T) {}
/// #
/// # fn example(env: &mut JNIEnv) -> Result<()> {
/// let class = Desc::<JClass>::lookup("java/lang/Object", env)?;
///
/// some_function(class.as_raw());
///
/// drop(class);
/// # Ok(())
/// # }
/// ```
///
/// This will still work without the call to `drop` at the end, but calling
/// `drop` ensures that the reference is not accidentally dropped earlier
/// than it should be.
fn lookup(self, _: &mut JNIEnv<'local>) -> Result<Self::Output>;
}
unsafe impl<'local, T> Desc<'local, T> for T
where
T: AsRef<T>,
{
type Output = Self;
fn lookup(self, _: &mut JNIEnv<'local>) -> Result<T> {
Ok(self)
}
}
unsafe impl<'local, 't_ref, T> Desc<'local, T> for &'t_ref T
where
T: AsRef<T>,
{
type Output = Self;
fn lookup(self, _: &mut JNIEnv<'local>) -> Result<Self::Output> {
Ok(self)
}
}
unsafe impl<'local, 'other_local, T> Desc<'local, T> for AutoLocal<'other_local, T>
where
T: AsRef<T> + Into<JObject<'other_local>>,
{
type Output = Self;
fn lookup(self, _: &mut JNIEnv<'local>) -> Result<Self::Output> {
Ok(self)
}
}
unsafe impl<'local, 'other_local, 'obj_ref, T> Desc<'local, T>
for &'obj_ref AutoLocal<'other_local, T>
where
T: AsRef<T> + Into<JObject<'other_local>>,
{
type Output = Self;
fn lookup(self, _: &mut JNIEnv<'local>) -> Result<Self::Output> {
Ok(self)
}
}

View File

@@ -0,0 +1,57 @@
use crate::{
descriptors::Desc,
errors::*,
objects::{AutoLocal, JClass, JObject, JThrowable, JValue},
strings::JNIString,
JNIEnv,
};
const DEFAULT_EXCEPTION_CLASS: &str = "java/lang/RuntimeException";
unsafe impl<'local, 'other_local, C, M> Desc<'local, JThrowable<'local>> for (C, M)
where
C: Desc<'local, JClass<'other_local>>,
M: Into<JNIString>,
{
type Output = AutoLocal<'local, JThrowable<'local>>;
fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> {
let jmsg: AutoLocal<JObject> = env.auto_local(env.new_string(self.1)?.into());
let obj: JThrowable = env
.new_object(self.0, "(Ljava/lang/String;)V", &[JValue::from(&jmsg)])?
.into();
Ok(env.auto_local(obj))
}
}
unsafe impl<'local> Desc<'local, JThrowable<'local>> for Exception {
type Output = AutoLocal<'local, JThrowable<'local>>;
fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> {
Desc::<JThrowable>::lookup((self.class, self.msg), env)
}
}
unsafe impl<'local, 'str_ref> Desc<'local, JThrowable<'local>> for &'str_ref str {
type Output = AutoLocal<'local, JThrowable<'local>>;
fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> {
Desc::<JThrowable>::lookup((DEFAULT_EXCEPTION_CLASS, self), env)
}
}
unsafe impl<'local> Desc<'local, JThrowable<'local>> for String {
type Output = AutoLocal<'local, JThrowable<'local>>;
fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> {
Desc::<JThrowable>::lookup((DEFAULT_EXCEPTION_CLASS, self), env)
}
}
unsafe impl<'local> Desc<'local, JThrowable<'local>> for JNIString {
type Output = AutoLocal<'local, JThrowable<'local>>;
fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> {
Desc::<JThrowable>::lookup((DEFAULT_EXCEPTION_CLASS, self), env)
}
}

View File

@@ -0,0 +1,33 @@
use crate::{
descriptors::Desc,
errors::*,
objects::{JClass, JFieldID, JStaticFieldID},
strings::JNIString,
JNIEnv,
};
unsafe impl<'local, 'other_local, T, U, V> Desc<'local, JFieldID> for (T, U, V)
where
T: Desc<'local, JClass<'other_local>>,
U: Into<JNIString>,
V: Into<JNIString>,
{
type Output = JFieldID;
fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> {
env.get_field_id(self.0, self.1, self.2)
}
}
unsafe impl<'local, 'other_local, T, U, V> Desc<'local, JStaticFieldID> for (T, U, V)
where
T: Desc<'local, JClass<'other_local>>,
U: Into<JNIString>,
V: Into<JNIString>,
{
type Output = JStaticFieldID;
fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> {
env.get_static_field_id(self.0, self.1, self.2)
}
}

View File

@@ -0,0 +1,45 @@
use crate::{
descriptors::Desc,
errors::*,
objects::{JClass, JMethodID, JStaticMethodID},
strings::JNIString,
JNIEnv,
};
unsafe impl<'local, 'other_local, T, U, V> Desc<'local, JMethodID> for (T, U, V)
where
T: Desc<'local, JClass<'other_local>>,
U: Into<JNIString>,
V: Into<JNIString>,
{
type Output = JMethodID;
fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> {
env.get_method_id(self.0, self.1, self.2)
}
}
unsafe impl<'local, 'other_local, T, Signature> Desc<'local, JMethodID> for (T, Signature)
where
T: Desc<'local, JClass<'other_local>>,
Signature: Into<JNIString>,
{
type Output = JMethodID;
fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> {
Desc::<JMethodID>::lookup((self.0, "<init>", self.1), env)
}
}
unsafe impl<'local, 'other_local, T, U, V> Desc<'local, JStaticMethodID> for (T, U, V)
where
T: Desc<'local, JClass<'other_local>>,
U: Into<JNIString>,
V: Into<JNIString>,
{
type Output = JStaticMethodID;
fn lookup(self, env: &mut JNIEnv<'local>) -> Result<Self::Output> {
env.get_static_method_id(self.0, self.1, self.2)
}
}

View File

@@ -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::*;

129
vendor/jni/src/wrapper/errors.rs vendored Normal file
View File

@@ -0,0 +1,129 @@
#![allow(missing_docs)]
use thiserror::Error;
use crate::sys;
use crate::wrapper::signature::TypeSignature;
pub type Result<T> = std::result::Result<T, Error>;
#[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 or type 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<T> From<::std::sync::TryLockError<T>> for Error {
fn from(_: ::std::sync::TryLockError<T>) -> 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;
}
/// An error that occurred while starting the JVM using the JNI Invocation API.
///
/// This only exists if the "invocation" feature is enabled.
#[cfg(feature = "invocation")]
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum StartJvmError {
/// An attempt was made to find a JVM using [java-locator], but it failed.
///
/// If this happens, give an explicit location to [`JavaVM::with_libjvm`] or set the
/// `JAVA_HOME` environment variable.
///
/// [java-locator]: https://docs.rs/java-locator/
/// [`JavaVM::with_libjvm`]: crate::JavaVM::with_libjvm
#[error("Couldn't automatically discover the Java VM's location (try setting the JAVA_HOME environment variable): {0}")]
NotFound(
#[from]
#[source]
java_locator::errors::JavaLocatorError,
),
/// An error occurred in trying to load the JVM shared library.
///
/// On Windows, if this happens it may be necessary to add your `$JAVA_HOME/bin` directory
/// to the DLL search path by adding it to the `PATH` environment variable.
#[error("Couldn't load the Java VM shared library ({0}): {1}")]
LoadError(String, #[source] libloading::Error),
/// The JNI function `JNI_CreateJavaVM` returned an error.
#[error("{0}")]
Create(
#[from]
#[source]
Error,
),
}
#[cfg(feature = "invocation")]
pub type StartJvmResult<T> = std::result::Result<T, StartJvmError>;

93
vendor/jni/src/wrapper/executor.rs vendored Normal file
View File

@@ -0,0 +1,93 @@
use std::sync::Arc;
use crate::{errors::*, JNIEnv, JavaVM};
/// The capacity of local frames, allocated for attached threads by default. Same as the default
/// value Hotspot uses when calling native Java methods.
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;
/// # //
/// # // Ignore this test without invocation feature, so that simple `cargo test` works
/// # #[cfg(feature = "invocation")]
/// # fn main() -> errors::StartJvmResult<()> {
/// # //
/// # 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);
/// env.call_static_method("java/lang/Math", "abs", "(I)I", &[x])?.i()
/// })?;
///
/// assert_eq!(val, 10);
///
/// # Ok(()) }
/// #
/// # // This is a stub that gets run instead if the invocation feature is not built
/// # #[cfg(not(feature = "invocation"))]
/// # fn main() {}
/// ```
#[derive(Clone)]
pub struct Executor {
vm: Arc<JavaVM>,
}
impl Executor {
/// Creates new Executor with specified JVM.
pub fn new(vm: Arc<JavaVM>) -> 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<F, T, E>(&self, capacity: i32, f: F) -> std::result::Result<T, E>
where
F: FnOnce(&mut JNIEnv) -> std::result::Result<T, E>,
E: From<Error>,
{
assert!(capacity > 0, "capacity should be a positive integer");
let mut jni_env = self.vm.attach_current_thread_as_daemon()?;
jni_env.with_local_frame(capacity, |jni_env| f(jni_env))
}
/// Executes a provided closure, making sure that the current thread
/// 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<F, T, E>(&self, f: F) -> std::result::Result<T, E>
where
F: FnOnce(&mut JNIEnv) -> std::result::Result<T, E>,
E: From<Error>,
{
self.with_attached_capacity(DEFAULT_LOCAL_FRAME_CAPACITY, f)
}
}

View File

@@ -0,0 +1,360 @@
use std::{borrow::Cow, ffi::CStr, io, os::raw::c_void, ptr};
use thiserror::Error;
use crate::{
sys::{JavaVMInitArgs, JavaVMOption},
JNIVersion,
};
use cfg_if::cfg_if;
mod char_encoding_generic;
#[cfg(windows)]
mod char_encoding_windows;
/// Errors that can occur when invoking a [`JavaVM`](super::vm::JavaVM) with the
/// [Invocation API](https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html).
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum JvmError {
/// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the supplied
/// string contains a U+0000 code point (except at the end).
///
/// This error is not raised if the string has a single U+0000 code point at the end.
///
/// [`InitArgsBuilder::option_encoded`] never raises this error.
#[error("internal null in option: {0}")]
NullOptString(String),
/// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the option
/// string is too long.
///
/// Currently, this error only occurs on Windows, where string length is limited to 1MB to
/// avoid overflow in [`WideCharToMultiByte`] (see [discussion]). String length is not
/// currently limited (other than by available memory) on other platforms.
///
/// [`InitArgsBuilder::option_encoded`] never raises this error, regardless of platform.
///
/// [discussion]: https://github.com/jni-rs/jni-rs/pull/414
/// [`WideCharToMultiByte`]: https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte
#[error("option is too long: {opt_string}")]
#[non_exhaustive]
OptStringTooLong {
/// The option string.
opt_string: String,
},
/// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the option
/// string is not representable in the platform default character encoding.
///
/// [`InitArgsBuilder::option_encoded`] never raises this error.
#[error(
"option {opt_string:?} is not representable in the platform default character encoding"
)]
#[non_exhaustive]
OptStringNotRepresentable {
/// The option string.
opt_string: String,
},
/// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the platform
/// reported an error converting it to its default character encoding.
///
/// [`InitArgsBuilder::option_encoded`] never raises this error.
#[error("couldn't convert option {opt_string:?} to the platform default character encoding: {error}")]
#[non_exhaustive]
OptStringTranscodeFailure {
/// The option string.
opt_string: String,
/// The error reported by the platform's character encoding conversion routine.
#[source]
error: io::Error,
},
}
impl JvmError {
/// Returns the JVM option that caused the error, if it was caused by one.
pub fn opt_string(&self) -> Option<&str> {
match self {
Self::NullOptString(opt_string) => Some(opt_string),
Self::OptStringTooLong { opt_string, .. } => Some(opt_string),
Self::OptStringNotRepresentable { opt_string, .. } => Some(opt_string),
Self::OptStringTranscodeFailure { opt_string, .. } => Some(opt_string),
}
.map(String::as_str)
}
#[cfg(all(test, windows))]
fn opt_string_mut(&mut self) -> Option<&mut String> {
match self {
Self::NullOptString(opt_string) => Some(opt_string),
Self::OptStringTooLong { opt_string, .. } => Some(opt_string),
Self::OptStringNotRepresentable { opt_string, .. } => Some(opt_string),
Self::OptStringTranscodeFailure { opt_string, .. } => Some(opt_string),
}
}
}
const SPECIAL_OPTIONS: &[&str] = &["vfprintf", "abort", "exit"];
const SPECIAL_OPTIONS_C: &[&CStr] = unsafe {
&[
CStr::from_bytes_with_nul_unchecked(b"vfprintf\0"),
CStr::from_bytes_with_nul_unchecked(b"abort\0"),
CStr::from_bytes_with_nul_unchecked(b"exit\0"),
]
};
/// Builder for JavaVM InitArgs.
///
/// *This API requires "invocation" feature to be enabled,
/// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).*
#[derive(Debug)]
pub struct InitArgsBuilder<'a> {
opts: Result<Vec<Cow<'a, CStr>>, JvmError>,
ignore_unrecognized: bool,
version: JNIVersion,
}
impl<'a> Default for InitArgsBuilder<'a> {
fn default() -> Self {
InitArgsBuilder {
opts: Ok(vec![]),
ignore_unrecognized: false,
version: JNIVersion::V8,
}
}
}
impl<'a> InitArgsBuilder<'a> {
/// Create a new default InitArgsBuilder
pub fn new() -> Self {
Default::default()
}
/// Adds a JVM option, such as `-Djavax.net.debug=all`.
///
/// See [the JNI specification][jni-options] for details on which options are accepted.
///
/// The `vfprintf`, `abort`, and `exit` options are unsupported at this time. Setting one of
/// these options has no effect.
///
/// The option must not contain any U+0000 code points except one at the end. A U+0000 code
/// point at the end is not required, but on platforms where UTF-8 is the default character
/// encoding, including one U+0000 code point at the end will make this method run slightly
/// faster.
///
/// # Errors
///
/// This method can fail if:
///
/// * `opt_string` contains a U+0000 code point before the end.
/// * `opt_string` cannot be represented in the platform default character encoding.
/// * the platform's character encoding conversion API reports some other error.
/// * `opt_string` is too long. (In the current implementation, the maximum allowed length is
/// 1048576 bytes on Windows. There is currently no limit on other platforms.)
///
/// Errors raised by this method are deferred. If an error occurs, it is returned from
/// [`InitArgsBuilder::build`] instead.
///
/// [jni-options]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html#jni_createjavavm
pub fn option(mut self, opt_string: impl AsRef<str> + Into<Cow<'a, str>>) -> Self {
if let Err(error) = self.try_option(opt_string) {
self.opts = Err(error);
}
self
}
/// Adds a JVM option, such as `-Djavax.net.debug=all`. Returns an error immediately upon
/// failure.
///
/// This is an alternative to [`InitArgsBuilder::option`] that does not defer errors. See
/// below for details.
///
/// See [the JNI specification][jni-options] for details on which options are accepted.
///
/// The `vfprintf`, `abort`, and `exit` options are unsupported at this time. Setting one of
/// these options has no effect.
///
/// The option must not contain any U+0000 code points except one at the end. A U+0000 code
/// point at the end is not required, but on platforms where UTF-8 is the default character
/// encoding, including one U+0000 code point at the end will make this method run slightly
/// faster.
///
/// # Errors
///
/// This method can fail if:
///
/// * `opt_string` contains a U+0000 code point before the end.
/// * `opt_string` cannot be represented in the platform default character encoding.
/// * the platform's character encoding conversion API reports some other error.
/// * `opt_string` is too long. (In the current implementation, the maximum allowed length is
/// 1048576 bytes on Windows. There is currently no limit on other platforms.)
///
/// Unlike the `option` method, this one does not defer errors. If the `opt_string` cannot be
/// used, then this method returns `Err` and `self` is not changed. If there is already a
/// deferred error, however, then this method does nothing.
///
/// [jni-options]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html#jni_createjavavm
pub fn try_option(&mut self, opt_string: impl Into<Cow<'a, str>>) -> Result<(), JvmError> {
let opt_string = opt_string.into();
// If there is already a deferred error, do nothing.
let opts = match &mut self.opts {
Ok(ok) => ok,
Err(_) => return Ok(()),
};
// If the option is the empty string, then skip everything else and pass a constant empty
// C string. This isn't just an optimization; Win32 `WideCharToMultiByte` will **fail** if
// passed an empty string, so we have to do this check first.
if matches!(opt_string.as_ref(), "" | "\0") {
opts.push(Cow::Borrowed(unsafe {
// Safety: This string not only is null-terminated without any interior null bytes,
// it's nothing but a null terminator.
CStr::from_bytes_with_nul_unchecked(b"\0")
}));
return Ok(());
}
// If this is one of the special options, do nothing.
else if SPECIAL_OPTIONS.contains(&&*opt_string) {
return Ok(());
}
let encoded: Cow<'a, CStr> = {
cfg_if! {
if #[cfg(windows)] {
char_encoding_windows::str_to_cstr_win32_default_codepage(opt_string)?
}
else {
// Assume UTF-8 on all other platforms.
char_encoding_generic::utf8_to_cstr(opt_string)?
}
}
};
opts.push(encoded);
Ok(())
}
/// Adds a JVM option, such as `-Djavax.net.debug=all`. The option must be a `CStr` encoded in
/// the platform default character encoding.
///
/// This is an alternative to [`InitArgsBuilder::option`] that does not do any encoding. This
/// method is not `unsafe` as it cannot cause undefined behavior, but the option will be
/// garbled (that is, become [mojibake](https://en.wikipedia.org/wiki/Mojibake)) if not
/// encoded correctly.
///
/// See [the JNI specification][jni-options] for details on which options are accepted.
///
/// The `vfprintf`, `abort`, and `exit` options are unsupported at this time. Setting one of
/// these options has no effect.
///
/// This method does not fail, and will neither return nor defer an error.
///
/// [jni-options]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html#jni_createjavavm
pub fn option_encoded(mut self, opt_string: impl Into<Cow<'a, CStr>>) -> Self {
let opt_string = opt_string.into();
// If there is already a deferred error, do nothing.
let opts = match &mut self.opts {
Ok(ok) => ok,
Err(_) => return self,
};
// If this is one of the special options, do nothing.
if SPECIAL_OPTIONS_C.contains(&&*opt_string) {
return self;
}
// Add the option.
opts.push(opt_string);
self
}
/// Set JNI version for the init args
///
/// 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`
///
/// # Errors
///
/// If a call to [`InitArgsBuilder::option`] caused a deferred error, it is returned from this
/// method.
pub fn build(self) -> Result<InitArgs<'a>, JvmError> {
let opt_strings = self.opts?;
let opts: Vec<JavaVMOption> = opt_strings
.iter()
.map(|opt_string| JavaVMOption {
optionString: opt_string.as_ptr() as _,
extraInfo: ptr::null_mut(),
})
.collect();
Ok(InitArgs {
inner: JavaVMInitArgs {
version: self.version.into(),
ignoreUnrecognized: self.ignore_unrecognized as _,
options: opts.as_ptr() as _,
nOptions: opts.len() as _,
},
_opts: opts,
_opt_strings: opt_strings,
})
}
/// Returns collected options.
///
/// If a call to [`InitArgsBuilder::option`] caused a deferred error, then this method returns
/// a reference to that error.
pub fn options(&self) -> Result<&[Cow<'a, CStr>], &JvmError> {
self.opts.as_ref().map(Vec::as_slice)
}
}
/// 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<'a> {
inner: JavaVMInitArgs,
// `JavaVMOption` structures are stored here. The JVM accesses this `Vec`'s contents through a
// raw pointer.
_opts: Vec<JavaVMOption>,
// Option strings are stored here. This ensures that any that are owned aren't dropped before
// the JVM is finished with them.
_opt_strings: Vec<Cow<'a, CStr>>,
}
impl<'a> InitArgs<'a> {
pub(crate) fn inner_ptr(&self) -> *mut c_void {
&self.inner as *const _ as _
}
}

View File

@@ -0,0 +1,117 @@
use super::JvmError;
use std::{
borrow::Cow,
ffi::{CStr, CString},
};
/// Converts `s: Cow<[u8]>` into a `Cow<CStr>`, adding a null byte if necessary.
///
/// `original`, if present, is the original string, which will be moved into a [`JvmError]`
/// in the event of failure. If `original` is absent, then `s` *is* the original
/// string (i.e. is encoded in UTF-8), and is to be moved into the `JvmError` upon failure.
///
/// # Errors
///
/// This will fail if `s` contains any null bytes other than a single null byte at the end.
///
/// # Safety
///
/// If `original` is `None`, then `s` must contain valid UTF-8.
pub(super) unsafe fn bytes_to_cstr<'a>(
mut s: Cow<'a, [u8]>,
original: Option<Cow<'_, str>>,
) -> Result<Cow<'a, CStr>, JvmError> {
// Check if it has a null byte at the end already. If not, add one.
let mut null_byte_added = false;
if s.last() != Some(&0) {
s.to_mut().push(0);
null_byte_added = true;
}
// This function is called if conversion fails because the string has a null byte
// in the middle.
let convert_error = move |s: Cow<'a, [u8]>| -> JvmError {
// We need to get back to a `String` in order to insert it into the error. How
// to do that depends on whether we were given a separate original or not.
let s: String = {
if let Some(original) = original {
// Yes, there is a separate original. Use that.
original.into_owned()
} else {
// No, `s` *is* the original. Strip off the null byte if we
// added one, then assume the rest is valid UTF-8.
let mut s: Vec<u8> = s.into_owned();
if null_byte_added {
let _removed_null_byte: Option<u8> = s.pop();
debug_assert_eq!(_removed_null_byte, Some(0));
}
// Safety: The caller of this function asserts that this is valid UTF-8. We
// have not changed it other than adding a null byte at the end.
unsafe { String::from_utf8_unchecked(s) }
}
};
JvmError::NullOptString(s)
};
// Now, try to convert. Exactly how to do this, and exactly how to handle errors, depends
// on whether it's borrowed or owned.
let s: Cow<'a, CStr> = match s {
Cow::Owned(s) => Cow::Owned({
CString::from_vec_with_nul(s)
.map_err(|error| convert_error(Cow::Owned(error.into_bytes())))?
}),
Cow::Borrowed(s) => Cow::Borrowed({
CStr::from_bytes_with_nul(s).map_err(|_error| convert_error(Cow::Borrowed(s)))?
}),
};
// Done.
Ok(s)
}
/// Converts `s: Cow<str>` into a `Cow<CStr>`, still in UTF-8 encoding, adding a null byte if
/// necessary.
pub(super) fn utf8_to_cstr<'a>(s: Cow<'a, str>) -> Result<Cow<'a, CStr>, JvmError> {
let s: Cow<'a, [u8]> = match s {
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
};
// Safety: `s` was just converted from type `str`, so it's already known to contain valid
// UTF-8.
unsafe { bytes_to_cstr(s, None) }
}
#[test]
fn test() {
use assert_matches::assert_matches;
{
let result = utf8_to_cstr("Hello, world 😎".into()).unwrap();
assert_eq!(
result.to_bytes_with_nul(),
b"Hello, world \xf0\x9f\x98\x8e\0"
);
assert_matches!(result, Cow::Owned(_));
}
{
let result = utf8_to_cstr("Hello, world 😎\0".into()).unwrap();
assert_eq!(
result.to_bytes_with_nul(),
b"Hello, world \xf0\x9f\x98\x8e\0"
);
assert_matches!(result, Cow::Borrowed(_));
}
{
let result = utf8_to_cstr("Hello,\0world".into()).unwrap_err();
let error_string = assert_matches!(result, JvmError::NullOptString(string) => string);
assert_eq!(error_string, "Hello,\0world");
}
}

View File

@@ -0,0 +1,443 @@
use super::{char_encoding_generic::*, JvmError};
use std::{
borrow::Cow,
convert::TryInto,
ffi::{c_int, c_uint, CStr},
io,
mem::MaybeUninit,
ptr,
};
use windows_sys::Win32::Globalization as winnls;
// The integer type used by `WideCharToMultiByte` for string lengths.
type WSize = c_int;
// The type of Windows codepage numbers.
type WCodepage = c_uint;
// The maximum length, in UTF-8 bytes, of strings that will be accepted for transcoding.
//
// The purpose of this limit is to prevent overflow. `WideCharToMultiByte` behaves rather badly
// (see https://github.com/jni-rs/jni-rs/pull/414 for discussion) if the string is long enough to
// overflow its counters.
//
// Although it is possible to transcode a string of any length by splitting it into smaller
// substrings, the code complexity needed to do so isn't worthwhile just for transcoding JVM
// options. Also, `test_overflow` would take a very long time to run, which was deemed unacceptable
// (see https://github.com/jni-rs/jni-rs/pull/414#issuecomment-1419130483). We set this arbitrary
// limit instead.
const MAX_INPUT_LEN: usize = 1048576;
/// Converts `s` into a `Cow<CStr>` encoded in the specified Windows code page.
pub(super) fn str_to_cstr_win32<'a>(
s: Cow<'a, str>,
needed_codepage: WCodepage,
) -> Result<Cow<'static, CStr>, JvmError> {
// First, check if the input string (UTF-8) is too long to transcode. Bail early if so.
if s.len() > MAX_INPUT_LEN {
return Err(JvmError::OptStringTooLong {
opt_string: s.into_owned(),
});
}
// This function will generate an error if `WideCharToMultiByte` fails.
fn convert_error(s: Cow<str>) -> JvmError {
JvmError::OptStringTranscodeFailure {
opt_string: s.into_owned(),
error: io::Error::last_os_error(),
}
}
// Convert the string to UTF-16 first.
let s_utf16: Vec<u16> = s.encode_utf16().collect();
// Determine how long the string is, in UTF-16 units, in the integer type that Win32 expects.
// Overflow should be impossible; panic if it happens.
let s_utf16_len: WSize = s_utf16
.len()
.try_into()
.expect("UTF-16 form of input string is too long");
// Decide which flags we're going to use.
let conversion_flags = match needed_codepage {
// No flags may be given for the following code pages.
// https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte
42
| 50220
| 50221
| 50222
| 50225
| 50227
| 50229
| 54936
| 57002..=57011
| 65000
| 65001 => 0,
_ => winnls::WC_COMPOSITECHECK | winnls::WC_NO_BEST_FIT_CHARS,
};
// Find out how much buffer space will be needed for the output and whether the string is
// fully representable.
let mut is_non_representable: Option<MaybeUninit<_>> = match needed_codepage {
// All characters are representable in UTF-7 and UTF-8, and moreover
// `WideCharToMultiByte` will fail if the target encoding is UTF-7 or UTF-8 and this is not
// `None`.
winnls::CP_UTF7 | winnls::CP_UTF8 => None,
_ => Some(MaybeUninit::uninit()),
};
// Safety: `s_utf16.as_ptr()` is a valid pointer to a UTF-16 string, and `s_utf16_len` is its
// length. `lpDefaultChar` is null. `lpUsedDefaultChar` is either null or valid. `cbMultiByte`
// is zero.
let required_buffer_space = unsafe {
winnls::WideCharToMultiByte(
needed_codepage,
conversion_flags,
s_utf16.as_ptr(),
s_utf16_len,
ptr::null_mut(),
0,
ptr::null(),
match &mut is_non_representable {
Some(x) => x.as_mut_ptr(),
None => ptr::null_mut(),
},
)
};
// Bail on error.
if required_buffer_space == 0 {
drop(s_utf16);
return Err(convert_error(s));
}
// Check if the string is not fully representable.
if let Some(is_non_representable) = is_non_representable {
// Safety: `is_non_representable` has been initialized by `WideCharToMultiByte`.
let is_non_representable = unsafe { is_non_representable.assume_init() };
if is_non_representable != 0 {
drop(s_utf16);
return Err(JvmError::OptStringNotRepresentable {
opt_string: s.into_owned(),
});
}
}
// Convert the required buffer space to `usize`, and increment it by one for the null
// terminator.
//
// This shouldn't overflow (see the comment on `MAX_INPUT_LEN` above), so we won't check for
// overflow here.
let required_buffer_space_usize: usize = required_buffer_space as _;
let required_buffer_space_usize_with_nul: usize = required_buffer_space_usize + 1;
// Allocate enough buffer space, including one byte for the null terminator.
let mut output = Vec::<u8>::with_capacity(required_buffer_space_usize_with_nul);
// Perform the actual conversion.
//
// Safety: `chunk.as_ptr()` is a valid pointer, and `chunk_len_i32` is its length.
// `chunk_output_ptr` is a valid pointer, and `required_buffer_space` is its length.
// All other raw pointers are null.
let used_buffer_space = unsafe {
winnls::WideCharToMultiByte(
needed_codepage,
conversion_flags,
s_utf16.as_ptr(),
s_utf16_len,
output.as_mut_ptr(),
required_buffer_space,
ptr::null(),
ptr::null_mut(),
)
};
drop(s_utf16);
// Bail on error.
if used_buffer_space == 0 {
drop(output);
return Err(convert_error(s));
}
let used_buffer_space_usize: usize = used_buffer_space as usize;
// Set the new length of the output buffer. Don't use `required_buffer_space`, just in case
// `WideCharToMultiByte` changes its mind about how much buffer space it's actually going to
// use.
//
// Safety: `used_buffer_space_usize` is the number of bytes that `WideCharToMultiByte` has
// just initialized.
unsafe {
output.set_len(used_buffer_space_usize);
}
// That's it, it's converted. Now turn it into a `CString`. This will add a null terminator if
// there isn't one already and check for null bytes in the middle.
unsafe { bytes_to_cstr(Cow::Owned(output), Some(s.into())) }
}
/// Converts `s` into the Windows default character encoding.
pub(super) fn str_to_cstr_win32_default_codepage<'a>(
s: Cow<'a, str>,
) -> Result<Cow<'a, CStr>, JvmError> {
// Get the code page. There is a remote possibility that it is UTF-8. If so, pass the
// string through unchanged (other than adding a null terminator). If not, we need to have
// Windows convert the string to the expected code page first.
// Safety: This function isn't actually unsafe.
let needed_codepage = unsafe { winnls::GetACP() };
if needed_codepage == winnls::CP_UTF8 {
// The code page is UTF-8! Lucky us.
return utf8_to_cstr(s);
}
// The code page is not UTF-8, so do the transcoding.
str_to_cstr_win32(s, needed_codepage)
}
/// Transcodes text in an arbitrary Windows codepage into a Rust `String`. Used to test
/// round-tripping.
#[cfg(test)]
fn codepage_to_string_win32(
codepage_string: impl AsRef<[u8]>,
codepage: WCodepage,
max_expected_utf16_len: WSize,
) -> io::Result<String> {
let codepage_string_slice = codepage_string.as_ref();
let codepage_string_slice_len: WSize = codepage_string_slice
.len()
.try_into()
.expect("`codepage_string`'s length is too large to transcode with Win32");
let mut buf = Vec::<u16>::with_capacity(
max_expected_utf16_len
.try_into()
.expect("expected_utf16_len is negative or exceeds address space"),
);
// Safety: All of these pointers and lengths are valid and checked for overflow.
let utf16_units_transcoded = unsafe {
winnls::MultiByteToWideChar(
codepage,
0,
codepage_string_slice.as_ptr() as *const _,
codepage_string_slice_len,
buf.as_mut_ptr(),
max_expected_utf16_len,
)
};
if utf16_units_transcoded == 0 {
return Err(io::Error::last_os_error());
}
// Safety: `MultiByteToWideChar` claims to have initialized this many UTF-16 units.
unsafe {
buf.set_len(utf16_units_transcoded as _);
}
drop(codepage_string);
let string =
String::from_utf16(buf.as_slice()).expect("`MultiByteToWideChar` generated invalid UTF-16");
Ok(string)
}
#[test]
fn test() {
use assert_matches::assert_matches;
{
let result = str_to_cstr_win32("Hello, world 😎".into(), winnls::CP_UTF8).unwrap();
assert_eq!(
result.to_bytes_with_nul(),
b"Hello, world \xf0\x9f\x98\x8e\0"
);
assert_matches!(result, Cow::Owned(_));
}
{
let result = str_to_cstr_win32("Hello, world 😎\0".into(), winnls::CP_UTF8).unwrap();
assert_eq!(
result.to_bytes_with_nul(),
b"Hello, world \xf0\x9f\x98\x8e\0"
);
}
{
let result = str_to_cstr_win32("Hello, world 😎".into(), 1252).unwrap_err();
let error_string = assert_matches!(result, JvmError::OptStringNotRepresentable { opt_string } => opt_string);
assert_eq!(error_string, "Hello, world 😎");
}
{
let result = str_to_cstr_win32("Hello, world™".into(), 1252).unwrap();
assert_eq!(result.to_bytes_with_nul(), b"Hello, world\x99\0");
assert_matches!(result, Cow::Owned(_));
}
}
#[test]
fn test_overflow() {
use assert_matches::assert_matches;
// Note: We avoid naïvely using `assert` here, because assertion failure will dump millions of
// characters to the console. Instead, here are some functions for handling errors without
// doing that.
#[track_caller]
fn check_and_clear_error_opt_string(expected_opt_string: &str, error: &mut JvmError) {
if let Some(actual_opt_string) = error.opt_string_mut() {
if actual_opt_string != expected_opt_string {
panic!("opt_string was mangled in moving it to an error");
}
*actual_opt_string = String::new();
}
}
#[track_caller]
fn expect_success(
expected_opt_string: &str,
result: Result<Cow<'static, CStr>, JvmError>,
) -> Cow<'static, CStr> {
match result {
Ok(ok) => ok,
Err(mut error) => {
check_and_clear_error_opt_string(expected_opt_string, &mut error);
panic!("unexpected transcoding failure: {}", error)
}
}
}
#[track_caller]
fn expect_successful_roundtrip(
expected_opt_string: &str,
result: Result<Cow<'static, CStr>, JvmError>,
) -> Cow<'static, CStr> {
let string = expect_success(expected_opt_string, result);
assert!(
expected_opt_string.as_bytes() == string.to_bytes(),
"opt_string was transcoded successfully but mangled"
);
string
}
#[track_caller]
fn expect_opt_string_too_long(
expected_opt_string: &str,
result: Result<Cow<'static, CStr>, JvmError>,
) {
let mut error = match result {
Err(err) => err,
Ok(ok) => {
assert!(
expected_opt_string.as_bytes() == ok.to_bytes(),
"transcoding unexpectedly succeeded and resulted in mangled output"
);
panic!("transcoding unexpectedly succeeded")
}
};
check_and_clear_error_opt_string(expected_opt_string, &mut error);
assert_matches!(error, JvmError::OptStringTooLong { .. });
}
{
// Try transcoding a plain ASCII string.
// First, allocate enough space to completely fill the maximum allowed length, plus one
// more.
//eprintln!("Allocating & filling ASCII");
let string = vec![b'H'; MAX_INPUT_LEN.checked_add(1).unwrap()];
//eprintln!("Checking UTF-8 correctness");
let mut string = String::from_utf8(string).unwrap();
// This string is currently one character too long to transcode, so there should be an
// overflow error.
//eprintln!("Transcoding ASCII string that's too long");
expect_opt_string_too_long(
&string,
str_to_cstr_win32(string.as_str().into(), winnls::CP_UTF8),
);
// But if we remove one character…
assert_eq!(string.pop(), Some('H'));
// …then it should transcode fine.
//eprintln!("Transcoding ASCII string that's not too long");
expect_successful_roundtrip(
&string,
str_to_cstr_win32(string.as_str().into(), winnls::CP_UTF8),
);
}
{
// Try transcoding a non-ASCII string.
// U+07FF is the highest code point that can be represnted in UTF-8 with only two bytes, so
// we'll use that. The UTF-8 encoding is `df bf`. We fill it this way because it's much
// faster than the naïve character-by-character approach (at least unless some future Rust
// compiler performs this optimization on its own, but 1.66 doesn't).
//eprintln!("Allocating & filling non-ASCII for UTF-8 and UTF-7");
let string_byte_pairs = vec![u16::from_be(0xdfbf); MAX_INPUT_LEN / 2];
//eprintln!("Checking UTF-8 correctness");
let string: &str =
std::str::from_utf8(bytemuck::cast_slice(string_byte_pairs.as_slice())).unwrap();
// Again, the string should transcode without overflow.
//eprintln!("Transcoding non-ASCII to UTF-8");
expect_successful_roundtrip(string, str_to_cstr_win32(string.into(), winnls::CP_UTF8));
// This should work even with UTF-7. This is the real reason we're using U+07FF: we need
// to check that the highest code point that fits under the limit will not overflow even
// with the worst-case code page.
{
//eprintln!("Transcoding non-ASCII to UTF-7");
let result = expect_success(string, str_to_cstr_win32(string.into(), winnls::CP_UTF7));
// *And* it should roundtrip back to UTF-8.
//eprintln!("Transcoding UTF-7 back to UTF-8");
let result: String = codepage_to_string_win32(
result.to_bytes(),
winnls::CP_UTF7,
(string.len() / 2).try_into().unwrap(),
)
.unwrap();
assert!(result == string, "didn't roundtrip via UTF-7");
}
}
{
// Try transcoding to Windows-1252. This is the slowest part of the test
// (`WideCharToMultiByte` is very slow at this, for some reason), so it's done last.
//eprintln!("Allocating & filling non-ASCII for Windows-1252");
let string_byte_pairs = vec![u16::from_be(0xc2ae); MAX_INPUT_LEN / 2];
//eprintln!("Checking UTF-8 correctness");
let string: &str =
std::str::from_utf8(bytemuck::cast_slice(string_byte_pairs.as_slice())).unwrap();
//eprintln!("Transcoding non-ASCII to Windows-1252");
let result = expect_success(string, str_to_cstr_win32(string.into(), 1252));
//eprintln!("Checking Windows-1252 for correctness");
assert!(
result.to_bytes().iter().all(|byte| *byte == 0xae),
"string didn't transcode to Windows-1252 properly"
);
}
}

7
vendor/jni/src/wrapper/java_vm/mod.rs vendored Normal file
View File

@@ -0,0 +1,7 @@
#[cfg(feature = "invocation")]
mod init_args;
#[cfg(feature = "invocation")]
pub use self::init_args::*;
mod vm;
pub use self::vm::*;

627
vendor/jni/src/wrapper/java_vm/vm.rs vendored Normal file
View File

@@ -0,0 +1,627 @@
use std::{
cell::RefCell,
ops::{Deref, DerefMut},
ptr,
sync::atomic::{AtomicUsize, Ordering},
thread::{current, Thread},
};
use log::{debug, error};
use crate::{errors::*, sys, JNIEnv};
#[cfg(feature = "invocation")]
use {
crate::InitArgs,
std::os::raw::c_void,
std::{ffi::OsStr, path::PathBuf},
};
/// 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.21.1", features = ["invocation"] }
/// ```
///
/// The application will be able to use [`JavaVM::new`] which will dynamically
/// load a `jvm` library (which is distributed with the JVM) at runtime:
///
/// ```rust
/// # use jni::errors;
/// # //
/// # // Ignore this test without invocation feature, so that simple `cargo test` works
/// # #[cfg(feature = "invocation")]
/// # fn main() -> errors::StartJvmResult<()> {
/// # 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 mut 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(()) }
/// #
/// # // This is a stub that gets run instead if the invocation feature is not built
/// # #[cfg(not(feature = "invocation"))]
/// # fn main() {}
/// ```
///
/// At runtime, the JVM installation path is determined via the [java-locator] crate:
/// 1. By the `JAVA_HOME` environment variable, if it is set.
/// 2. Otherwise — from `java` output.
///
/// It is recommended to set `JAVA_HOME`
///
/// For the operating system to correctly load the `jvm` library it may also be
/// necessary to update the path that the OS uses to find dependencies of the
/// `jvm` library.
/// * On **Windows**, append the path to `$JAVA_HOME/bin` to the `PATH` environment variable.
/// * On **MacOS**, append the path to `libjvm.dylib` to `LD_LIBRARY_PATH` environment variable.
/// * On **Linux**, append the path to `libjvm.so` to `LD_LIBRARY_PATH` environment variable.
///
/// The exact relative path to `jvm` library is version-specific.
///
/// [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
/// [java-locator]: https://crates.io/crates/java-locator
#[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 the "invocation" feature to be enabled,
/// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).*
///
/// This will attempt to locate a JVM using
/// [java-locator], if the JVM has not already been loaded. Use the
/// [`with_libjvm`][Self::with_libjvm] method to give an explicit location for the JVM shared
/// library (`jvm.dll`, `libjvm.so`, or `libjvm.dylib`, depending on the platform).
#[cfg(feature = "invocation")]
pub fn new(args: InitArgs) -> StartJvmResult<Self> {
Self::with_libjvm(args, || {
Ok([
java_locator::locate_jvm_dyn_library()
.map_err(StartJvmError::NotFound)?
.as_str(),
java_locator::get_jvm_dyn_lib_file_name(),
]
.iter()
.collect::<PathBuf>())
})
}
/// Launch a new JavaVM using the provided init args, loading it from the given shared library file if it's not already loaded.
///
/// Unlike original JNI API, the main thread (the thread from which this method is called) will
/// not be attached to JVM. You must explicitly use `attach_current_thread…` methods (refer
/// to [Attaching Native Threads section](#attaching-native-threads)).
///
/// *This API requires the "invocation" feature to be enabled,
/// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).*
///
/// The `libjvm_path` parameter takes a *closure* which returns the path to the JVM shared
/// library. The closure is only called if the JVM is not already loaded. Any work that needs
/// to be done to locate the JVM shared library should be done inside that closure.
#[cfg(feature = "invocation")]
pub fn with_libjvm<P: AsRef<OsStr>>(
args: InitArgs,
libjvm_path: impl FnOnce() -> StartJvmResult<P>,
) -> StartJvmResult<Self> {
// Determine the path to the shared library.
let libjvm_path = libjvm_path()?;
let libjvm_path_string = libjvm_path.as_ref().to_string_lossy().into_owned();
// Try to load it.
let libjvm = match unsafe { libloading::Library::new(libjvm_path.as_ref()) } {
Ok(ok) => ok,
Err(error) => return Err(StartJvmError::LoadError(libjvm_path_string, error)),
};
unsafe {
// Try to find the `JNI_CreateJavaVM` function in the loaded library.
let create_fn = libjvm
.get(b"JNI_CreateJavaVM\0")
.map_err(|error| StartJvmError::LoadError(libjvm_path_string.to_owned(), error))?;
// Create the JVM.
Self::with_create_fn_ptr(args, *create_fn).map_err(StartJvmError::Create)
}
}
#[cfg(feature = "invocation")]
unsafe fn with_create_fn_ptr(
args: InitArgs,
create_fn_ptr: unsafe extern "system" fn(
pvm: *mut *mut sys::JavaVM,
penv: *mut *mut c_void,
args: *mut c_void,
) -> sys::jint,
) -> Result<Self> {
let mut ptr: *mut sys::JavaVM = ::std::ptr::null_mut();
let mut env: *mut sys::JNIEnv = ::std::ptr::null_mut();
jni_error_code_to_result(create_fn_ptr(
&mut ptr as *mut _,
&mut env as *mut *mut sys::JNIEnv as *mut *mut c_void,
args.inner_ptr(),
))?;
let vm = Self::from_raw(ptr)?;
java_vm_unchecked!(vm.0, DetachCurrentThread);
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<Self> {
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<JNIEnv> {
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<AttachGuard> {
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))
}
}
}
/// Explicitly detaches the current thread from the JVM.
///
/// _**Note**: This operation is _rarely_ appropriate to use, because the
/// attachment methods [ensure](#attaching-native-threads) that the thread
/// is automatically detached._
///
/// Detaching a non-attached thread is a no-op.
///
/// To support the use of `JavaVM::destroy()` it may be necessary to use this API to
/// explicitly detach daemon threads before `JavaVM::destroy()` is called because
/// `JavaVM::destroy()` does not synchronize and wait for daemon threads.
///
/// Any daemon thread that is still "attached" after `JavaVM::destroy()` returns would
/// cause undefined behaviour if it then tries to make any JNI calls or tries
/// to detach itself.
///
/// Normally `jni-rs` will automatically detach threads from the `JavaVM` by storing
/// a guard in thread-local-storage that will detach on `Drop` but this will cause
/// undefined behaviour if `JavaVM::destroy()` has been called.
///
/// Calling this will clear the thread-local-storage guard and detach the thread
/// early to avoid any attempt to automatically detach when the thread exits.
///
/// # Safety
///
/// __Any existing `JNIEnv`s and `AttachGuard`s created in the calling thread
/// will be invalidated after this method completes. It is the__ callers __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.
pub unsafe 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<JNIEnv> {
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<JNIEnv> {
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<JNIEnv> {
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) }
}
/// Unloads the JavaVM and frees all it's associated resources
///
/// Firstly if this thread is not already attached to the `JavaVM` then
/// it will be attached.
///
/// This thread will then wait until there are no other non-daemon threads
/// attached to the `JavaVM` before unloading it (including threads spawned
/// by Java and those that are attached via JNI)
///
/// # Safety
///
/// IF YOU ARE USING DAEMON THREADS THIS MAY BE DIFFICULT TO USE SAFELY!
///
/// ## Daemon thread rules
///
/// Since the JNI spec makes it clear that `DestroyJavaVM` will not wait for
/// attached deamon threads to exit, this also means that if you do have any
/// attached daemon threads it is your responsibility to ensure that they
/// don't try and use JNI after the `JavaVM` is destroyed and you won't be able
/// to detach them after the `JavaVM` has been destroyed.
///
/// This creates a very unsafe hazard in `jni-rs` because it normally automatically
/// ensures that any thread that gets attached will be detached before it exits.
///
/// Normally `jni-rs` will automatically detach threads from the `JavaVM` by storing
/// a guard in thread-local-storage that will detach on `Drop` but this will cause
/// undefined behaviour if `JavaVM::destroy()` has been called before the thread
/// exits.
///
/// To clear this thread-local-storage guard from daemon threads you can call
/// [`JavaVM::detach_current_thread()`] within each daemon thread, before calling
/// this API.
///
/// Calling this will clear the thread-local-storage guard and detach the thread
/// early to avoid any attempt to automatically detach when the thread exits.
///
/// ## Don't call from a Java native function
///
/// There must be no Java methods on the call stack when `JavaVM::destroy()` is called.
///
/// ## Drop all JNI state, including auto-release types before calling `JavaVM::destroy()`
///
/// There is currently no `'vm` lifetime associated with a `JavaVM` that
/// would allow the borrow checker to enforce that all `jni` resources
/// associated with the `JavaVM` have been released.
///
/// Since these JNI resources could lead to undefined behaviour through any
/// use after the `JavaVM` has been destroyed then it is your responsibility
/// to release these resources.
///
/// In particular, there are numerous auto-release types in the `jni` API
/// that will automatically make JNI calls within their `Drop`
/// implementation. All such types _must_ be dropped before `destroy()` is
/// called to avoid undefined bahaviour.
///
/// Here is an non-exhaustive list of auto-release types to consider:
/// - `AttachGuard`
/// - `AutoElements`
/// - `AutoElementsCritical`
/// - `AutoLocal`
/// - `GlobalRef`
/// - `JavaStr`
/// - `JMap`
/// - `WeakRef`
///
/// ## Invalid `JavaVM` on return
///
/// After `destroy()` returns then the `JavaVM` will be in an undefined state
/// and must be dropped (e.g. via `std::mem::drop()`) to avoid undefined behaviour.
///
/// This method doesn't take ownership of the `JavaVM` before it is
/// destroyed because the `JavaVM` may have been shared (E.g. via an `Arc`)
/// between all the threads that have not yet necessarily exited before this
/// is called.
///
/// So although the `JavaVM` won't necessarily be solely owned by this
/// thread when `destroy()` is first called it will conceptually own the
/// `JavaVM` before `destroy()` returns.
pub unsafe fn destroy(&self) -> Result<()> {
unsafe {
let res = java_vm_unchecked!(self.0, DestroyJavaVM);
jni_error_code_to_result(res)
}
}
}
thread_local! {
static THREAD_ATTACH_GUARD: RefCell<Option<InternalAttachGuard>> = 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<'local> {
env: JNIEnv<'local>,
should_detach: bool,
}
impl<'local> AttachGuard<'local> {
/// AttachGuard created with this method will detach current thread on drop
fn new(env: JNIEnv<'local>) -> 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<'local>) -> Self {
Self {
env,
should_detach: false,
}
}
}
impl<'local> Deref for AttachGuard<'local> {
type Target = JNIEnv<'local>;
fn deref(&self) -> &Self::Target {
&self.env
}
}
impl<'local> DerefMut for AttachGuard<'local> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.env
}
}
impl<'local> Drop for AttachGuard<'local> {
fn drop(&mut self) {
if self.should_detach {
InternalAttachGuard::clear_tls();
}
}
}
#[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(),
);
}
}
}

3007
vendor/jni/src/wrapper/jnienv.rs vendored Normal file

File diff suppressed because it is too large Load Diff

142
vendor/jni/src/wrapper/macros.rs vendored Normal file
View File

@@ -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
}
}
};
}

View File

@@ -0,0 +1,264 @@
use log::error;
use std::ptr::NonNull;
use crate::sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort};
use crate::wrapper::objects::ReleaseMode;
use crate::{errors::*, sys, JNIEnv};
use super::JPrimitiveArray;
#[cfg(doc)]
use super::JByteArray;
mod type_array_sealed {
use crate::sys::{jarray, jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort};
use crate::{errors::*, JNIEnv};
use std::ptr::NonNull;
/// Trait to define type array access/release
///
/// # Safety
///
/// The methods of this trait must uphold the invariants described in [`JNIEnv::unsafe_clone`] when
/// using the provided [`JNIEnv`].
///
/// The `get` method must return a valid pointer to the beginning of the JNI array.
///
/// The `release` method must not invalidate the `ptr` if the `mode` is [`sys::JNI_COMMIT`].
pub unsafe trait TypeArraySealed: Copy {
/// getter
///
/// # Safety
///
/// `array` must be a valid pointer to an `Array` object, or `null`
///
/// The caller is responsible for passing the returned pointer to [`release`], along
/// with the same `env` and `array` reference (which needs to still be valid)
unsafe fn get(env: &mut JNIEnv, array: jarray, is_copy: &mut jboolean)
-> Result<*mut Self>;
/// releaser
///
/// # Safety
///
/// `ptr` must have been previously returned by the `get` function.
///
/// If `mode` is not [`sys::JNI_COMMIT`], `ptr` must not be used again after calling this
/// function.
unsafe fn release(
env: &mut JNIEnv,
array: jarray,
ptr: NonNull<Self>,
mode: i32,
) -> Result<()>;
}
// TypeArray builder
macro_rules! type_array {
( $jni_type:ty, $jni_get:tt, $jni_release:tt ) => {
/// $jni_type array access/release impl
unsafe impl TypeArraySealed for $jni_type {
/// Get Java $jni_type array
unsafe fn get(
env: &mut JNIEnv,
array: jarray,
is_copy: &mut jboolean,
) -> Result<*mut Self> {
let internal = env.get_native_interface();
// Even though this method may throw OoME, use `jni_unchecked`
// instead of `jni_non_null_call` to remove (a slight) overhead
// of exception checking. An error will still be detected as a `null`
// result inside AutoElements ctor. Also, modern Hotspot in case of lack
// of memory will return null and won't throw an exception:
// https://sourcegraph.com/github.com/openjdk/jdk/-/blob/src/hotspot/share/memory/allocation.hpp#L488-489
let res = jni_unchecked!(internal, $jni_get, array, is_copy);
Ok(res)
}
/// Release Java $jni_type array
unsafe fn release(
env: &mut JNIEnv,
array: jarray,
ptr: NonNull<Self>,
mode: i32,
) -> Result<()> {
let internal = env.get_native_interface();
jni_unchecked!(internal, $jni_release, array, ptr.as_ptr(), mode as i32);
Ok(())
}
}
};
}
type_array!(jint, GetIntArrayElements, ReleaseIntArrayElements);
type_array!(jlong, GetLongArrayElements, ReleaseLongArrayElements);
type_array!(jbyte, GetByteArrayElements, ReleaseByteArrayElements);
type_array!(
jboolean,
GetBooleanArrayElements,
ReleaseBooleanArrayElements
);
type_array!(jchar, GetCharArrayElements, ReleaseCharArrayElements);
type_array!(jshort, GetShortArrayElements, ReleaseShortArrayElements);
type_array!(jfloat, GetFloatArrayElements, ReleaseFloatArrayElements);
type_array!(jdouble, GetDoubleArrayElements, ReleaseDoubleArrayElements);
}
/// A sealed trait to define type array access/release for primitive JNI types
pub trait TypeArray: type_array_sealed::TypeArraySealed {}
impl TypeArray for jint {}
impl TypeArray for jlong {}
impl TypeArray for jbyte {}
impl TypeArray for jboolean {}
impl TypeArray for jchar {}
impl TypeArray for jshort {}
impl TypeArray for jfloat {}
impl TypeArray for jdouble {}
/// Auto-release wrapper for a mutable pointer to the elements of a [`JPrimitiveArray`]
/// (such as [`JByteArray`])
///
/// This type is used to wrap pointers returned by `Get<Type>ArrayElements`
/// and ensure the pointer is released via `Release<Type>ArrayElements` when dropped.
pub struct AutoElements<'local, 'other_local, 'array, T: TypeArray> {
array: &'array JPrimitiveArray<'other_local, T>,
len: usize,
ptr: NonNull<T>,
mode: ReleaseMode,
is_copy: bool,
env: JNIEnv<'local>,
}
impl<'local, 'other_local, 'array, T: TypeArray> AutoElements<'local, 'other_local, 'array, T> {
/// # Safety
///
/// `len` must be the correct length (number of elements) of the given `array`
pub(crate) unsafe fn new_with_len(
env: &mut JNIEnv<'local>,
array: &'array JPrimitiveArray<'other_local, T>,
len: usize,
mode: ReleaseMode,
) -> Result<Self> {
// Safety: The cloned `JNIEnv` will not be used to create any local references. It will be
// passed to the methods of the `TypeArray` implementation, but that trait is `unsafe` and
// implementations are required to uphold the invariants of `unsafe_clone`.
let mut env = unsafe { env.unsafe_clone() };
let mut is_copy: jboolean = 0xff;
let ptr = unsafe { T::get(&mut env, array.as_raw(), &mut is_copy) }?;
Ok(AutoElements {
array,
len,
ptr: NonNull::new(ptr).ok_or(Error::NullPtr("Non-null ptr expected"))?,
mode,
is_copy: is_copy == sys::JNI_TRUE,
env,
})
}
pub(crate) fn new(
env: &mut JNIEnv<'local>,
array: &'array JPrimitiveArray<'other_local, T>,
mode: ReleaseMode,
) -> Result<Self> {
let len = env.get_array_length(array)? as usize;
unsafe { Self::new_with_len(env, array, len, mode) }
}
/// Get a reference to the wrapped pointer
pub fn as_ptr(&self) -> *mut T {
self.ptr.as_ptr()
}
/// Commits the changes to the array, if it is a copy
pub fn commit(&mut self) -> Result<()> {
unsafe { self.release_array_elements(sys::JNI_COMMIT) }
}
/// Calls the release function.
///
/// # Safety
///
/// `mode` must be a valid parameter to the JNI `Release<PrimitiveType>ArrayElements`' `mode`
/// parameter.
///
/// If `mode` is not [`sys::JNI_COMMIT`], then `self.ptr` must not have already been released.
unsafe fn release_array_elements(&mut self, mode: i32) -> Result<()> {
T::release(&mut self.env, self.array.as_raw(), self.ptr, mode)
}
/// Don't copy back the changes to the array on release (if it is a copy).
///
/// This has no effect if the array is not a copy.
///
/// This method is useful to change the release mode of an array originally created
/// with `ReleaseMode::CopyBack`.
pub fn discard(&mut self) {
self.mode = ReleaseMode::NoCopyBack;
}
/// Indicates if the array is a copy or not
pub fn is_copy(&self) -> bool {
self.is_copy
}
/// Returns the array length (number of elements)
pub fn len(&self) -> usize {
self.len
}
/// Returns true if the vector contains no elements.
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<'local, 'other_local, 'array, T: TypeArray>
AsRef<AutoElements<'local, 'other_local, 'array, T>>
for AutoElements<'local, 'other_local, 'array, T>
{
fn as_ref(&self) -> &AutoElements<'local, 'other_local, 'array, T> {
self
}
}
impl<'local, 'other_local, 'array, T: TypeArray> Drop
for AutoElements<'local, 'other_local, 'array, T>
{
fn drop(&mut self) {
// Safety: `self.mode` is valid and the array has not yet been released.
let res = unsafe { self.release_array_elements(self.mode as i32) };
match res {
Ok(()) => {}
Err(e) => error!("error releasing array: {:#?}", e),
}
}
}
impl<'local, 'other_local, 'array, T: TypeArray>
From<&AutoElements<'local, 'other_local, 'array, T>> for *mut T
{
fn from(other: &AutoElements<T>) -> *mut T {
other.as_ptr()
}
}
impl<'local, 'other_local, 'array, T: TypeArray> std::ops::Deref
for AutoElements<'local, 'other_local, 'array, T>
{
type Target = [T];
fn deref(&self) -> &Self::Target {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
}
impl<'local, 'other_local, 'array, T: TypeArray> std::ops::DerefMut
for AutoElements<'local, 'other_local, 'array, T>
{
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_mut(), self.len) }
}
}

View File

@@ -0,0 +1,168 @@
use log::error;
use std::ptr::NonNull;
use crate::sys::jboolean;
use crate::wrapper::objects::ReleaseMode;
use crate::{errors::*, sys, JNIEnv};
use super::{JPrimitiveArray, TypeArray};
#[cfg(doc)]
use super::JByteArray;
/// Auto-release wrapper for a mutable pointer to the elements of a [`JPrimitiveArray`]
/// (such as [`JByteArray`])
///
/// This type is used to wrap pointers returned by `GetPrimitiveArrayCritical`
/// and ensure the pointer is released via `ReleasePrimitiveArrayCritical` when dropped.
pub struct AutoElementsCritical<'local, 'other_local, 'array, 'env, T: TypeArray> {
array: &'array JPrimitiveArray<'other_local, T>,
len: usize,
ptr: NonNull<T>,
mode: ReleaseMode,
is_copy: bool,
env: &'env mut JNIEnv<'local>,
}
impl<'local, 'other_local, 'array, 'env, T: TypeArray>
AutoElementsCritical<'local, 'other_local, 'array, 'env, T>
{
/// # Safety
///
/// `len` must be the correct length (number of elements) of the given `array`
pub(crate) unsafe fn new_with_len(
env: &'env mut JNIEnv<'local>,
array: &'array JPrimitiveArray<'other_local, T>,
len: usize,
mode: ReleaseMode,
) -> Result<Self> {
let mut is_copy: jboolean = 0xff;
// Even though this method may throw OoME, use `jni_unchecked`
// instead of `jni_non_null_call` to remove (a slight) overhead
// of exception checking. An error will still be detected as a `null`
// result below; and, as this method is unlikely to create a copy,
// an OoME is highly unlikely.
let ptr = jni_unchecked!(
env.get_native_interface(),
GetPrimitiveArrayCritical,
array.as_raw(),
&mut is_copy
) as *mut T;
Ok(AutoElementsCritical {
array,
len,
ptr: NonNull::new(ptr).ok_or(Error::NullPtr("Non-null ptr expected"))?,
mode,
is_copy: is_copy == sys::JNI_TRUE,
env,
})
}
pub(crate) fn new(
env: &'env mut JNIEnv<'local>,
array: &'array JPrimitiveArray<'other_local, T>,
mode: ReleaseMode,
) -> Result<Self> {
let len = env.get_array_length(array)? as usize;
unsafe { Self::new_with_len(env, array, len, mode) }
}
/// Get a reference to the wrapped pointer
pub fn as_ptr(&self) -> *mut T {
self.ptr.as_ptr()
}
/// Calls `ReleasePrimitiveArrayCritical`.
///
/// # Safety
///
/// `mode` must be a valid parameter to the JNI `ReleasePrimitiveArrayCritical` `mode`
/// parameter.
///
/// If `mode` is not [`sys::JNI_COMMIT`], then `self.ptr` must not have already been released.
unsafe fn release_primitive_array_critical(&mut self, mode: i32) -> Result<()> {
jni_unchecked!(
self.env.get_native_interface(),
ReleasePrimitiveArrayCritical,
self.array.as_raw(),
self.ptr.as_ptr().cast(),
mode
);
Ok(())
}
/// Don't copy back the changes to the array on release (if it is a copy).
///
/// This has no effect if the array is not a copy.
///
/// This method is useful to change the release mode of an array originally created
/// with `ReleaseMode::CopyBack`.
pub fn discard(&mut self) {
self.mode = ReleaseMode::NoCopyBack;
}
/// Indicates if the array is a copy or not
pub fn is_copy(&self) -> bool {
self.is_copy
}
/// Returns the array length (number of elements)
pub fn len(&self) -> usize {
self.len
}
/// Returns true if the vector contains no elements.
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<'local, 'other_local, 'array, 'env, T: TypeArray>
AsRef<AutoElementsCritical<'local, 'other_local, 'array, 'env, T>>
for AutoElementsCritical<'local, 'other_local, 'array, 'env, T>
{
fn as_ref(&self) -> &AutoElementsCritical<'local, 'other_local, 'array, 'env, T> {
self
}
}
impl<'local, 'other_local, 'array, 'env, T: TypeArray> Drop
for AutoElementsCritical<'local, 'other_local, 'array, 'env, T>
{
fn drop(&mut self) {
// Safety: `self.mode` is valid and the array has not yet been released.
let res = unsafe { self.release_primitive_array_critical(self.mode as i32) };
match res {
Ok(()) => {}
Err(e) => error!("error releasing primitive array: {:#?}", e),
}
}
}
impl<'local, 'other_local, 'array, 'env, T: TypeArray>
From<&AutoElementsCritical<'local, 'other_local, 'array, 'env, T>> for *mut T
{
fn from(other: &AutoElementsCritical<T>) -> *mut T {
other.as_ptr()
}
}
impl<'local, 'other_local, 'array, 'env, T: TypeArray> std::ops::Deref
for AutoElementsCritical<'local, 'other_local, 'array, 'env, T>
{
type Target = [T];
fn deref(&self) -> &Self::Target {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
}
impl<'local, 'other_local, 'array, 'env, T: TypeArray> std::ops::DerefMut
for AutoElementsCritical<'local, 'other_local, 'array, 'env, T>
{
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_mut(), self.len) }
}
}

View File

@@ -0,0 +1,156 @@
use std::{
mem::ManuallyDrop,
ops::{Deref, DerefMut},
ptr,
};
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.
///
/// 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
#[derive(Debug)]
pub struct AutoLocal<'local, T>
where
T: Into<JObject<'local>>,
{
obj: ManuallyDrop<T>,
env: JNIEnv<'local>,
}
impl<'local, T> AutoLocal<'local, T>
where
// Note that this bound prevents `AutoLocal` from wrapping a `GlobalRef`, which implements
// `AsRef<JObject<'static>>` but *not* `Into<JObject<'static>>`. This is good, because trying
// to delete a global reference as though it were local would cause undefined behavior.
T: Into<JObject<'local>>,
{
/// Creates a new auto-delete wrapper for a local ref.
///
/// Once this wrapper goes out of scope, the `delete_local_ref` will be
/// called on the object. While wrapped, the object can be accessed via
/// the `Deref` impl.
pub fn new(obj: T, env: &JNIEnv<'local>) -> Self {
// Safety: The cloned `JNIEnv` will not be used to create any local references, only to
// delete one.
let env = unsafe { env.unsafe_clone() };
AutoLocal {
obj: ManuallyDrop::new(obj),
env,
}
}
/// Forget the wrapper, returning the original object.
///
/// 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) -> T {
// We need to move `self.obj` out of `self`. Normally that's trivial, but moving out of a
// type with a `Drop` implementation is not allowed. We'll have to do it manually (and
// carefully) with `unsafe`.
//
// This could be done without `unsafe` by adding `where T: Default` and using
// `std::mem::replace` to extract `self.obj`, but doing it this way avoids unnecessarily
// running the drop routine on `self`.
// Before we mutilate `self`, make sure its drop code will not be automatically run. That
// would cause undefined behavior.
let mut self_md = ManuallyDrop::new(self);
unsafe {
// Drop the `JNIEnv` in place. As of this writing, that's a no-op, but if `JNIEnv`
// gains any drop code in the future, this will run it.
//
// Safety: The `&mut` proves that `self_md.env` is valid and not aliased. It is not
// accessed again after this point. It is wrapped inside `ManuallyDrop`, and will
// therefore not be dropped twice.
ptr::drop_in_place(&mut self_md.env);
// Move `obj` out of `self` and return it.
//
// Safety: The `&mut` proves that `self_md.obj` is valid and not aliased. It is not
// accessed again after this point. It is wrapped inside `ManuallyDrop`, and will
// therefore not be dropped after it is moved.
ptr::read(&*self_md.obj)
}
}
}
impl<'local, T> Drop for AutoLocal<'local, T>
where
T: Into<JObject<'local>>,
{
fn drop(&mut self) {
// Extract the local reference from `self.obj` so that we can delete it.
//
// This is needed because it is not allowed to move out of `self` during drop. A safe
// alternative would be to wrap `self.obj` in `Option`, but that would incur a run-time
// performance penalty from constantly checking if it's `None`.
//
// Safety: `self.obj` is not used again after this `take` call.
let obj = unsafe { ManuallyDrop::take(&mut self.obj) };
// Delete the extracted local reference.
let res = self.env.delete_local_ref(obj);
match res {
Ok(()) => {}
Err(e) => debug!("error dropping global ref: {:#?}", e),
}
}
}
impl<'local, T, U> AsRef<U> for AutoLocal<'local, T>
where
T: AsRef<U> + Into<JObject<'local>>,
{
fn as_ref(&self) -> &U {
self.obj.as_ref()
}
}
impl<'local, T, U> AsMut<U> for AutoLocal<'local, T>
where
T: AsMut<U> + Into<JObject<'local>>,
{
fn as_mut(&mut self) -> &mut U {
self.obj.as_mut()
}
}
impl<'local, T> Deref for AutoLocal<'local, T>
where
T: Into<JObject<'local>>,
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.obj
}
}
impl<'local, T> DerefMut for AutoLocal<'local, T>
where
T: Into<JObject<'local>>,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.obj
}
}

View File

@@ -0,0 +1,116 @@
use std::{mem, ops::Deref, sync::Arc};
use log::{debug, warn};
use crate::{errors::Result, objects::JObject, sys, JNIEnv, JavaVM};
// Note: `GlobalRef` must not implement `Into<JObject>`! If it did, then it would be possible to
// wrap it in `AutoLocal`, which would cause undefined behavior upon drop as a result of calling
// the wrong JNI function to delete the reference.
/// A global JVM reference. These are "pinned" by the garbage collector and are
/// guaranteed to not get collected until released. Thus, this is allowed to
/// outlive the `JNIEnv` that it came from and can be used in other threads.
///
/// `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<GlobalRefGuard>,
}
#[derive(Debug)]
struct GlobalRefGuard {
obj: JObject<'static>,
vm: JavaVM,
}
impl AsRef<GlobalRef> for GlobalRef {
fn as_ref(&self) -> &GlobalRef {
self
}
}
impl AsRef<JObject<'static>> for GlobalRef {
fn as_ref(&self) -> &JObject<'static> {
self
}
}
impl Deref for GlobalRef {
type Target = JObject<'static>;
fn deref(&self) -> &Self::Target {
&self.inner.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<'static> {
self.as_ref()
}
}
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,
}
}
}
impl Drop for GlobalRefGuard {
fn drop(&mut self) {
let raw: sys::jobject = mem::take(&mut self.obj).into_raw();
let drop_impl = |env: &JNIEnv| -> Result<()> {
let internal = env.get_native_interface();
// This method is safe to call in case of pending exceptions (see chapter 2 of the spec)
jni_unchecked!(internal, DeleteGlobalRef, raw);
Ok(())
};
let res = match self.vm.get_env() {
Ok(env) => drop_impl(&env),
Err(_) => {
warn!("Dropping a GlobalRef in a detached thread. Fix your code if this message appears frequently (see the GlobalRef docs).");
self.vm
.attach_current_thread()
.and_then(|env| drop_impl(&env))
}
};
if let Err(err) = res {
debug!("error dropping global ref: {:#?}", err);
}
}
}

View File

@@ -0,0 +1,68 @@
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(Debug)]
pub struct JByteBuffer<'local>(JObject<'local>);
impl<'local> AsRef<JByteBuffer<'local>> for JByteBuffer<'local> {
fn as_ref(&self) -> &JByteBuffer<'local> {
self
}
}
impl<'local> AsRef<JObject<'local>> for JByteBuffer<'local> {
fn as_ref(&self) -> &JObject<'local> {
self
}
}
impl<'local> ::std::ops::Deref for JByteBuffer<'local> {
type Target = JObject<'local>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'local> From<JByteBuffer<'local>> for JObject<'local> {
fn from(other: JByteBuffer) -> JObject {
other.0
}
}
impl<'local> From<JObject<'local>> for JByteBuffer<'local> {
fn from(other: JObject) -> Self {
unsafe { Self::from_raw(other.into_raw()) }
}
}
impl<'local, 'obj_ref> From<&'obj_ref JObject<'local>> for &'obj_ref JByteBuffer<'local> {
fn from(other: &'obj_ref JObject<'local>) -> Self {
// Safety: `JByteBuffer` is `repr(transparent)` around `JObject`.
unsafe { &*(other as *const JObject<'local> as *const JByteBuffer<'local>) }
}
}
impl<'local> std::default::Default for JByteBuffer<'local> {
fn default() -> Self {
Self(JObject::null())
}
}
impl<'local> JByteBuffer<'local> {
/// 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
}
}

View File

@@ -0,0 +1,83 @@
use crate::{
objects::JObject,
sys::{jclass, jobject},
};
/// Lifetime'd representation of a `jclass`. Just a `JObject` wrapped in a new
/// class.
#[repr(transparent)]
#[derive(Debug)]
pub struct JClass<'local>(JObject<'local>);
impl<'local> AsRef<JClass<'local>> for JClass<'local> {
fn as_ref(&self) -> &JClass<'local> {
self
}
}
impl<'local> AsRef<JObject<'local>> for JClass<'local> {
fn as_ref(&self) -> &JObject<'local> {
self
}
}
impl<'local> ::std::ops::Deref for JClass<'local> {
type Target = JObject<'local>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'local> From<JClass<'local>> for JObject<'local> {
fn from(other: JClass) -> JObject {
other.0
}
}
/// This conversion assumes that the `JObject` is a pointer to a class object.
impl<'local> From<JObject<'local>> for JClass<'local> {
fn from(other: JObject) -> Self {
unsafe { Self::from_raw(other.into_raw()) }
}
}
/// This conversion assumes that the `JObject` is a pointer to a class object.
impl<'local, 'obj_ref> From<&'obj_ref JObject<'local>> for &'obj_ref JClass<'local> {
fn from(other: &'obj_ref JObject<'local>) -> Self {
// Safety: `JClass` is `repr(transparent)` around `JObject`.
unsafe { &*(other as *const JObject<'local> as *const JClass<'local>) }
}
}
impl<'local> std::default::Default for JClass<'local> {
fn default() -> Self {
Self(JObject::null())
}
}
impl<'local> JClass<'local> {
/// Creates a [`JClass`] that wraps the given `raw` [`jclass`]
///
/// # Safety
///
/// `raw` may be a null pointer. If `raw` is not a null pointer, then:
///
/// * `raw` must be a valid raw JNI local reference.
/// * There must not be any other `JObject` representing the same local reference.
/// * The lifetime `'local` must not outlive the local reference frame that the local reference
/// was created in.
pub unsafe fn from_raw(raw: jclass) -> Self {
Self(JObject::from_raw(raw as jobject))
}
/// Returns the raw JNI pointer.
pub fn as_raw(&self) -> jclass {
self.0.as_raw() as jclass
}
/// Unwrap to the raw jni type.
pub fn into_raw(self) -> jclass {
self.0.into_raw() as jclass
}
}

View File

@@ -0,0 +1,60 @@
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
}
}
impl AsRef<JFieldID> for JFieldID {
fn as_ref(&self) -> &JFieldID {
self
}
}
impl AsMut<JFieldID> for JFieldID {
fn as_mut(&mut self) -> &mut JFieldID {
self
}
}

299
vendor/jni/src/wrapper/objects/jlist.rs vendored Normal file
View File

@@ -0,0 +1,299 @@
use crate::{
errors::*,
objects::{AutoLocal, JClass, JMethodID, JObject, JValue},
signature::{Primitive, ReturnType},
sys::jint,
JNIEnv,
};
use std::marker::PhantomData;
/// Wrapper for JObjects that implement `java/util/List`. Provides methods to get,
/// add, and remove elements.
///
/// Looks up the class and method ids on creation rather than for every method
/// call.
pub struct JList<'local, 'other_local_1: 'obj_ref, 'obj_ref> {
internal: &'obj_ref JObject<'other_local_1>,
_phantom_class: PhantomData<AutoLocal<'local, JClass<'local>>>,
get: JMethodID,
add: JMethodID,
add_idx: JMethodID,
remove: JMethodID,
size: JMethodID,
}
impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef<JList<'local, 'other_local_1, 'obj_ref>>
for JList<'local, 'other_local_1, 'obj_ref>
{
fn as_ref(&self) -> &JList<'local, 'other_local_1, 'obj_ref> {
self
}
}
impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef<JObject<'other_local_1>>
for JList<'local, 'other_local_1, 'obj_ref>
{
fn as_ref(&self) -> &JObject<'other_local_1> {
self.internal
}
}
impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> JList<'local, 'other_local_1, 'obj_ref> {
/// Create a map from the environment and an object. This looks up the
/// necessary class and method ids to call all of the methods on it so that
/// exra work doesn't need to be done on every method call.
pub fn from_env(
env: &mut JNIEnv<'local>,
obj: &'obj_ref JObject<'other_local_1>,
) -> Result<JList<'local, 'other_local_1, 'obj_ref>> {
let class = AutoLocal::new(env.find_class("java/util/List")?, env);
let get = env.get_method_id(&class, "get", "(I)Ljava/lang/Object;")?;
let add = env.get_method_id(&class, "add", "(Ljava/lang/Object;)Z")?;
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,
_phantom_class: PhantomData,
get,
add,
add_idx,
remove,
size,
})
}
/// 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<'other_local_2>(
&self,
env: &mut JNIEnv<'other_local_2>,
idx: jint,
) -> Result<Option<JObject<'other_local_2>>> {
// SAFETY: We keep the class loaded, and fetched the method ID for this function.
// Provided argument is statically known as a JObject/null, rather than another primitive type.
let result = unsafe {
env.call_method_unchecked(
self.internal,
self.get,
ReturnType::Object,
&[JValue::from(idx).as_jni()],
)
};
match result {
Ok(val) => Ok(Some(val.l()?)),
Err(e) => match e {
Error::NullPtr(_) => Ok(None),
_ => Err(e),
},
}
}
/// Append an element to the list
pub fn add(&self, env: &mut JNIEnv, value: &JObject) -> Result<()> {
// SAFETY: We keep the class loaded, and fetched the method ID for this function.
// Provided argument is statically known as a JObject/null, rather than another primitive type.
let result = unsafe {
env.call_method_unchecked(
self.internal,
self.add,
ReturnType::Primitive(Primitive::Boolean),
&[JValue::from(value).as_jni()],
)
};
let _ = result?;
Ok(())
}
/// Insert an element at a specific index
pub fn insert(&self, env: &mut JNIEnv, idx: jint, value: &JObject) -> Result<()> {
// SAFETY: We keep the class loaded, and fetched the method ID for this function.
// Provided argument is statically known as a JObject/null, rather than another primitive type.
let result = unsafe {
env.call_method_unchecked(
self.internal,
self.add_idx,
ReturnType::Primitive(Primitive::Void),
&[JValue::from(idx).as_jni(), JValue::from(value).as_jni()],
)
};
let _ = result?;
Ok(())
}
/// Remove an element from the list by index
pub fn remove<'other_local_2>(
&self,
env: &mut JNIEnv<'other_local_2>,
idx: jint,
) -> Result<Option<JObject<'other_local_2>>> {
// SAFETY: We keep the class loaded, and fetched the method ID for this function.
// Provided argument is statically known as a int, rather than any other java type.
let result = unsafe {
env.call_method_unchecked(
self.internal,
self.remove,
ReturnType::Object,
&[JValue::from(idx).as_jni()],
)
};
match result {
Ok(val) => Ok(Some(val.l()?)),
Err(e) => match e {
Error::NullPtr(_) => Ok(None),
_ => Err(e),
},
}
}
/// Get the size of the list
pub fn size(&self, env: &mut JNIEnv) -> Result<jint> {
// SAFETY: We keep the class loaded, and fetched the method ID for this function.
let result = unsafe {
env.call_method_unchecked(
self.internal,
self.size,
ReturnType::Primitive(Primitive::Int),
&[],
)
};
result.and_then(|v| v.i())
}
/// Pop the last element from the list
///
/// Note that this calls `size()` to determine the last index.
pub fn pop<'other_local_2>(
&self,
env: &mut JNIEnv<'other_local_2>,
) -> Result<Option<JObject<'other_local_2>>> {
let size = self.size(env)?;
if size == 0 {
return Ok(None);
}
// SAFETY: We keep the class loaded, and fetched the method ID for this function.
// Provided argument is statically known as a int.
let result = unsafe {
env.call_method_unchecked(
self.internal,
self.remove,
ReturnType::Object,
&[JValue::from(size - 1).as_jni()],
)
};
match result {
Ok(val) => Ok(Some(val.l()?)),
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.
///
/// The returned iterator does not implement [`std::iter::Iterator`] and
/// cannot be used with a `for` loop. This is because its `next` method
/// uses a `&mut JNIEnv` to call the Java iterator. Use a `while let` loop
/// instead:
///
/// ```rust,no_run
/// # use jni::{errors::Result, JNIEnv, objects::{AutoLocal, JList, JObject}};
/// #
/// # fn example(env: &mut JNIEnv, list: JList) -> Result<()> {
/// let mut iterator = list.iter(env)?;
///
/// while let Some(obj) = iterator.next(env)? {
/// let obj: AutoLocal<JObject> = env.auto_local(obj);
///
/// // Do something with `obj` here.
/// }
/// # Ok(())
/// # }
/// ```
///
/// Each call to `next` creates a new local reference. To prevent excessive
/// memory usage or overflow error, the local reference should be deleted
/// using [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`] before the
/// next loop iteration. Alternatively, if the list is known to have a
/// small, predictable size, the loop could be wrapped in
/// [`JNIEnv::with_local_frame`] to delete all of the local references at
/// once.
pub fn iter<'list>(
&'list self,
env: &mut JNIEnv,
) -> Result<JListIter<'list, 'local, 'obj_ref, 'other_local_1>> {
Ok(JListIter {
list: self,
current: 0,
size: self.size(env)?,
})
}
}
/// An iterator over the keys and values in a `java.util.List`. See
/// [`JList::iter`] for more information.
///
/// TODO: make the iterator implementation for java iterators its own thing
/// and generic enough to use elsewhere.
pub struct JListIter<'list, 'local, 'other_local_1: 'obj_ref, 'obj_ref> {
list: &'list JList<'local, 'other_local_1, 'obj_ref>,
current: jint,
size: jint,
}
impl<'list, 'local, 'other_local_1: 'obj_ref, 'obj_ref>
JListIter<'list, 'local, 'other_local_1, 'obj_ref>
{
/// Advances the iterator and returns the next object in the
/// `java.util.List`, or `None` if there are no more objects.
///
/// See [`JList::iter`] for more information.
///
/// This method creates a new local reference. To prevent excessive memory
/// usage or overflow error, the local reference should be deleted using
/// [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`] before the next
/// loop iteration. Alternatively, if the list is known to have a small,
/// predictable size, the loop could be wrapped in
/// [`JNIEnv::with_local_frame`] to delete all of the local references at
/// once.
///
/// This method returns:
///
/// * `Ok(Some(_))`: if there was another object in the list.
/// * `Ok(None)`: if there are no more objects in the list.
/// * `Err(_)`: if there was an error calling the Java method to
/// get the next object.
///
/// This is like [`std::iter::Iterator::next`], but requires a parameter of
/// type `&mut JNIEnv` in order to call into Java.
pub fn next<'other_local_2>(
&mut self,
env: &mut JNIEnv<'other_local_2>,
) -> Result<Option<JObject<'other_local_2>>> {
if self.current == self.size {
return Ok(None);
}
let res = self.list.get(env, self.current);
self.current = match &res {
Ok(Some(_)) => self.current + 1,
Ok(None) => self.current,
Err(_) => self.size,
};
res
}
}

312
vendor/jni/src/wrapper/objects/jmap.rs vendored Normal file
View File

@@ -0,0 +1,312 @@
use crate::{
errors::*,
objects::{AutoLocal, JClass, JMethodID, JObject, JValue},
signature::{Primitive, ReturnType},
JNIEnv,
};
use std::marker::PhantomData;
/// Wrapper for JObjects that implement `java/util/Map`. Provides methods to get
/// and set entries and a way to iterate over key/value pairs.
///
/// Looks up the class and method ids on creation rather than for every method
/// call.
pub struct JMap<'local, 'other_local_1: 'obj_ref, 'obj_ref> {
internal: &'obj_ref JObject<'other_local_1>,
class: AutoLocal<'local, JClass<'local>>,
get: JMethodID,
put: JMethodID,
remove: JMethodID,
}
impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef<JMap<'local, 'other_local_1, 'obj_ref>>
for JMap<'local, 'other_local_1, 'obj_ref>
{
fn as_ref(&self) -> &JMap<'local, 'other_local_1, 'obj_ref> {
self
}
}
impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef<JObject<'other_local_1>>
for JMap<'local, 'other_local_1, 'obj_ref>
{
fn as_ref(&self) -> &JObject<'other_local_1> {
self.internal
}
}
impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> JMap<'local, 'other_local_1, 'obj_ref> {
/// Create a map from the environment and an object. This looks up the
/// necessary class and method ids to call all of the methods on it so that
/// exra work doesn't need to be done on every method call.
pub fn from_env(
env: &mut JNIEnv<'local>,
obj: &'obj_ref JObject<'other_local_1>,
) -> Result<JMap<'local, 'other_local_1, 'obj_ref>> {
let class = AutoLocal::new(env.find_class("java/util/Map")?, env);
let get = env.get_method_id(&class, "get", "(Ljava/lang/Object;)Ljava/lang/Object;")?;
let put = env.get_method_id(
&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,
})
}
/// 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<'other_local_2>(
&self,
env: &mut JNIEnv<'other_local_2>,
key: &JObject,
) -> Result<Option<JObject<'other_local_2>>> {
// SAFETY: We keep the class loaded, and fetched the method ID for this function.
// Provided argument is statically known as a JObject/null, rather than another primitive type.
let result = unsafe {
env.call_method_unchecked(
self.internal,
self.get,
ReturnType::Object,
&[JValue::from(key).as_jni()],
)
};
match result {
Ok(val) => Ok(Some(val.l()?)),
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<'other_local_2>(
&self,
env: &mut JNIEnv<'other_local_2>,
key: &JObject,
value: &JObject,
) -> Result<Option<JObject<'other_local_2>>> {
// SAFETY: We keep the class loaded, and fetched the method ID for this function.
// Provided argument is statically known as a JObject/null, rather than another primitive type.
let result = unsafe {
env.call_method_unchecked(
self.internal,
self.put,
ReturnType::Object,
&[JValue::from(key).as_jni(), JValue::from(value).as_jni()],
)
};
match result {
Ok(val) => Ok(Some(val.l()?)),
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<'other_local_2>(
&self,
env: &mut JNIEnv<'other_local_2>,
key: &JObject,
) -> Result<Option<JObject<'other_local_2>>> {
// SAFETY: We keep the class loaded, and fetched the method ID for this function.
// Provided argument is statically known as a JObject/null, rather than another primitive type.
let result = unsafe {
env.call_method_unchecked(
self.internal,
self.remove,
ReturnType::Object,
&[JValue::from(key).as_jni()],
)
};
match result {
Ok(val) => Ok(Some(val.l()?)),
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.
///
/// The returned iterator does not implement [`std::iter::Iterator`] and
/// cannot be used with a `for` loop. This is because its `next` method
/// uses a `&mut JNIEnv` to call the Java iterator. Use a `while let` loop
/// instead:
///
/// ```rust,no_run
/// # use jni::{errors::Result, JNIEnv, objects::{AutoLocal, JMap, JObject}};
/// #
/// # fn example(env: &mut JNIEnv, map: JMap) -> Result<()> {
/// let mut iterator = map.iter(env)?;
///
/// while let Some((key, value)) = iterator.next(env)? {
/// let key: AutoLocal<JObject> = env.auto_local(key);
/// let value: AutoLocal<JObject> = env.auto_local(value);
///
/// // Do something with `key` and `value` here.
/// }
/// # Ok(())
/// # }
/// ```
///
/// Each call to `next` creates two new local references. To prevent
/// excessive memory usage or overflow error, the local references should
/// be deleted using [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`]
/// before the next loop iteration. Alternatively, if the map is known to
/// have a small, predictable size, the loop could be wrapped in
/// [`JNIEnv::with_local_frame`] to delete all of the local references at
/// once.
pub fn iter<'map, 'iter_local>(
&'map self,
env: &mut JNIEnv<'iter_local>,
) -> Result<JMapIter<'map, 'local, 'other_local_1, 'obj_ref, 'iter_local>> {
let iter_class = AutoLocal::new(env.find_class("java/util/Iterator")?, env);
let has_next = env.get_method_id(&iter_class, "hasNext", "()Z")?;
let next = env.get_method_id(&iter_class, "next", "()Ljava/lang/Object;")?;
let entry_class = AutoLocal::new(env.find_class("java/util/Map$Entry")?, env);
let get_key = env.get_method_id(&entry_class, "getKey", "()Ljava/lang/Object;")?;
let get_value = env.get_method_id(&entry_class, "getValue", "()Ljava/lang/Object;")?;
// Get the iterator over Map entries.
// SAFETY: We keep the class loaded, and fetched the method ID for this function. Arg list is known empty.
let entry_set = AutoLocal::new(
unsafe {
env.call_method_unchecked(
self.internal,
(&self.class, "entrySet", "()Ljava/util/Set;"),
ReturnType::Object,
&[],
)
}?
.l()?,
env,
);
// SAFETY: We keep the class loaded, and fetched the method ID for this function. Arg list is known empty.
let iter = AutoLocal::new(
unsafe {
env.call_method_unchecked(
entry_set,
("java/util/Set", "iterator", "()Ljava/util/Iterator;"),
ReturnType::Object,
&[],
)
}?
.l()?,
env,
);
Ok(JMapIter {
_phantom_map: PhantomData,
has_next,
next,
get_key,
get_value,
iter,
})
}
}
/// An iterator over the keys and values in a map. See [`JMap::iter`] for more
/// information.
///
/// TODO: make the iterator implementation for java iterators its own thing
/// and generic enough to use elsewhere.
pub struct JMapIter<'map, 'local, 'other_local_1: 'obj_ref, 'obj_ref, 'iter_local> {
_phantom_map: PhantomData<&'map JMap<'local, 'other_local_1, 'obj_ref>>,
has_next: JMethodID,
next: JMethodID,
get_key: JMethodID,
get_value: JMethodID,
iter: AutoLocal<'iter_local, JObject<'iter_local>>,
}
impl<'map, 'local, 'other_local_1: 'obj_ref, 'obj_ref, 'iter_local>
JMapIter<'map, 'local, 'other_local_1, 'obj_ref, 'iter_local>
{
/// Advances the iterator and returns the next key-value pair in the
/// `java.util.Map`, or `None` if there are no more objects.
///
/// See [`JMap::iter`] for more information.
///
/// This method creates two new local references. To prevent excessive
/// memory usage or overflow error, the local references should be deleted
/// using [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`] before the
/// next loop iteration. Alternatively, if the map is known to have a
/// small, predictable size, the loop could be wrapped in
/// [`JNIEnv::with_local_frame`] to delete all of the local references at
/// once.
///
/// This method returns:
///
/// * `Ok(Some(_))`: if there was another key-value pair in the map.
/// * `Ok(None)`: if there are no more key-value pairs in the map.
/// * `Err(_)`: if there was an error calling the Java method to
/// get the next key-value pair.
///
/// This is like [`std::iter::Iterator::next`], but requires a parameter of
/// type `&mut JNIEnv` in order to call into Java.
pub fn next<'other_local_2>(
&mut self,
env: &mut JNIEnv<'other_local_2>,
) -> Result<Option<(JObject<'other_local_2>, JObject<'other_local_2>)>> {
// SAFETY: We keep the class loaded, and fetched the method ID for these functions. We know none expect args.
let has_next = unsafe {
env.call_method_unchecked(
&self.iter,
self.has_next,
ReturnType::Primitive(Primitive::Boolean),
&[],
)
}?
.z()?;
if !has_next {
return Ok(None);
}
let next =
unsafe { env.call_method_unchecked(&self.iter, self.next, ReturnType::Object, &[]) }?
.l()?;
let next = env.auto_local(next);
let key =
unsafe { env.call_method_unchecked(&next, self.get_key, ReturnType::Object, &[]) }?
.l()?;
let value =
unsafe { env.call_method_unchecked(&next, self.get_value, ReturnType::Object, &[]) }?
.l()?;
Ok(Some((key, value)))
}
}

View File

@@ -0,0 +1,60 @@
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
}
}
impl AsRef<JMethodID> for JMethodID {
fn as_ref(&self) -> &JMethodID {
self
}
}
impl AsMut<JMethodID> for JMethodID {
fn as_mut(&mut self) -> &mut JMethodID {
self
}
}

View File

@@ -0,0 +1,103 @@
use std::marker::PhantomData;
use crate::sys::jobject;
#[cfg(doc)]
use crate::{objects::GlobalRef, JNIEnv};
/// Wrapper around [`sys::jobject`] that adds a lifetime to ensure that
/// the underlying JNI pointer won't be accessible to safe Rust code if the
/// object reference is released.
///
/// It matches C's representation of the raw pointer, so it can be used in any
/// of the extern function argument positions that would take a `jobject`.
///
/// Most other types in the `objects` module deref to this, as they do in the C
/// representation.
///
/// The lifetime `'local` represents the local reference frame that this
/// reference belongs to. See the [`JNIEnv`] documentation for more information
/// about local reference frames. If `'local` is `'static`, then this reference
/// does not belong to a local reference frame, that is, it is either null or a
/// [global reference][GlobalRef].
///
/// Note that an *owned* `JObject` is always a local reference and will never
/// have the `'static` lifetime. [`GlobalRef`] does implement
/// <code>[AsRef]&lt;JObject&lt;'static>></code>, but this only yields a
/// *borrowed* `&JObject<'static>`, never an owned `JObject<'static>`.
///
/// Local references belong to a single thread and are not safe to share across
/// threads. This type implements [`Send`] and [`Sync`] if and only if the
/// lifetime `'local` is `'static`.
#[repr(transparent)]
#[derive(Debug)]
pub struct JObject<'local> {
internal: jobject,
lifetime: PhantomData<&'local ()>,
}
unsafe impl Send for JObject<'static> {}
unsafe impl Sync for JObject<'static> {}
impl<'local> AsRef<JObject<'local>> for JObject<'local> {
fn as_ref(&self) -> &JObject<'local> {
self
}
}
impl<'local> AsMut<JObject<'local>> for JObject<'local> {
fn as_mut(&mut self) -> &mut JObject<'local> {
self
}
}
impl<'local> ::std::ops::Deref for JObject<'local> {
type Target = jobject;
fn deref(&self) -> &Self::Target {
&self.internal
}
}
impl<'local> JObject<'local> {
/// Creates a [`JObject`] that wraps the given `raw` [`jobject`]
///
/// # Safety
///
/// `raw` may be a null pointer. If `raw` is not a null pointer, then:
///
/// * `raw` must be a valid raw JNI local reference.
/// * There must not be any other `JObject` representing the same local reference.
/// * The lifetime `'local` must not outlive the local reference frame that the local reference
/// was created in.
pub unsafe fn from_raw(raw: jobject) -> Self {
Self {
internal: raw,
lifetime: PhantomData,
}
}
/// Returns the raw JNI pointer.
pub fn as_raw(&self) -> jobject {
self.internal
}
/// Unwrap to the internal jni type.
pub fn into_raw(self) -> jobject {
self.internal
}
/// Creates a new null reference.
///
/// Null references are always valid and do not belong to a local reference frame. Therefore,
/// the returned `JObject` always has the `'static` lifetime.
pub fn null() -> JObject<'static> {
unsafe { JObject::from_raw(std::ptr::null_mut() as jobject) }
}
}
impl<'local> std::default::Default for JObject<'local> {
fn default() -> Self {
Self::null()
}
}

View File

@@ -0,0 +1,81 @@
use crate::{
objects::JObject,
sys::{jobject, jobjectArray},
};
use super::AsJArrayRaw;
/// Lifetime'd representation of a [`jobjectArray`] which wraps a [`JObject`] reference
#[repr(transparent)]
#[derive(Debug)]
pub struct JObjectArray<'local>(JObject<'local>);
impl<'local> AsRef<JObjectArray<'local>> for JObjectArray<'local> {
fn as_ref(&self) -> &JObjectArray<'local> {
self
}
}
impl<'local> AsRef<JObject<'local>> for JObjectArray<'local> {
fn as_ref(&self) -> &JObject<'local> {
self
}
}
impl<'local> ::std::ops::Deref for JObjectArray<'local> {
type Target = JObject<'local>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'local> From<JObjectArray<'local>> for JObject<'local> {
fn from(other: JObjectArray) -> JObject {
other.0
}
}
/// This conversion assumes that the `JObject` is a pointer to a class object.
impl<'local> From<JObject<'local>> for JObjectArray<'local> {
fn from(other: JObject) -> Self {
unsafe { Self::from_raw(other.into_raw()) }
}
}
/// This conversion assumes that the `JObject` is a pointer to a class object.
impl<'local, 'obj_ref> From<&'obj_ref JObject<'local>> for &'obj_ref JObjectArray<'local> {
fn from(other: &'obj_ref JObject<'local>) -> Self {
// Safety: `JObjectArray` is `repr(transparent)` around `JObject`.
unsafe { &*(other as *const JObject<'local> as *const JObjectArray<'local>) }
}
}
impl<'local> std::default::Default for JObjectArray<'local> {
fn default() -> Self {
Self(JObject::null())
}
}
unsafe impl<'local> AsJArrayRaw<'local> for JObjectArray<'local> {}
impl<'local> JObjectArray<'local> {
/// Creates a [`JObjectArray`] that wraps the given `raw` [`jobjectArray`]
///
/// # Safety
///
/// `raw` may be a null pointer. If `raw` is not a null pointer, then:
///
/// * `raw` must be a valid raw JNI local reference.
/// * There must not be any other `JObject` representing the same local reference.
/// * The lifetime `'local` must not outlive the local reference frame that the local reference
/// was created in.
pub unsafe fn from_raw(raw: jobjectArray) -> Self {
Self(JObject::from_raw(raw as jobject))
}
/// Unwrap to the raw jni type.
pub fn into_raw(self) -> jobjectArray {
self.0.into_raw() as jobjectArray
}
}

View File

@@ -0,0 +1,144 @@
use std::marker::PhantomData;
use crate::{
objects::JObject,
sys::{jarray, jobject},
};
use super::TypeArray;
#[cfg(doc)]
use crate::JNIEnv;
/// Lifetime'd representation of a [`jarray`] which wraps a [`JObject`] reference
///
/// This is a wrapper type for a [`JObject`] local reference that's used to
/// differentiate JVM array types.
#[repr(transparent)]
#[derive(Debug)]
pub struct JPrimitiveArray<'local, T: TypeArray> {
obj: JObject<'local>,
lifetime: PhantomData<&'local T>,
}
impl<'local, T: TypeArray> AsRef<JPrimitiveArray<'local, T>> for JPrimitiveArray<'local, T> {
fn as_ref(&self) -> &JPrimitiveArray<'local, T> {
self
}
}
impl<'local, T: TypeArray> AsMut<JPrimitiveArray<'local, T>> for JPrimitiveArray<'local, T> {
fn as_mut(&mut self) -> &mut JPrimitiveArray<'local, T> {
self
}
}
impl<'local, T: TypeArray> AsRef<JObject<'local>> for JPrimitiveArray<'local, T> {
fn as_ref(&self) -> &JObject<'local> {
&self.obj
}
}
impl<'local, T: TypeArray> ::std::ops::Deref for JPrimitiveArray<'local, T> {
type Target = JObject<'local>;
fn deref(&self) -> &Self::Target {
&self.obj
}
}
impl<'local, T: TypeArray> From<JPrimitiveArray<'local, T>> for JObject<'local> {
fn from(other: JPrimitiveArray<'local, T>) -> JObject {
other.obj
}
}
/// This conversion assumes that the `JObject` is a pointer to a class object.
impl<'local, T: TypeArray> From<JObject<'local>> for JPrimitiveArray<'local, T> {
fn from(other: JObject) -> Self {
unsafe { Self::from_raw(other.into_raw()) }
}
}
/// This conversion assumes that the `JObject` is a pointer to a class object.
impl<'local, 'obj_ref, T: TypeArray> From<&'obj_ref JObject<'local>>
for &'obj_ref JPrimitiveArray<'local, T>
{
fn from(other: &'obj_ref JObject<'local>) -> Self {
// Safety: `JPrimitiveArray` is `repr(transparent)` around `JObject`.
unsafe { &*(other as *const JObject<'local> as *const JPrimitiveArray<'local, T>) }
}
}
impl<'local, T: TypeArray> std::default::Default for JPrimitiveArray<'local, T> {
fn default() -> Self {
Self {
obj: JObject::null(),
lifetime: PhantomData,
}
}
}
impl<'local, T: TypeArray> JPrimitiveArray<'local, T> {
/// Creates a [`JPrimitiveArray`] that wraps the given `raw` [`jarray`]
///
/// # Safety
///
/// `raw` may be a null pointer. If `raw` is not a null pointer, then:
///
/// * `raw` must be a valid raw JNI local reference.
/// * There must not be any other `JObject` representing the same local reference.
/// * The lifetime `'local` must not outlive the local reference frame that the local reference
/// was created in.
pub unsafe fn from_raw(raw: jarray) -> Self {
Self {
obj: JObject::from_raw(raw as jobject),
lifetime: PhantomData,
}
}
/// Unwrap to the raw jni type.
pub fn into_raw(self) -> jarray {
self.obj.into_raw() as jarray
}
}
/// Lifetime'd representation of a [`crate::sys::jbooleanArray`] which wraps a [`JObject`] reference
pub type JBooleanArray<'local> = JPrimitiveArray<'local, crate::sys::jboolean>;
/// Lifetime'd representation of a [`crate::sys::jbyteArray`] which wraps a [`JObject`] reference
pub type JByteArray<'local> = JPrimitiveArray<'local, crate::sys::jbyte>;
/// Lifetime'd representation of a [`crate::sys::jcharArray`] which wraps a [`JObject`] reference
pub type JCharArray<'local> = JPrimitiveArray<'local, crate::sys::jchar>;
/// Lifetime'd representation of a [`crate::sys::jshortArray`] which wraps a [`JObject`] reference
pub type JShortArray<'local> = JPrimitiveArray<'local, crate::sys::jshort>;
/// Lifetime'd representation of a [`crate::sys::jintArray`] which wraps a [`JObject`] reference
pub type JIntArray<'local> = JPrimitiveArray<'local, crate::sys::jint>;
/// Lifetime'd representation of a [`crate::sys::jlongArray`] which wraps a [`JObject`] reference
pub type JLongArray<'local> = JPrimitiveArray<'local, crate::sys::jlong>;
/// Lifetime'd representation of a [`crate::sys::jfloatArray`] which wraps a [`JObject`] reference
pub type JFloatArray<'local> = JPrimitiveArray<'local, crate::sys::jfloat>;
/// Lifetime'd representation of a [`crate::sys::jdoubleArray`] which wraps a [`JObject`] reference
pub type JDoubleArray<'local> = JPrimitiveArray<'local, crate::sys::jdouble>;
/// Trait to access the raw `jarray` pointer for types that wrap an array reference
///
/// # Safety
///
/// Implementing this trait will allow a type to be passed to [`JNIEnv::get_array_length()`]
/// or other JNI APIs that only work with a valid reference to an array (or `null`)
///
pub unsafe trait AsJArrayRaw<'local>: AsRef<JObject<'local>> {
/// Returns the raw JNI pointer as a `jarray`
fn as_jarray_raw(&self) -> jarray {
self.as_ref().as_raw() as jarray
}
}
unsafe impl<'local, T: TypeArray> AsJArrayRaw<'local> for JPrimitiveArray<'local, T> {}

View File

@@ -0,0 +1,60 @@
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
}
}
impl AsRef<JStaticFieldID> for JStaticFieldID {
fn as_ref(&self) -> &JStaticFieldID {
self
}
}
impl AsMut<JStaticFieldID> for JStaticFieldID {
fn as_mut(&mut self) -> &mut JStaticFieldID {
self
}
}

View File

@@ -0,0 +1,60 @@
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
}
}
impl AsRef<JStaticMethodID> for JStaticMethodID {
fn as_ref(&self) -> &JStaticMethodID {
self
}
}
impl AsMut<JStaticMethodID> for JStaticMethodID {
fn as_mut(&mut self) -> &mut JStaticMethodID {
self
}
}

View File

@@ -0,0 +1,75 @@
use crate::{
objects::JObject,
sys::{jobject, jstring},
};
/// Lifetime'd representation of a `jstring`. Just a `JObject` wrapped in a new
/// class.
#[repr(transparent)]
pub struct JString<'local>(JObject<'local>);
impl<'local> AsRef<JString<'local>> for JString<'local> {
fn as_ref(&self) -> &JString<'local> {
self
}
}
impl<'local> AsRef<JObject<'local>> for JString<'local> {
fn as_ref(&self) -> &JObject<'local> {
self
}
}
impl<'local> ::std::ops::Deref for JString<'local> {
type Target = JObject<'local>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'local> From<JString<'local>> for JObject<'local> {
fn from(other: JString) -> JObject {
other.0
}
}
impl<'local> From<JObject<'local>> for JString<'local> {
fn from(other: JObject) -> Self {
unsafe { Self::from_raw(other.into_raw()) }
}
}
impl<'local, 'obj_ref> From<&'obj_ref JObject<'local>> for &'obj_ref JString<'local> {
fn from(other: &'obj_ref JObject<'local>) -> Self {
// Safety: `JString` is `repr(transparent)` around `JObject`.
unsafe { &*(other as *const JObject<'local> as *const JString<'local>) }
}
}
impl<'local> std::default::Default for JString<'local> {
fn default() -> Self {
Self(JObject::null())
}
}
impl<'local> JString<'local> {
/// Creates a [`JString`] that wraps the given `raw` [`jstring`]
///
/// # Safety
///
/// `raw` may be a null pointer. If `raw` is not a null pointer, then:
///
/// * `raw` must be a valid raw JNI local reference.
/// * There must not be any other `JObject` representing the same local reference.
/// * The lifetime `'local` must not outlive the local reference frame that the local reference
/// was created in.
pub unsafe fn from_raw(raw: 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
}
}

View File

@@ -0,0 +1,75 @@
use crate::{
objects::JObject,
sys::{jobject, jthrowable},
};
/// Lifetime'd representation of a `jthrowable`. Just a `JObject` wrapped in a
/// new class.
#[repr(transparent)]
pub struct JThrowable<'local>(JObject<'local>);
impl<'local> AsRef<JThrowable<'local>> for JThrowable<'local> {
fn as_ref(&self) -> &JThrowable<'local> {
self
}
}
impl<'local> AsRef<JObject<'local>> for JThrowable<'local> {
fn as_ref(&self) -> &JObject<'local> {
self
}
}
impl<'local> ::std::ops::Deref for JThrowable<'local> {
type Target = JObject<'local>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'local> From<JThrowable<'local>> for JObject<'local> {
fn from(other: JThrowable) -> JObject {
other.0
}
}
impl<'local> From<JObject<'local>> for JThrowable<'local> {
fn from(other: JObject) -> Self {
unsafe { Self::from_raw(other.into_raw()) }
}
}
impl<'local, 'obj_ref> From<&'obj_ref JObject<'local>> for &'obj_ref JThrowable<'local> {
fn from(other: &'obj_ref JObject<'local>) -> Self {
// Safety: `JThrowable` is `repr(transparent)` around `JObject`.
unsafe { &*(other as *const JObject<'local> as *const JThrowable<'local>) }
}
}
impl<'local> std::default::Default for JThrowable<'local> {
fn default() -> Self {
Self(JObject::null())
}
}
impl<'local> JThrowable<'local> {
/// Creates a [`JThrowable`] that wraps the given `raw` [`jthrowable`]
///
/// # Safety
///
/// `raw` may be a null pointer. If `raw` is not a null pointer, then:
///
/// * `raw` must be a valid raw JNI local reference.
/// * There must not be any other `JObject` representing the same local reference.
/// * The lifetime `'local` must not outlive the local reference frame that the local reference
/// was created in.
pub unsafe fn from_raw(raw: 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
}
}

417
vendor/jni/src/wrapper/objects/jvalue.rs vendored Normal file
View File

@@ -0,0 +1,417 @@
use std::convert::TryFrom;
use std::fmt::Debug;
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.
///
/// `JValueGen` is a generic type, meant to represent both owned and borrowed
/// JNI values. The type parameter `O` refers to what kind of object reference
/// the `JValueGen` can hold, which is either:
///
/// * an owned [`JObject`], used for values returned from a Java method call,
/// or
/// * a borrowed `&JObject`, used for parameters passed to a Java method call.
///
/// These two cases are represented by the type aliases [`JValueOwned`] and
/// [`JValue`], respectively.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub enum JValueGen<O> {
Object(O),
Byte(jbyte),
Char(jchar),
Short(jshort),
Int(jint),
Long(jlong),
Bool(jboolean),
Float(jfloat),
Double(jdouble),
Void,
}
/// An <dfn>owned</dfn> [`JValueGen`].
///
/// This type is used for values returned from Java method calls. If the Java
/// method returns an object reference, it will take the form of an owned
/// [`JObject`].
pub type JValueOwned<'local> = JValueGen<JObject<'local>>;
/// A <dfn>reference</dfn> [`JValueGen`].
///
/// This type is used for parameters passed to Java method calls. If the Java
/// method is to be passed an object reference, it takes the form of a borrowed
/// <code>&[JObject]</code>.
pub type JValue<'local, 'obj_ref> = JValueGen<&'obj_ref JObject<'local>>;
impl<O> JValueGen<O> {
/// Convert the enum to its jni-compatible equivalent.
pub fn as_jni<'local>(&self) -> jvalue
where
O: AsRef<JObject<'local>> + Debug,
{
let val: jvalue = match self {
JValueGen::Object(obj) => jvalue {
l: obj.as_ref().as_raw(),
},
JValueGen::Byte(byte) => jvalue { b: *byte },
JValueGen::Char(char) => jvalue { c: *char },
JValueGen::Short(short) => jvalue { s: *short },
JValueGen::Int(int) => jvalue { i: *int },
JValueGen::Long(long) => jvalue { j: *long },
JValueGen::Bool(boolean) => jvalue { b: *boolean as i8 },
JValueGen::Float(float) => jvalue { f: *float },
JValueGen::Double(double) => jvalue { d: *double },
JValueGen::Void => jvalue {
l: ::std::ptr::null_mut(),
},
};
trace!("converted {:?} to jvalue {:?}", self, unsafe {
::std::mem::transmute::<_, u64>(val)
});
val
}
/// Convert the enum to its jni-compatible equivalent.
#[deprecated = "Use `as_jni` instead."]
pub fn to_jni<'local>(self) -> jvalue
where
O: AsRef<JObject<'local>> + Debug,
{
self.as_jni()
}
/// Get the type name for the enum variant.
pub fn type_name(&self) -> &'static str {
match *self {
JValueGen::Void => "void",
JValueGen::Object(_) => "object",
JValueGen::Byte(_) => "byte",
JValueGen::Char(_) => "char",
JValueGen::Short(_) => "short",
JValueGen::Int(_) => "int",
JValueGen::Long(_) => "long",
JValueGen::Bool(_) => "bool",
JValueGen::Float(_) => "float",
JValueGen::Double(_) => "double",
}
}
/// 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<Primitive> {
Some(match *self {
JValueGen::Object(_) => return None,
JValueGen::Void => Primitive::Void,
JValueGen::Byte(_) => Primitive::Byte,
JValueGen::Char(_) => Primitive::Char,
JValueGen::Short(_) => Primitive::Short,
JValueGen::Int(_) => Primitive::Int,
JValueGen::Long(_) => Primitive::Long,
JValueGen::Bool(_) => Primitive::Boolean,
JValueGen::Float(_) => Primitive::Float,
JValueGen::Double(_) => Primitive::Double,
})
}
/// Try to unwrap to an Object.
pub fn l(self) -> Result<O> {
match self {
JValueGen::Object(obj) => Ok(obj),
_ => Err(Error::WrongJValueType("object", self.type_name())),
}
}
/// Try to unwrap to a boolean.
pub fn z(self) -> Result<bool> {
match self {
JValueGen::Bool(b) => Ok(b == JNI_TRUE),
_ => Err(Error::WrongJValueType("bool", self.type_name())),
}
}
/// Try to unwrap to a byte.
pub fn b(self) -> Result<jbyte> {
match self {
JValueGen::Byte(b) => Ok(b),
_ => Err(Error::WrongJValueType("jbyte", self.type_name())),
}
}
/// Try to unwrap to a char.
pub fn c(self) -> Result<jchar> {
match self {
JValueGen::Char(b) => Ok(b),
_ => Err(Error::WrongJValueType("jchar", self.type_name())),
}
}
/// Try to unwrap to a double.
pub fn d(self) -> Result<jdouble> {
match self {
JValueGen::Double(b) => Ok(b),
_ => Err(Error::WrongJValueType("jdouble", self.type_name())),
}
}
/// Try to unwrap to a float.
pub fn f(self) -> Result<jfloat> {
match self {
JValueGen::Float(b) => Ok(b),
_ => Err(Error::WrongJValueType("jfloat", self.type_name())),
}
}
/// Try to unwrap to an int.
pub fn i(self) -> Result<jint> {
match self {
JValueGen::Int(b) => Ok(b),
_ => Err(Error::WrongJValueType("jint", self.type_name())),
}
}
/// Try to unwrap to a long.
pub fn j(self) -> Result<jlong> {
match self {
JValueGen::Long(b) => Ok(b),
_ => Err(Error::WrongJValueType("jlong", self.type_name())),
}
}
/// Try to unwrap to a short.
pub fn s(self) -> Result<jshort> {
match self {
JValueGen::Short(b) => Ok(b),
_ => Err(Error::WrongJValueType("jshort", self.type_name())),
}
}
/// Try to unwrap to a void.
pub fn v(self) -> Result<()> {
match self {
JValueGen::Void => Ok(()),
_ => Err(Error::WrongJValueType("void", self.type_name())),
}
}
/// Copies or borrows the value in this `JValue`.
///
/// If the value is a primitive type, it is copied. If the value is an
/// object reference, it is borrowed.
pub fn borrow(&self) -> JValueGen<&O> {
match self {
JValueGen::Object(o) => JValueGen::Object(o),
JValueGen::Byte(v) => JValueGen::Byte(*v),
JValueGen::Char(v) => JValueGen::Char(*v),
JValueGen::Short(v) => JValueGen::Short(*v),
JValueGen::Int(v) => JValueGen::Int(*v),
JValueGen::Long(v) => JValueGen::Long(*v),
JValueGen::Bool(v) => JValueGen::Bool(*v),
JValueGen::Float(v) => JValueGen::Float(*v),
JValueGen::Double(v) => JValueGen::Double(*v),
JValueGen::Void => JValueGen::Void,
}
}
}
impl<'obj_ref, O> From<&'obj_ref JValueGen<O>> for JValueGen<&'obj_ref O> {
fn from(other: &'obj_ref JValueGen<O>) -> Self {
other.borrow()
}
}
impl<'local, T: Into<JObject<'local>>> From<T> for JValueOwned<'local> {
fn from(other: T) -> Self {
Self::Object(other.into())
}
}
impl<'local: 'obj_ref, 'obj_ref, T: AsRef<JObject<'local>>> From<&'obj_ref T>
for JValue<'local, 'obj_ref>
{
fn from(other: &'obj_ref T) -> Self {
Self::Object(other.as_ref())
}
}
impl<'local> TryFrom<JValueOwned<'local>> for JObject<'local> {
type Error = Error;
fn try_from(value: JValueOwned<'local>) -> Result<Self> {
match value {
JValueGen::Object(o) => Ok(o),
_ => Err(Error::WrongJValueType("object", value.type_name())),
}
}
}
impl<O> From<bool> for JValueGen<O> {
fn from(other: bool) -> Self {
JValueGen::Bool(if other { JNI_TRUE } else { JNI_FALSE })
}
}
// jbool
impl<O> From<jboolean> for JValueGen<O> {
fn from(other: jboolean) -> Self {
JValueGen::Bool(other)
}
}
impl<O> TryFrom<JValueGen<O>> for jboolean {
type Error = Error;
fn try_from(value: JValueGen<O>) -> Result<Self> {
match value {
JValueGen::Bool(b) => Ok(b),
_ => Err(Error::WrongJValueType("bool", value.type_name())),
}
}
}
// jchar
impl<O> From<jchar> for JValueGen<O> {
fn from(other: jchar) -> Self {
JValueGen::Char(other)
}
}
impl<O> TryFrom<JValueGen<O>> for jchar {
type Error = Error;
fn try_from(value: JValueGen<O>) -> Result<Self> {
match value {
JValueGen::Char(c) => Ok(c),
_ => Err(Error::WrongJValueType("char", value.type_name())),
}
}
}
// jshort
impl<O> From<jshort> for JValueGen<O> {
fn from(other: jshort) -> Self {
JValueGen::Short(other)
}
}
impl<O> TryFrom<JValueGen<O>> for jshort {
type Error = Error;
fn try_from(value: JValueGen<O>) -> Result<Self> {
match value {
JValueGen::Short(s) => Ok(s),
_ => Err(Error::WrongJValueType("short", value.type_name())),
}
}
}
// jfloat
impl<O> From<jfloat> for JValueGen<O> {
fn from(other: jfloat) -> Self {
JValueGen::Float(other)
}
}
impl<O> TryFrom<JValueGen<O>> for jfloat {
type Error = Error;
fn try_from(value: JValueGen<O>) -> Result<Self> {
match value {
JValueGen::Float(f) => Ok(f),
_ => Err(Error::WrongJValueType("float", value.type_name())),
}
}
}
// jdouble
impl<O> From<jdouble> for JValueGen<O> {
fn from(other: jdouble) -> Self {
JValueGen::Double(other)
}
}
impl<O> TryFrom<JValueGen<O>> for jdouble {
type Error = Error;
fn try_from(value: JValueGen<O>) -> Result<Self> {
match value {
JValueGen::Double(d) => Ok(d),
_ => Err(Error::WrongJValueType("double", value.type_name())),
}
}
}
// jint
impl<O> From<jint> for JValueGen<O> {
fn from(other: jint) -> Self {
JValueGen::Int(other)
}
}
impl<O> TryFrom<JValueGen<O>> for jint {
type Error = Error;
fn try_from(value: JValueGen<O>) -> Result<Self> {
match value {
JValueGen::Int(i) => Ok(i),
_ => Err(Error::WrongJValueType("int", value.type_name())),
}
}
}
// jlong
impl<O> From<jlong> for JValueGen<O> {
fn from(other: jlong) -> Self {
JValueGen::Long(other)
}
}
impl<O> TryFrom<JValueGen<O>> for jlong {
type Error = Error;
fn try_from(value: JValueGen<O>) -> Result<Self> {
match value {
JValueGen::Long(l) => Ok(l),
_ => Err(Error::WrongJValueType("long", value.type_name())),
}
}
}
// jbyte
impl<O> From<jbyte> for JValueGen<O> {
fn from(other: jbyte) -> Self {
JValueGen::Byte(other)
}
}
impl<O> TryFrom<JValueGen<O>> for jbyte {
type Error = Error;
fn try_from(value: JValueGen<O>) -> Result<Self> {
match value {
JValueGen::Byte(b) => Ok(b),
_ => Err(Error::WrongJValueType("byte", value.type_name())),
}
}
}
// jvoid
impl<O> From<()> for JValueGen<O> {
fn from(_: ()) -> Self {
JValueGen::Void
}
}
impl<O> TryFrom<JValueGen<O>> for () {
type Error = Error;
fn try_from(value: JValueGen<O>) -> Result<Self> {
match value {
JValueGen::Void => Ok(()),
_ => Err(Error::WrongJValueType("void", value.type_name())),
}
}
}

66
vendor/jni/src/wrapper/objects/mod.rs vendored Normal file
View File

@@ -0,0 +1,66 @@
// 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::*;
mod weak_ref;
pub use self::weak_ref::*;
// For automatic local ref deletion
mod auto_local;
pub use self::auto_local::*;
mod release_mode;
pub use self::release_mode::*;
/// Primitive Array types
mod jobject_array;
pub use self::jobject_array::*;
/// Primitive Array types
mod jprimitive_array;
pub use self::jprimitive_array::*;
// For automatic pointer-based generic array release
mod auto_elements;
pub use self::auto_elements::*;
// For automatic pointer-based primitive array release
mod auto_elements_critical;
pub use self::auto_elements_critical::*;

View File

@@ -0,0 +1,18 @@
use crate::sys::JNI_ABORT;
#[cfg(doc)]
use super::{AutoElements, AutoElementsCritical};
/// ReleaseMode
///
/// This defines the release mode of [`AutoElements`] (and [`AutoElementsCritical`]) 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,
}

View File

@@ -0,0 +1,176 @@
use std::sync::Arc;
use log::{debug, warn};
use crate::{
errors::Result,
objects::{GlobalRef, JObject},
sys, JNIEnv, JavaVM,
};
// Note: `WeakRef` must not implement `Into<JObject>`! If it did, then it would be possible to
// wrap it in `AutoLocal`, which would cause undefined behavior upon drop as a result of calling
// the wrong JNI function to delete the reference.
/// A *weak* global JVM reference. These are global in scope like
/// [`GlobalRef`], and may outlive the `JNIEnv` they came from, but are
/// *not* guaranteed to not get collected until released.
///
/// `WeakRef` can be cloned to use _the same_ weak reference in different
/// contexts. If you want to create yet another weak ref to the same java object, call
/// [`WeakRef::clone_in_jvm`].
///
/// Underlying weak reference will be dropped, when the last instance
/// of `WeakRef` leaves its scope.
///
/// It is _recommended_ that a native thread that drops the weak reference is attached
/// to the Java thread (i.e., has an instance of `JNIEnv`). If the native thread is *not* attached,
/// the `WeakRef#drop` will print a warning and implicitly `attach` and `detach` it, which
/// significantly affects performance.
#[derive(Clone)]
pub struct WeakRef {
inner: Arc<WeakRefGuard>,
}
struct WeakRefGuard {
raw: sys::jweak,
vm: JavaVM,
}
unsafe impl Send for WeakRef {}
unsafe impl Sync for WeakRef {}
impl WeakRef {
/// Creates a new wrapper for a global reference.
///
/// # Safety
///
/// Expects a valid raw weak global reference that should be created with `NewWeakGlobalRef`
/// JNI function.
pub(crate) unsafe fn from_raw(vm: JavaVM, raw: sys::jweak) -> Self {
WeakRef {
inner: Arc::new(WeakRefGuard { raw, vm }),
}
}
/// Returns the raw JNI weak reference.
pub fn as_raw(&self) -> sys::jweak {
self.inner.raw
}
/// Creates a new local reference to this object.
///
/// This object may have already been garbage collected by the time this method is called. If
/// so, this method returns `Ok(None)`. Otherwise, it returns `Ok(Some(r))` where `r` is the
/// new local reference.
///
/// If this method returns `Ok(Some(r))`, it is guaranteed that the object will not be garbage
/// collected at least until `r` is deleted or becomes invalid.
pub fn upgrade_local<'local>(&self, env: &JNIEnv<'local>) -> Result<Option<JObject<'local>>> {
let r = env.new_local_ref(unsafe { JObject::from_raw(self.as_raw()) })?;
// Per JNI spec, `NewLocalRef` will return a null pointer if the object was GC'd.
if r.is_null() {
Ok(None)
} else {
Ok(Some(r))
}
}
/// Creates a new strong global reference to this object.
///
/// This object may have already been garbage collected by the time this method is called. If
/// so, this method returns `Ok(None)`. Otherwise, it returns `Ok(Some(r))` where `r` is the
/// new strong global reference.
///
/// If this method returns `Ok(Some(r))`, it is guaranteed that the object will not be garbage
/// collected at least until `r` is dropped.
pub fn upgrade_global(&self, env: &JNIEnv) -> Result<Option<GlobalRef>> {
let r = env.new_global_ref(unsafe { JObject::from_raw(self.as_raw()) })?;
// Unlike `NewLocalRef`, the JNI spec does *not* guarantee that `NewGlobalRef` will return a
// null pointer if the object was GC'd, so we'll have to check.
if env.is_same_object(&r, JObject::null())? {
Ok(None)
} else {
Ok(Some(r))
}
}
/// Checks if the object referred to by this `WeakRef` has been garbage collected.
///
/// Note that garbage collection can happen at any moment, so a return of `Ok(true)` from this
/// method does not guarantee that [`WeakRef::upgrade_local`] or [`WeakRef::upgrade_global`]
/// will succeed.
///
/// This is equivalent to
/// <code>self.[is_same_object][WeakRef::is_same_object](env, [JObject::null]\())</code>.
pub fn is_garbage_collected(&self, env: &JNIEnv) -> Result<bool> {
self.is_same_object(env, JObject::null())
}
// The following methods are wrappers around those `JNIEnv` methods that make sense for a weak
// reference. These methods exist because they use `JObject::from_raw` on the raw pointer of a
// weak reference. Although this usage is sound, it is `unsafe`. It's also confusing because
// `JObject` normally represents a strong reference.
/// Returns true if this weak reference refers to the given object. Otherwise returns false.
///
/// If `object` is [null][JObject::null], then this method is equivalent to
/// [`WeakRef::is_garbage_collected`]: it returns true if the object referred to by this
/// `WeakRef` has been garbage collected, or false if the object has not yet been garbage
/// collected.
pub fn is_same_object<'local, O>(&self, env: &JNIEnv<'local>, object: O) -> Result<bool>
where
O: AsRef<JObject<'local>>,
{
env.is_same_object(unsafe { JObject::from_raw(self.as_raw()) }, object)
}
/// Returns true if this weak reference refers to the same object as another weak reference.
/// Otherwise returns false.
///
/// This method will also return true if both weak references refer to an object that has been
/// garbage collected.
pub fn is_weak_ref_to_same_object(&self, env: &JNIEnv, other: &WeakRef) -> Result<bool> {
self.is_same_object(env, unsafe { JObject::from_raw(other.as_raw()) })
}
/// Creates a new weak reference to the same object that this one refers to.
///
/// `WeakRef` implements [`Clone`], which should normally be used whenever a new `WeakRef` to
/// the same object is needed. However, that only increments an internal reference count and
/// does not actually create a new weak reference in the JVM. If you specifically need to have
/// the JVM create a new weak reference, use this method instead of `Clone`.
///
/// This method returns `Ok(None)` if the object has already been garbage collected.
pub fn clone_in_jvm(&self, env: &JNIEnv) -> Result<Option<WeakRef>> {
env.new_weak_ref(unsafe { JObject::from_raw(self.as_raw()) })
}
}
impl Drop for WeakRefGuard {
fn drop(&mut self) {
fn drop_impl(env: &JNIEnv, raw: sys::jweak) -> Result<()> {
let internal = env.get_native_interface();
// This method is safe to call in case of pending exceptions (see chapter 2 of the spec)
jni_unchecked!(internal, DeleteWeakGlobalRef, raw);
Ok(())
}
let res = match self.vm.get_env() {
Ok(env) => drop_impl(&env, self.raw),
Err(_) => {
warn!("Dropping a WeakRef in a detached thread. Fix your code if this message appears frequently (see the WeakRef docs).");
self.vm
.attach_current_thread()
.and_then(|env| drop_impl(&env, self.raw))
}
};
if let Err(err) = res {
debug!("error dropping weak ref: {:#?}", err);
}
}
}

274
vendor/jni/src/wrapper/signature.rs vendored Normal file
View File

@@ -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<JavaType>),
Method(Box<TypeSignature>),
}
impl FromStr for JavaType {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
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<Self, Self::Err> {
parser(parse_return)
.parse(s)
.map(|res| res.0)
.map_err(|e| Error::ParseFailed(e, s.to_owned()))
}
}
impl fmt::Display for ReturnType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ReturnType::Primitive(ref ty) => ty.fmt(f),
ReturnType::Object => write!(f, "L;"),
ReturnType::Array => write!(f, "["),
}
}
}
/// A method type signature. This is the structure representation of something
/// like `(Ljava/lang/String;)Z`. Used by the `call_(object|static)_method`
/// functions on jnienv to ensure safety.
#[allow(missing_docs)]
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct TypeSignature {
pub args: Vec<JavaType>,
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: AsRef<str>>(s: S) -> Result<TypeSignature> {
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<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Primitive, S>
where
S::Error: ParseError<char, S::Range, S::Position>,
{
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<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
where
S::Error: ParseError<char, S::Range, S::Position>,
{
let marker = token('[');
(marker, parser(parse_type))
.map(|(_, ty)| JavaType::Array(Box::new(ty)))
.parse_stream(input)
.into()
}
fn parse_object<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
where
S::Error: ParseError<char, S::Range, S::Position>,
{
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<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
where
S::Error: ParseError<char, S::Range, S::Position>,
{
parser(parse_primitive)
.map(JavaType::Primitive)
.or(parser(parse_array))
.or(parser(parse_object))
.or(parser(parse_sig))
.parse_stream(input)
.into()
}
fn parse_return<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<ReturnType, S>
where
S::Error: ParseError<char, S::Range, S::Position>,
{
parser(parse_primitive)
.map(ReturnType::Primitive)
.or(parser(parse_array).map(|_| ReturnType::Array))
.or(parser(parse_object).map(|_| ReturnType::Object))
.parse_stream(input)
.into()
}
fn parse_args<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Vec<JavaType>, S>
where
S::Error: ParseError<char, S::Range, S::Position>,
{
between(token('('), token(')'), many(parser(parse_type)))
.parse_stream(input)
.into()
}
fn parse_sig<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
where
S::Error: ParseError<char, S::Range, S::Position>,
{
(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"));
}
}
}
}

View File

@@ -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<T> From<T> for JNIString
where
T: AsRef<str>,
{
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<'str_ref> From<&'str_ref JNIStr> for Cow<'str_ref, str> {
fn from(other: &'str_ref JNIStr) -> Cow<'str_ref, str> {
let bytes = other.to_bytes();
match from_java_cesu8(bytes) {
Ok(s) => s,
Err(e) => {
debug!("error decoding java cesu8: {:#?}", e);
String::from_utf8_lossy(bytes)
}
}
}
}
impl From<JNIString> 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<'jni_str>(ptr: *const c_char) -> &'jni_str JNIStr {
&*(ffi::CStr::from_ptr(ptr) as *const ffi::CStr as *const ffi_str::JNIStr)
}
}
// impls for CoW
impl Borrow<JNIStr> 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()),
}
}
}
}

View File

@@ -0,0 +1,205 @@
use jni_sys::{jboolean, JNI_TRUE};
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<'local, 'other_local: 'obj_ref, 'obj_ref> {
internal: *const c_char,
obj: &'obj_ref JString<'other_local>,
env: JNIEnv<'local>,
}
impl<'local, 'other_local: 'obj_ref, 'obj_ref> JavaStr<'local, 'other_local, 'obj_ref> {
/// Get a pointer to the character array beneath a [JString]
///
/// The string will be `NULL` terminated and encoded as
/// [Modified UTF-8](https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8) /
/// [CESU-8](https://en.wikipedia.org/wiki/CESU-8).
///
/// The implementation may either create a copy of the character array for
/// the given `String` or it may pin it to avoid it being collected by the
/// garbage collector.
///
/// Returns a tuple with the pointer and the status of whether the implementation
/// created a copy of the underlying character array.
///
/// # Warning
///
/// The caller must release the array when they are done with it via
/// [Self::release_string_utf_chars]
///
/// # Safety
///
/// The caller must guarantee that the Object passed in is an instance of `java.lang.String`,
/// passing in anything else will lead to undefined behaviour (The JNI implementation
/// is likely to crash or abort the process).
unsafe fn get_string_utf_chars(
env: &JNIEnv<'_>,
obj: &JString<'_>,
) -> Result<(*const c_char, bool)> {
non_null!(obj, "get_string_utf_chars obj argument");
let mut is_copy: jboolean = 0;
let ptr: *const c_char = jni_non_null_call!(
env.get_raw(),
GetStringUTFChars,
obj.as_raw(),
&mut is_copy as *mut _
);
let is_copy = is_copy == JNI_TRUE;
Ok((ptr, is_copy))
}
/// Release the backing string
///
/// This will either free the copy that was made by `GetStringUTFChars` or unpin it so it
/// may be released by the garbage collector once there are no further references to the string.
///
/// # Safety
///
/// The caller must guarantee that [Self::internal] was constructed from a valid pointer obtained from [Self::get_string_utf_chars]
unsafe fn release_string_utf_chars(&mut self) -> Result<()> {
non_null!(self.obj, "release_string_utf_chars obj argument");
// This method is safe to call in case of pending exceptions (see the chapter 2 of the spec)
jni_unchecked!(
self.env.get_raw(),
ReleaseStringUTFChars,
self.obj.as_raw(),
self.internal
);
Ok(())
}
/// Get a [JavaStr] from a [JNIEnv] and a [JString].
/// You probably want [JNIEnv::get_string] instead of this method.
pub fn from_env(env: &JNIEnv<'local>, obj: &'obj_ref JString<'other_local>) -> Result<Self> {
Ok(unsafe {
let (ptr, _) = Self::get_string_utf_chars(env, obj)?;
Self::from_raw(env, obj, ptr)
})
}
/// Get the raw string pointer from the JavaStr.
///
/// The string will be `NULL` terminated and encoded as
/// [Modified UTF-8](https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8) /
/// [CESU-8](https://en.wikipedia.org/wiki/CESU-8).
pub fn get_raw(&self) -> *const c_char {
self.internal
}
/// Consumes the `JavaStr`, returning the raw string pointer
///
/// The string will be `NULL` terminated and encoded as
/// [Modified UTF-8](https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8) /
/// [CESU-8](https://en.wikipedia.org/wiki/CESU-8).
///
/// # Warning
/// The programmer is responsible for making sure the backing string gets
/// released when they are done with it, for example by reconstructing a
/// [JavaStr] with [`Self::from_raw`], which will release the backing string
/// when it is dropped.
pub fn into_raw(self) -> *const c_char {
let mut _dont_call_drop = std::mem::ManuallyDrop::new(self);
// Drop the `JNIEnv` in place. As of this writing, that's a no-op, but if `JNIEnv`
// gains any drop code in the future, this will run it.
//
// Safety: The `&mut` proves that `self.env` is valid and not aliased. It is not
// accessed again after this point. Because `self` has been moved into `ManuallyDrop`,
// the `JNIEnv` will not be dropped twice.
unsafe {
std::ptr::drop_in_place(&mut _dont_call_drop.env);
}
_dont_call_drop.internal
}
/// Get a [JavaStr] from it's raw components
///
/// # Safety
///
/// The caller must guarantee that `ptr` is a valid, non-null pointer returned by [`Self::into_raw`],
/// and that `obj` is the same `String` object originally used to create the [JavaStr]
///
/// # Example
/// ```rust,no_run
/// # use jni::{errors::Result, JNIEnv, strings::JavaStr};
/// #
/// # fn example(env: &mut JNIEnv) -> Result<()> {
/// let jstring = env.new_string("foo")?;
/// let java_str = env.get_string(&jstring)?;
///
/// let ptr = java_str.into_raw();
/// // Do whatever you need with the pointer
/// let java_str = unsafe { JavaStr::from_raw(env, &jstring, ptr) };
/// # Ok(())
/// # }
/// ```
pub unsafe fn from_raw(
env: &JNIEnv<'local>,
obj: &'obj_ref JString<'other_local>,
ptr: *const c_char,
) -> Self {
Self {
internal: ptr,
obj,
// Safety: The cloned `JNIEnv` will not be used to create any local references, only to
// release `ptr`.
env: env.unsafe_clone(),
}
}
}
impl<'local, 'other_local: 'obj_ref, 'obj_ref> ::std::ops::Deref
for JavaStr<'local, 'other_local, 'obj_ref>
{
type Target = JNIStr;
fn deref(&self) -> &Self::Target {
self.into()
}
}
impl<'local, 'other_local: 'obj_ref, 'obj_ref: 'java_str, 'java_str>
From<&'java_str JavaStr<'local, 'other_local, 'obj_ref>> for &'java_str JNIStr
{
fn from(other: &'java_str JavaStr) -> &'java_str JNIStr {
unsafe { JNIStr::from_ptr(other.internal) }
}
}
impl<'local, 'other_local: 'obj_ref, 'obj_ref: 'java_str, 'java_str>
From<&'java_str JavaStr<'local, 'other_local, 'obj_ref>> for Cow<'java_str, str>
{
fn from(other: &'java_str JavaStr) -> Cow<'java_str, str> {
let jni_str: &JNIStr = other;
jni_str.into()
}
}
impl<'local, 'other_local: 'obj_ref, 'obj_ref> From<JavaStr<'local, 'other_local, 'obj_ref>>
for String
{
fn from(other: JavaStr) -> String {
let cow: Cow<str> = (&other).into();
cow.into_owned()
}
}
impl<'local, 'other_local: 'obj_ref, 'obj_ref> Drop for JavaStr<'local, 'other_local, 'obj_ref> {
fn drop(&mut self) {
match unsafe { self.release_string_utf_chars() } {
Ok(()) => {}
Err(e) => warn!("error dropping java str: {}", e),
}
}
}

6
vendor/jni/src/wrapper/strings/mod.rs vendored Normal file
View File

@@ -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::*;

43
vendor/jni/src/wrapper/version.rs vendored Normal file
View File

@@ -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<i32> 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<JNIVersion> 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,
}
}
}

24
vendor/jni/test_profile vendored Normal file
View File

@@ -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

211
vendor/jni/tests/executor.rs vendored Normal file
View File

@@ -0,0 +1,211 @@
#![cfg(feature = "invocation")]
use std::{
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Barrier,
},
thread::spawn,
time::Duration,
};
use jni::{objects::AutoLocal, sys::jint, Executor};
use rusty_fork::rusty_fork_test;
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());
}
// We need to test `JavaVM::destroy()` in a separate process otherwise it will break
// all the other tests
rusty_fork_test! {
#[test]
fn test_destroy() {
const THREAD_NUM: usize = 2;
const DAEMON_THREAD_NUM: usize = 2;
static MATH_CLASS: &str = "java/lang/Math";
// We don't test this using an `Executor` because we don't want to
// attach all the threads as daemon threads.
let jvm = jvm().clone();
let atomic = Arc::new(AtomicUsize::new(0));
let attach_barrier = Arc::new(Barrier::new(THREAD_NUM + DAEMON_THREAD_NUM + 1));
let daemons_detached_barrier = Arc::new(Barrier::new(DAEMON_THREAD_NUM + 1));
let mut threads = Vec::new();
for _ in 0..THREAD_NUM {
let attach_barrier = Arc::clone(&attach_barrier);
let jvm = jvm.clone();
let atomic = atomic.clone();
let jh = spawn(move || {
let mut env = jvm.attach_current_thread().unwrap();
println!("java thread attach");
attach_barrier.wait();
println!("java thread run");
std::thread::sleep(Duration::from_millis(250));
println!("use before destroy...");
// Make some token JNI call
let _class = AutoLocal::new(env.find_class(MATH_CLASS).unwrap(), &env);
atomic.fetch_add(1, Ordering::SeqCst);
println!("java thread finished");
});
threads.push(jh);
}
for _ in 0..DAEMON_THREAD_NUM {
let attach_barrier = Arc::clone(&attach_barrier);
let daemons_detached_barrier = Arc::clone(&daemons_detached_barrier);
let jvm = jvm.clone();
let atomic = atomic.clone();
let jh = spawn(move || {
// We have to be _very_ careful to ensure we have finished accessing the
// JavaVM before it gets destroyed, including dropping the AutoLocal
// for the `MATH_CLASS`
{
let mut env = jvm.attach_current_thread_as_daemon().unwrap();
println!("daemon thread attach");
attach_barrier.wait();
println!("daemon thread run");
println!("daemon JVM use before destroy...");
let _class = AutoLocal::new(env.find_class(MATH_CLASS).unwrap(), &env);
}
// For it to be safe to call `JavaVM::destroy()` we need to ensure that
// daemon threads are detached from the JavaVM ahead of time because
// `JavaVM::destroy()` does not synchronize and wait for them to exit
// which means we would effectively trigger a use-after-free when daemon
// threads exit and they try to automatically detach from the `JavaVM`
//
// # Safety
// We won't be accessing any (invalid) `JNIEnv` once we have detached this
// thread
unsafe {
jvm.detach_current_thread();
}
daemons_detached_barrier.wait();
for _ in 0..10 {
std::thread::sleep(Duration::from_millis(100));
println!("daemon thread running");
}
atomic.fetch_add(1, Ordering::SeqCst);
println!("daemon thread finished");
});
threads.push(jh);
}
// At this point we at least know that all threads have been attached
// to the JVM
println!("MAIN: waiting for threads attached barrier");
attach_barrier.wait();
// Before we try and destroy the JavaVM we need to be sure that the daemon
// threads have finished using the VM since `jvm.destroy()` won't wait
// for daemon threads to exit.
println!("MAIN: waiting for daemon threads detached barrier");
daemons_detached_barrier.wait();
// # Safety
//
// We drop the `jvm` variable immediately after `destroy()` returns to avoid
// any use-after-free.
unsafe {
println!("MAIN: calling DestroyJavaVM()...");
jvm.destroy().unwrap();
drop(jvm);
println!("MAIN: jvm destroyed");
}
println!("MAIN: joining (waiting for) all threads");
let mut joined = 0;
for jh in threads {
jh.join().unwrap();
joined += 1;
println!(
"joined {joined} threads, atomic = {}",
atomic.load(Ordering::SeqCst)
);
}
assert_eq!(
atomic.load(Ordering::SeqCst),
THREAD_NUM + DAEMON_THREAD_NUM
);
}
}

View File

@@ -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<JavaVM>, executor: Executor) {
check_detached(vm);
executor
.with_attached::<_, _, Error>(|_| {
check_attached(vm);
executor.with_attached::<_, _, Error>(|_| {
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")
}

View File

@@ -0,0 +1,43 @@
// This is a separate test program because it has to start a JVM with a specific option.
#![cfg(feature = "invocation")]
use jni::{objects::JString, InitArgsBuilder, JavaVM};
#[test]
fn invocation_character_encoding() {
let jvm_args = InitArgsBuilder::new()
.version(jni::JNIVersion::V8)
.option("-Xcheck:jni")
// U+00A0 NO-BREAK SPACE is the only non-ASCII character that's present in all parts of
// ISO 8859. This minimizes the chance of this test failing as a result of the character
// not being present in the platform default character encoding. This test will still fail
// on platforms where the default character encoding cannot represent a no-break space,
// such as GBK.
.option("-Dnbsp=\u{00a0}")
.build()
.unwrap_or_else(|e| panic!("{:#?}", e));
let jvm = JavaVM::new(jvm_args).unwrap_or_else(|e| panic!("{:#?}", e));
let mut env = jvm.attach_current_thread().unwrap();
let prop_name = env.new_string("nbsp").unwrap();
let prop_value: JString = env
.call_static_method(
"java/lang/System",
"getProperty",
"(Ljava/lang/String;)Ljava/lang/String;",
&[(&prop_name).into()],
)
.unwrap()
.l()
.unwrap()
.into();
let prop_value_str = env.get_string(&prop_value).unwrap();
let prop_value_str: &str = prop_value_str.to_str().unwrap();
assert_eq!("\u{00a0}", prop_value_str);
}

43
vendor/jni/tests/java_integers.rs vendored Normal file
View File

@@ -0,0 +1,43 @@
#![cfg(feature = "invocation")]
use jni::{errors::Error, objects::JValue};
mod util;
use util::{attach_current_thread, print_exception};
#[test]
fn test_java_integers() {
let mut env = attach_current_thread();
let array_length = 50;
for value in -10..10 {
env.with_local_frame(16, |env| -> Result<_, Error> {
let integer_value =
env.new_object("java/lang/Integer", "(I)V", &[JValue::Int(value)])?;
let values_array =
env.new_object_array(array_length, "java/lang/Integer", &integer_value)?;
let 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(())
})
.unwrap_or_else(|e| {
print_exception(&env);
panic!("{:#?}", e);
})
}
}

43
vendor/jni/tests/jmap.rs vendored Normal file
View File

@@ -0,0 +1,43 @@
#![cfg(feature = "invocation")]
use jni::objects::{JMap, JObject, JString};
mod util;
use util::{attach_current_thread, unwrap};
#[test]
pub fn jmap_push_and_iterate() {
let mut env = attach_current_thread();
let data = &["hello", "world", "from", "test"];
// Create a new map. Use LinkedHashMap to have predictable iteration order
let map_object = unwrap(env.new_object("java/util/LinkedHashMap", "()V", &[]), &env);
let map = unwrap(JMap::from_env(&mut env, &map_object), &env);
// Push all strings
unwrap(
data.iter().try_for_each(|s| {
env.new_string(s)
.map(JObject::from)
.and_then(|s| map.put(&mut env, &s, &s).map(|_| ()))
}),
&env,
);
// Collect the keys using the JMap iterator
let mut collected = Vec::new();
unwrap(
map.iter(&mut env).and_then(|mut iter| {
while let Some(e) = iter.next(&mut env)? {
let s = JString::from(e.0);
let s = env.get_string(&s)?;
collected.push(String::from(s));
}
Ok(())
}),
&env,
);
let orig = data.to_vec();
assert_eq!(orig, collected);
}

1191
vendor/jni/tests/jni_api.rs vendored Normal file

File diff suppressed because it is too large Load Diff

80
vendor/jni/tests/jni_global_refs.rs vendored Normal file
View File

@@ -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 mut env = attach_current_thread();
let mut join_handlers = Vec::new();
let atomic_integer = {
let local_ref = AutoLocal::new(
unwrap(
env.new_object(
"java/util/concurrent/atomic/AtomicInteger",
"(I)V",
&[JValue::from(0)],
),
&env,
),
&env,
);
unwrap(env.new_global_ref(&local_ref), &env)
};
// Test with a different number of threads (from 2 to 8)
for thread_num in 2..9 {
let barrier = Arc::new(Barrier::new(thread_num));
for _ in 0..thread_num {
let barrier = barrier.clone();
let atomic_integer = atomic_integer.clone();
let jh = spawn(move || {
let mut env = attach_current_thread();
barrier.wait();
for _ in 0..ITERS_PER_THREAD {
unwrap(
unwrap(
env.call_method(&atomic_integer, "incrementAndGet", "()I", &[]),
&env,
)
.i(),
&env,
);
}
});
join_handlers.push(jh);
}
for jh in join_handlers.drain(..) {
jh.join().unwrap();
}
let expected = (ITERS_PER_THREAD * thread_num) as jint;
assert_eq!(
expected,
unwrap(
unwrap(
env.call_method(&atomic_integer, "getAndSet", "(I)I", &[JValue::from(0)]),
&env,
)
.i(),
&env,
)
);
}
}

195
vendor/jni/tests/jni_weak_refs.rs vendored Normal file
View File

@@ -0,0 +1,195 @@
#![cfg(feature = "invocation")]
use std::{
sync::{Arc, Barrier},
thread::spawn,
};
use jni::{
objects::{AutoLocal, JValue},
sys::jint,
JNIEnv,
};
mod util;
use util::{attach_current_thread, unwrap};
#[test]
pub fn weak_ref_works_in_other_threads() {
const ITERS_PER_THREAD: usize = 10_000;
let mut env = attach_current_thread();
let mut join_handlers = Vec::new();
let atomic_integer_local = AutoLocal::new(
unwrap(
env.new_object(
"java/util/concurrent/atomic/AtomicInteger",
"(I)V",
&[JValue::from(0)],
),
&env,
),
&env,
);
let atomic_integer =
unwrap(env.new_weak_ref(&atomic_integer_local), &env).expect("weak ref should not be null");
// Test with a different number of threads (from 2 to 8)
for thread_num in 2..9 {
let barrier = Arc::new(Barrier::new(thread_num));
for _ in 0..thread_num {
let barrier = barrier.clone();
let atomic_integer = atomic_integer.clone();
let jh = spawn(move || {
let mut env = attach_current_thread();
barrier.wait();
for _ in 0..ITERS_PER_THREAD {
let atomic_integer = env.auto_local(
unwrap(atomic_integer.upgrade_local(&env), &env)
.expect("AtomicInteger shouldn't have been GC'd yet"),
);
unwrap(
unwrap(
env.call_method(&atomic_integer, "incrementAndGet", "()I", &[]),
&env,
)
.i(),
&env,
);
}
});
join_handlers.push(jh);
}
for jh in join_handlers.drain(..) {
jh.join().unwrap();
}
let expected = (ITERS_PER_THREAD * thread_num) as jint;
assert_eq!(
expected,
unwrap(
unwrap(
env.call_method(
&atomic_integer_local,
"getAndSet",
"(I)I",
&[JValue::from(0)]
),
&env,
)
.i(),
&env,
)
);
}
}
#[test]
fn weak_ref_is_actually_weak() {
let mut env = attach_current_thread();
// This test uses `with_local_frame` to work around issue #109.
fn run_gc(env: &mut JNIEnv) {
unwrap(
env.with_local_frame(1, |env| {
env.call_static_method("java/lang/System", "gc", "()V", &[])?;
Ok(())
}),
env,
);
}
for _ in 0..100 {
let obj_local = unwrap(
env.with_local_frame_returning_local(2, |env| {
env.new_object("java/lang/Object", "()V", &[])
}),
&env,
);
let obj_local = env.auto_local(obj_local);
let obj_weak =
unwrap(env.new_weak_ref(&obj_local), &env).expect("weak ref should not be null");
let obj_weak2 =
unwrap(obj_weak.clone_in_jvm(&env), &env).expect("weak ref should not be null");
run_gc(&mut env);
for obj_weak in &[&obj_weak, &obj_weak2] {
{
let obj_local_from_weak = env.auto_local(
unwrap(obj_weak.upgrade_local(&env), &env)
.expect("object shouldn't have been GC'd yet"),
);
assert!(!obj_local_from_weak.is_null());
assert!(unwrap(
env.is_same_object(&obj_local_from_weak, &obj_local),
&env,
));
}
{
let obj_global_from_weak = unwrap(obj_weak.upgrade_global(&env), &env)
.expect("object shouldn't have been GC'd yet");
assert!(!obj_global_from_weak.is_null());
assert!(unwrap(
env.is_same_object(&obj_global_from_weak, &obj_local),
&env,
));
}
assert!(unwrap(obj_weak.is_same_object(&env, &obj_local), &env));
assert!(
!unwrap(obj_weak.is_garbage_collected(&env), &env),
"`is_garbage_collected` returned incorrect value"
);
}
assert!(unwrap(
obj_weak.is_weak_ref_to_same_object(&env, &obj_weak2),
&env,
));
drop(obj_local);
run_gc(&mut env);
for obj_weak in &[&obj_weak, &obj_weak2] {
{
let obj_local_from_weak = unwrap(obj_weak.upgrade_local(&env), &env);
assert!(
obj_local_from_weak.is_none(),
"object should have been GC'd"
);
}
{
let obj_global_from_weak = unwrap(obj_weak.upgrade_global(&env), &env);
assert!(
obj_global_from_weak.is_none(),
"object should have been GC'd"
);
}
assert!(
unwrap(obj_weak.is_garbage_collected(&env), &env),
"`is_garbage_collected` returned incorrect value"
);
}
assert!(unwrap(
obj_weak.is_weak_ref_to_same_object(&env, &obj_weak2),
&env,
));
}
}

View File

@@ -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 mut guard = attach_current_thread();
assert_eq!(jvm().threads_attached(), 1);
let val = call_java_abs(&mut 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());
}

19
vendor/jni/tests/threads_detach.rs vendored Normal file
View File

@@ -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 mut env = attach_current_thread_permanently();
let val = call_java_abs(&mut env, -2);
assert_eq!(val, 2);
assert_eq!(jvm().threads_attached(), 1);
});
thread.join().unwrap();
assert_eq!(jvm().threads_attached(), 0);
}

View File

@@ -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 mut env = attach_current_thread_as_daemon();
let val = call_java_abs(&mut env, -3);
assert_eq!(val, 3);
assert_eq!(jvm().threads_attached(), 1);
});
thread.join().unwrap();
assert_eq!(jvm().threads_attached(), 0);
}

View File

@@ -0,0 +1,21 @@
#![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 mut guard = attach_current_thread();
let val = call_java_abs(&mut guard, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// # Safety
// we won't be trying to use a pre-existing (invalid) `JNIEnv` after detaching
unsafe {
detach_current_thread();
}
assert_eq!(jvm().threads_attached(), 0);
assert!(jvm().get_env().is_err());
}

View File

@@ -0,0 +1,21 @@
#![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 mut guard = attach_current_thread_as_daemon();
let val = call_java_abs(&mut guard, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// # Safety
// we won't be trying to use a pre-existing (invalid) `JNIEnv` after detaching
unsafe {
detach_current_thread();
}
assert_eq!(jvm().threads_attached(), 0);
assert!(jvm().get_env().is_err());
}

View File

@@ -0,0 +1,21 @@
#![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 mut guard = attach_current_thread_permanently();
let val = call_java_abs(&mut guard, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// # Safety
// we won't be trying to use a pre-existing (invalid) `JNIEnv` after detaching
unsafe {
detach_current_thread();
}
assert_eq!(jvm().threads_attached(), 0);
assert!(jvm().get_env().is_err());
}

View File

@@ -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 mut env = attach_current_thread_as_daemon();
let val = call_java_abs(&mut env, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// Create nested AttachGuard.
{
let mut env_nested = attach_current_thread();
let val = call_java_abs(&mut env_nested, -2);
assert_eq!(val, 2);
assert_eq!(jvm().threads_attached(), 1);
}
// Call a Java method after nested guard has been dropped to check that
// this thread has not been detached.
let val = call_java_abs(&mut env, -3);
assert_eq!(val, 3);
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_permanently is a no-op.
{
let mut env_nested = attach_current_thread_permanently();
let val = call_java_abs(&mut env_nested, -4);
assert_eq!(val, 4);
assert_eq!(jvm().threads_attached(), 1);
}
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_as_daemon is a no-op.
{
let mut env_nested = attach_current_thread_as_daemon();
let val = call_java_abs(&mut env_nested, -5);
assert_eq!(val, 5);
assert_eq!(jvm().threads_attached(), 1);
}
assert_eq!(jvm().threads_attached(), 1);
}

View File

@@ -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 mut env = attach_current_thread();
let val = call_java_abs(&mut env, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// Create nested AttachGuard.
{
let mut env_nested = attach_current_thread();
let val = call_java_abs(&mut env_nested, -2);
assert_eq!(val, 2);
assert_eq!(jvm().threads_attached(), 1);
}
// Call a Java method after nested guard has been dropped to check that
// this thread has not been detached.
let val = call_java_abs(&mut env, -3);
assert_eq!(val, 3);
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_permanently is a no-op.
{
let mut env_nested = attach_current_thread_permanently();
let val = call_java_abs(&mut env_nested, -4);
assert_eq!(val, 4);
assert_eq!(jvm().threads_attached(), 1);
}
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_as_daemon is a no-op.
{
let mut env_nested = attach_current_thread_as_daemon();
let val = call_java_abs(&mut env_nested, -5);
assert_eq!(val, 5);
assert_eq!(jvm().threads_attached(), 1);
}
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());
}

View File

@@ -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 mut env = attach_current_thread_permanently();
let val = call_java_abs(&mut env, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// Create nested AttachGuard.
{
let mut env_nested = attach_current_thread();
let val = call_java_abs(&mut env_nested, -2);
assert_eq!(val, 2);
assert_eq!(jvm().threads_attached(), 1);
}
// Call a Java method after nested guard has been dropped to check that
// this thread has not been detached.
let val = call_java_abs(&mut env, -3);
assert_eq!(val, 3);
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_permanently is a no-op.
{
let mut env_nested = attach_current_thread_permanently();
let val = call_java_abs(&mut env_nested, -4);
assert_eq!(val, 4);
assert_eq!(jvm().threads_attached(), 1);
}
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_as_daemon is a no-op.
{
let mut env_nested = attach_current_thread_as_daemon();
let val = call_java_abs(&mut env_nested, -5);
assert_eq!(val, 5);
assert_eq!(jvm().threads_attached(), 1);
}
assert_eq!(jvm().threads_attached(), 1);
assert!(jvm().get_env().is_ok());
}

53
vendor/jni/tests/util/example_proxy.rs vendored Normal file
View File

@@ -0,0 +1,53 @@
#![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<Self> {
let obj = exec.with_attached(|env: &mut JNIEnv| {
let i = env.new_object(
"java/util/concurrent/atomic/AtomicInteger",
"(I)V",
&[JValue::from(init_value)],
)?;
env.new_global_ref(i)
})?;
Ok(AtomicIntegerProxy { exec, obj })
}
/// Gets a current value from java object
pub fn get(&mut self) -> Result<jint> {
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<jint> {
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<jint> {
let delta = JValue::from(delta);
self.exec.with_attached(|env| {
env.call_method(&self.obj, "addAndGet", "(I)I", &[delta])?
.i()
})
}
}

85
vendor/jni/tests/util/mod.rs vendored Normal file
View File

@@ -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<JavaVM> {
static mut JVM: Option<Arc<JavaVM>> = 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: &mut 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 unsafe 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<T>(res: Result<T>, env: &JNIEnv) -> T {
res.unwrap_or_else(|e| {
print_exception(env);
panic!("{:#?}", e);
})
}