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

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,
}
}
}