From 8b7df41cb87f37f725a04ceb3fffe4d9f04773ae Mon Sep 17 00:00:00 2001 From: Pickle Burger Date: Tue, 16 Apr 2024 23:48:14 -0700 Subject: [PATCH 1/7] support primitive arrays in to_rust --- rust/src/api/mod.rs | 211 ++++++++++++++++++++++++++++++++++++++++++ rust/src/cache.rs | 122 +++++++++++++++++++++++- rust/src/jni_utils.rs | 43 +++++++++ 3 files changed, 372 insertions(+), 4 deletions(-) diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index dd5485f..e5e8d37 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -70,6 +70,16 @@ pub(crate) const PRIMITIVE_LONG: &'static str = "long"; pub(crate) const PRIMITIVE_FLOAT: &'static str = "float"; pub(crate) const PRIMITIVE_DOUBLE: &'static str = "double"; pub(crate) const PRIMITIVE_CHAR: &'static str = "char"; + +pub(crate) const PRIMITIVE_BOOLEAN_ARRAY: &'static str = "[Z"; +pub(crate) const PRIMITIVE_BYTE_ARRAY: &'static str = "[B"; +pub(crate) const PRIMITIVE_SHORT_ARRAY: &'static str = "[S"; +pub(crate) const PRIMITIVE_INT_ARRAY: &'static str = "[I"; +pub(crate) const PRIMITIVE_LONG_ARRAY: &'static str = "[J"; +pub(crate) const PRIMITIVE_FLOAT_ARRAY: &'static str = "[F"; +pub(crate) const PRIMITIVE_DOUBLE_ARRAY: &'static str = "[D"; +pub(crate) const PRIMITIVE_CHAR_ARRAY: &'static str = "[C"; + pub(crate) const CLASS_NATIVE_CALLBACK_TO_RUST_CHANNEL_SUPPORT: &'static str = "org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport"; pub(crate) const CLASS_J4RS_EVENT_HANDLER: &'static str = @@ -269,6 +279,91 @@ impl Jvm { (**jni_environment).v1_6.CallStaticObjectMethod, )) }); + let _ = cache::get_jni_get_array_length().or_else(|| { + cache::set_jni_get_array_length(Some( + (**jni_environment).v1_6.GetArrayLength, + )) + }); + let _ = cache::get_jni_get_byte_array_elements().or_else(|| { + cache::set_jni_get_byte_array_elements(Some( + (**jni_environment).v1_6.GetByteArrayElements, + )) + }); + let _ = cache::get_jni_release_byte_array_elements().or_else(|| { + cache::set_jni_release_byte_array_elements(Some( + (**jni_environment).v1_6.ReleaseByteArrayElements, + )) + }); + let _ = cache::get_jni_get_short_array_elements().or_else(|| { + cache::set_jni_get_short_array_elements(Some( + (**jni_environment).v1_6.GetShortArrayElements, + )) + }); + let _ = cache::get_jni_release_short_array_elements().or_else(|| { + cache::set_jni_release_short_array_elements(Some( + (**jni_environment).v1_6.ReleaseShortArrayElements, + )) + }); + let _ = cache::get_jni_get_char_array_elements().or_else(|| { + cache::set_jni_get_char_array_elements(Some( + (**jni_environment).v1_6.GetCharArrayElements, + )) + }); + let _ = cache::get_jni_release_char_array_elements().or_else(|| { + cache::set_jni_release_char_array_elements(Some( + (**jni_environment).v1_6.ReleaseCharArrayElements, + )) + }); + let _ = cache::get_jni_get_int_array_elements().or_else(|| { + cache::set_jni_get_int_array_elements(Some( + (**jni_environment).v1_6.GetIntArrayElements, + )) + }); + let _ = cache::get_jni_release_int_array_elements().or_else(|| { + cache::set_jni_release_int_array_elements(Some( + (**jni_environment).v1_6.ReleaseIntArrayElements, + )) + }); + let _ = cache::get_jni_get_long_array_elements().or_else(|| { + cache::set_jni_get_long_array_elements(Some( + (**jni_environment).v1_6.GetLongArrayElements, + )) + }); + let _ = cache::get_jni_release_long_array_elements().or_else(|| { + cache::set_jni_release_long_array_elements(Some( + (**jni_environment).v1_6.ReleaseLongArrayElements, + )) + }); + let _ = cache::get_jni_get_float_array_elements().or_else(|| { + cache::set_jni_get_float_array_elements(Some( + (**jni_environment).v1_6.GetFloatArrayElements, + )) + }); + let _ = cache::get_jni_release_float_array_elements().or_else(|| { + cache::set_jni_release_float_array_elements(Some( + (**jni_environment).v1_6.ReleaseFloatArrayElements, + )) + }); + let _ = cache::get_jni_get_double_array_elements().or_else(|| { + cache::set_jni_get_double_array_elements(Some( + (**jni_environment).v1_6.GetDoubleArrayElements, + )) + }); + let _ = cache::get_jni_release_double_array_elements().or_else(|| { + cache::set_jni_release_double_array_elements(Some( + (**jni_environment).v1_6.ReleaseDoubleArrayElements, + )) + }); + let _ = cache::get_jni_get_boolean_array_elements().or_else(|| { + cache::set_jni_get_boolean_array_elements(Some( + (**jni_environment).v1_6.GetBooleanArrayElements, + )) + }); + let _ = cache::get_jni_release_boolean_array_elements().or_else(|| { + cache::set_jni_release_boolean_array_elements(Some( + (**jni_environment).v1_6.ReleaseBooleanArrayElements, + )) + }); let _ = cache::get_jni_new_object_array().or_else(|| { cache::set_jni_new_object_array(Some((**jni_environment).v1_6.NewObjectArray)) }); @@ -1177,6 +1272,38 @@ impl Jvm { || PRIMITIVE_DOUBLE == class_name) { rust_box_from_java_object!(jni_utils::f64_from_jobject) + } else if t_type == TypeId::of::>() + && PRIMITIVE_BYTE_ARRAY == class_name + { + rust_box_from_java_object!(jni_utils::i8_array_from_jobject) + } else if t_type == TypeId::of::>() + && PRIMITIVE_SHORT_ARRAY == class_name + { + rust_box_from_java_object!(jni_utils::i16_array_from_jobject) + } else if t_type == TypeId::of::>() + && PRIMITIVE_CHAR_ARRAY == class_name + { + rust_box_from_java_object!(jni_utils::u16_array_from_jobject) + } else if t_type == TypeId::of::>() + && PRIMITIVE_INT_ARRAY == class_name + { + rust_box_from_java_object!(jni_utils::i32_array_from_jobject) + } else if t_type == TypeId::of::>() + && PRIMITIVE_LONG_ARRAY == class_name + { + rust_box_from_java_object!(jni_utils::i64_array_from_jobject) + } else if t_type == TypeId::of::>() + && PRIMITIVE_FLOAT_ARRAY == class_name + { + rust_box_from_java_object!(jni_utils::f32_array_from_jobject) + } else if t_type == TypeId::of::>() + && PRIMITIVE_DOUBLE_ARRAY == class_name + { + rust_box_from_java_object!(jni_utils::f64_array_from_jobject) + } else if t_type == TypeId::of::>() + && PRIMITIVE_BOOLEAN_ARRAY == class_name + { + rust_box_from_java_object!(jni_utils::boolean_array_from_jobject) } else { Ok(Box::new(self.to_rust_deserialized(instance)?)) } @@ -1988,6 +2115,90 @@ mod api_unit_tests { Ok(()) } + #[test] + fn test_byte_array_to_rust() -> errors::Result<()> { + let jvm = create_tests_jvm()?; + let rust_value: Vec = vec![-3_i8, 7_i8, 8_i8]; + let ia: Vec<_> = rust_value.iter().map(|x| InvocationArg::try_from(x).unwrap().into_primitive().unwrap()).collect(); + let java_instance = jvm.create_java_array(PRIMITIVE_BYTE, &ia)?; + let rust_value_from_java: Vec = jvm.to_rust(java_instance)?; + assert_eq!(rust_value_from_java, rust_value); + + Ok(()) + } + + #[test] + fn test_short_array_to_rust() -> errors::Result<()> { + let jvm = create_tests_jvm()?; + let rust_value: Vec = vec![-3_i16, 7_i16, 10000_i16]; + let ia: Vec<_> = rust_value.iter().map(|x| InvocationArg::try_from(x).unwrap().into_primitive().unwrap()).collect(); + let java_instance = jvm.create_java_array(PRIMITIVE_SHORT, &ia)?; + let rust_value_from_java: Vec = jvm.to_rust(java_instance)?; + assert_eq!(rust_value_from_java, rust_value); + + Ok(()) + } + + #[test] + fn test_char_array_to_rust() -> errors::Result<()> { + let jvm = create_tests_jvm()?; + let rust_value: Vec = vec![3_u16, 7_u16, 10000_u16]; + let ia: Vec<_> = rust_value.iter().map(|x| InvocationArg::try_from(x).unwrap().into_primitive().unwrap()).collect(); + let java_instance = jvm.create_java_array(PRIMITIVE_CHAR, &ia)?; + let rust_value_from_java: Vec = jvm.to_rust(java_instance)?; + assert_eq!(rust_value_from_java, rust_value); + + Ok(()) + } + + #[test] + fn test_int_array_to_rust() -> errors::Result<()> { + let jvm = create_tests_jvm()?; + let rust_value: Vec = vec![-100_000, -1_000_000, 1_000_000]; + let ia: Vec<_> = rust_value.iter().map(|x| InvocationArg::try_from(x).unwrap().into_primitive().unwrap()).collect(); + let java_instance = jvm.create_java_array(PRIMITIVE_INT, &ia)?; + let rust_value_from_java: Vec = jvm.to_rust(java_instance)?; + assert_eq!(rust_value_from_java, rust_value); + + Ok(()) + } + + #[test] + fn test_long_array_to_rust() -> errors::Result<()> { + let jvm = create_tests_jvm()?; + let rust_value: Vec = vec![3, 7, 8]; + let ia: Vec<_> = rust_value.iter().map(|x| InvocationArg::try_from(x).unwrap().into_primitive().unwrap()).collect(); + let java_instance = jvm.create_java_array(PRIMITIVE_LONG, &ia)?; + let rust_value_from_java: Vec = jvm.to_rust(java_instance)?; + assert_eq!(rust_value_from_java, rust_value); + + Ok(()) + } + + #[test] + fn test_float_array_to_rust() -> errors::Result<()> { + let jvm = create_tests_jvm()?; + let rust_value: Vec = vec![3_f32, 7_f32, 8_f32]; + let ia: Vec<_> = rust_value.iter().map(|x| InvocationArg::try_from(x).unwrap().into_primitive().unwrap()).collect(); + let java_instance = jvm.create_java_array(PRIMITIVE_FLOAT, &ia)?; + let rust_value_from_java: Vec = jvm.to_rust(java_instance)?; + assert_eq!(rust_value_from_java, rust_value); + + Ok(()) + } + + #[test] + fn test_double_array_to_rust() -> errors::Result<()> { + let jvm = create_tests_jvm()?; + let rust_value: Vec = vec![3_f64, 7_f64, 8_f64]; + let ia: Vec<_> = rust_value.iter().map(|x| InvocationArg::try_from(x).unwrap().into_primitive().unwrap()).collect(); + let java_instance = jvm.create_java_array(PRIMITIVE_DOUBLE, &ia)?; + let rust_value_from_java: Vec = jvm.to_rust(java_instance)?; + assert_eq!(rust_value_from_java, rust_value); + + Ok(()) + } + #[test] fn test_int_to_rust() -> errors::Result<()> { let jvm = create_tests_jvm()?; diff --git a/rust/src/cache.rs b/rust/src/cache.rs index 173e5e9..0111d70 100644 --- a/rust/src/cache.rs +++ b/rust/src/cache.rs @@ -16,10 +16,9 @@ use std::cell::RefCell; use std::path::PathBuf; use std::sync::Mutex; -use jni_sys::{ - self, jboolean, jbyte, jclass, jdouble, jfloat, jint, jlong, jmethodID, jobject, jobjectArray, - jshort, jsize, jstring, JNIEnv, -}; +use jni_sys::{self, jarray, jboolean, jbooleanArray, jbyte, jbyteArray, jchar, jcharArray, jclass, + jdouble, jdoubleArray, jfloat, jfloatArray, jint, jintArray, jlong, jlongArray, + jmethodID, JNIEnv, jobject, jobjectArray, jshort, jshortArray, jsize, jstring}; use libc::c_char; use crate::errors::opt_to_res; @@ -83,8 +82,108 @@ pub(crate) type JniCallDoubleMethod = #[allow(non_snake_case)] pub(crate) type JniCallVoidMethod = unsafe extern "C" fn(env: *mut JNIEnv, obj: jobject, methodID: jmethodID, ...); +#[allow(non_snake_case)] pub(crate) type JniCallStaticObjectMethod = unsafe extern "C" fn(env: *mut JNIEnv, obj: jobject, methodID: jmethodID, ...) -> jobject; +pub(crate) type JniGetArrayLength = + unsafe extern "system" fn(env: *mut JNIEnv, array: jarray) -> jsize; + +macro_rules! primitive_array_definitions { + ( + $jni_get_array_elements_type:ident, + $jni_release_array_elements_type:ident, + $jni_get_array_elements_cell:ident, + $jni_release_array_elements_cell:ident, + $set_jni_get_array_elements_cell:ident, + $get_jni_get_array_elements_cell:ident, + $set_jni_release_array_elements_cell:ident, + $get_jni_release_array_elements_cell:ident, + $jarray_type:ty, + $jtype:ty + ) => { + #[allow(non_snake_case)] + pub(crate) type $jni_get_array_elements_type = + unsafe extern "system" fn(env: *mut JNIEnv, array: $jarray_type, isCopy: *mut jboolean) -> *mut $jtype; + #[allow(non_snake_case)] + pub(crate) type $jni_release_array_elements_type = + unsafe extern "system" fn(env: *mut JNIEnv, array: $jarray_type, elems: *mut $jtype, mode: jint); + + thread_local! { + pub(crate) static $jni_get_array_elements_cell: RefCell> = RefCell::new(None); + pub(crate) static $jni_release_array_elements_cell: RefCell> = RefCell::new(None); + } + + pub(crate) fn $set_jni_get_array_elements_cell( + j: Option<$jni_get_array_elements_type>, + ) -> Option<$jni_get_array_elements_type> { + debug("Called set_jni_get__array_elements"); + $jni_get_array_elements_cell.with(|opt| { + *opt.borrow_mut() = j; + }); + $get_jni_get_array_elements_cell() + } + + pub(crate) fn $get_jni_get_array_elements_cell() -> Option<$jni_get_array_elements_type> { + $jni_get_array_elements_cell.with(|opt| *opt.borrow()) + } + + pub(crate) fn $set_jni_release_array_elements_cell( + j: Option<$jni_release_array_elements_type>, + ) -> Option<$jni_release_array_elements_type> { + debug("Called set_jni_release__array_elements"); + $jni_release_array_elements_cell.with(|opt| { + *opt.borrow_mut() = j; + }); + $get_jni_release_array_elements_cell() + } + + pub(crate) fn $get_jni_release_array_elements_cell() -> Option<$jni_release_array_elements_type> { + $jni_release_array_elements_cell.with(|opt| *opt.borrow()) + } + }; +} + +primitive_array_definitions!(JniGetByteArrayElements, JniReleaseByteArrayElements, + JNI_GET_BYTE_ARRAY_ELEMENTS, JNI_RELEASE_BYTE_ARRAY_ELEMENTS, + set_jni_get_byte_array_elements, get_jni_get_byte_array_elements, + set_jni_release_byte_array_elements, get_jni_release_byte_array_elements, + jbyteArray, jbyte); +primitive_array_definitions!(JniGetShortArrayElements, JniReleaseShortArrayElements, + JNI_GET_SHORT_ARRAY_ELEMENTS, JNI_RELEASE_SHORT_ARRAY_ELEMENTS, + set_jni_get_short_array_elements, get_jni_get_short_array_elements, + set_jni_release_short_array_elements, get_jni_release_short_array_elements, + jshortArray, jshort); +primitive_array_definitions!(JniGetIntArrayElements, JniReleaseIntArrayElements, + JNI_GET_INT_ARRAY_ELEMENTS, JNI_RELEASE_INT_ARRAY_ELEMENTS, + set_jni_get_int_array_elements, get_jni_get_int_array_elements, + set_jni_release_int_array_elements, get_jni_release_int_array_elements, + jintArray, jint); +primitive_array_definitions!(JniGetLongArrayElements, JniReleaseLongArrayElements, + JNI_GET_LONG_ARRAY_ELEMENTS, JNI_RELEASE_LONG_ARRAY_ELEMENTS, + set_jni_get_long_array_elements, get_jni_get_long_array_elements, + set_jni_release_long_array_elements, get_jni_release_long_array_elements, + jlongArray, jlong); +primitive_array_definitions!(JniGetFloatArrayElements, JniReleaseFloatArrayElements, + JNI_GET_FLOAT_ARRAY_ELEMENTS, JNI_RELEASE_FLOAT_ARRAY_ELEMENTS, + set_jni_get_float_array_elements, get_jni_get_float_array_elements, + set_jni_release_float_array_elements, get_jni_release_float_array_elements, + jfloatArray, jfloat); +primitive_array_definitions!(JniGetDoubleArrayElements, JniReleaseDoubleArrayElements, + JNI_GET_DOUBLE_ARRAY_ELEMENTS, JNI_RELEASE_DOUBLE_ARRAY_ELEMENTS, + set_jni_get_double_array_elements, get_jni_get_double_array_elements, + set_jni_release_double_array_elements, get_jni_release_double_array_elements, + jdoubleArray, jdouble); +primitive_array_definitions!(JniGetCharArrayElements, JniReleaseCharArrayElements, + JNI_GET_CHAR_ARRAY_ELEMENTS, JNI_RELEASE_CHAR_ARRAY_ELEMENTS, + set_jni_get_char_array_elements, get_jni_get_char_array_elements, + set_jni_release_char_array_elements, get_jni_release_char_array_elements, + jcharArray, jchar); +primitive_array_definitions!(JniGetBooleanArrayElements, JniReleaseBooleanArrayElements, + JNI_GET_BOOLEAN_ARRAY_ELEMENTS, JNI_RELEASE_BOOLEAN_ARRAY_ELEMENTS, + set_jni_get_boolean_array_elements, get_jni_get_boolean_array_elements, + set_jni_release_boolean_array_elements, get_jni_release_boolean_array_elements, + jbooleanArray, jboolean); + pub(crate) type JniNewObjectArray = unsafe extern "system" fn( env: *mut JNIEnv, len: jsize, @@ -135,6 +234,7 @@ thread_local! { pub(crate) static JNI_CALL_DOUBLE_METHOD: RefCell> = RefCell::new(None); pub(crate) static JNI_CALL_VOID_METHOD: RefCell> = RefCell::new(None); pub(crate) static JNI_CALL_STATIC_OBJECT_METHOD: RefCell> = RefCell::new(None); + pub(crate) static JNI_GET_ARRAY_LENGTH: RefCell> = RefCell::new(None); pub(crate) static JNI_NEW_OBJECT_ARRAY: RefCell> = RefCell::new(None); pub(crate) static JNI_SET_OBJECT_ARRAY_ELEMENT: RefCell> = RefCell::new(None); pub(crate) static JNI_EXCEPTION_CHECK: RefCell> = RefCell::new(None); @@ -474,6 +574,20 @@ pub(crate) fn get_jni_call_static_object_method() -> Option, +) -> Option { + debug("Called set_jni_get_byte_array_elements"); + JNI_GET_ARRAY_LENGTH.with(|opt| { + *opt.borrow_mut() = j; + }); + get_jni_get_array_length() +} + +pub(crate) fn get_jni_get_array_length() -> Option { + JNI_GET_ARRAY_LENGTH.with(|opt| *opt.borrow()) +} + pub(crate) fn set_jni_new_object_array(j: Option) -> Option { debug("Called set_jni_new_object_array"); diff --git a/rust/src/jni_utils.rs b/rust/src/jni_utils.rs index c9246c7..8e94532 100644 --- a/rust/src/jni_utils.rs +++ b/rust/src/jni_utils.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::any::TypeId; use std::os::raw::{c_char, c_double}; use std::ptr; @@ -440,6 +441,48 @@ pub(crate) unsafe fn f64_from_jobject(obj: jobject, jni_env: *mut JNIEnv) -> err } } +macro_rules! primitive_array_from_jobject { + ($fn_name:ident, $rust_type:ty, $get_array_element:path, $release_array_element:path) => { + pub(crate) unsafe fn $fn_name(obj: jobject, jni_env: *mut JNIEnv) -> errors::Result> { + if obj.is_null() { + Err(errors::J4RsError::JniError( + format!("Attempt to create an {:?} array from null", TypeId::of::<$rust_type>()), + )) + } else { + let length = (opt_to_res(cache::get_jni_get_array_length())?)( + jni_env, + obj + ); + let bytes = (opt_to_res($get_array_element())?)( + jni_env, + obj, + ptr::null_mut() + ); + if bytes.is_null() { return Err(errors::J4RsError::JniError("get__array_elements failed".to_string())) } + let parts_mut = std::slice::from_raw_parts_mut(bytes as *mut $rust_type, length as usize); + let mut vec = Vec::with_capacity(length as usize); + vec.extend_from_slice(parts_mut); + (opt_to_res($release_array_element())?)( + jni_env, + obj, + bytes, + jni_sys::JNI_ABORT, + ); + Ok(vec) + } + } + }; +} + +primitive_array_from_jobject!(i8_array_from_jobject, i8, cache::get_jni_get_byte_array_elements, cache::get_jni_release_byte_array_elements); +primitive_array_from_jobject!(i16_array_from_jobject, i16, cache::get_jni_get_short_array_elements, cache::get_jni_release_short_array_elements); +primitive_array_from_jobject!(u16_array_from_jobject, u16, cache::get_jni_get_char_array_elements, cache::get_jni_release_char_array_elements); +primitive_array_from_jobject!(i32_array_from_jobject, i32, cache::get_jni_get_int_array_elements, cache::get_jni_release_int_array_elements); +primitive_array_from_jobject!(i64_array_from_jobject, i64, cache::get_jni_get_long_array_elements, cache::get_jni_release_long_array_elements); +primitive_array_from_jobject!(f32_array_from_jobject, f32, cache::get_jni_get_float_array_elements, cache::get_jni_release_float_array_elements); +primitive_array_from_jobject!(f64_array_from_jobject, f64, cache::get_jni_get_double_array_elements, cache::get_jni_release_double_array_elements); +primitive_array_from_jobject!(boolean_array_from_jobject, bool, cache::get_jni_get_boolean_array_elements, cache::get_jni_release_boolean_array_elements); + pub(crate) unsafe fn string_from_jobject( obj: jobject, jni_env: *mut JNIEnv, From b041d07b7b1733a007e564619fed7d4dc7fc8172 Mon Sep 17 00:00:00 2001 From: Pickle Burger Date: Wed, 17 Apr 2024 10:21:39 -0700 Subject: [PATCH 2/7] adding u16 `TryFrom`s for InvocationArg --- rust/src/api/invocation_arg.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/rust/src/api/invocation_arg.rs b/rust/src/api/invocation_arg.rs index 8aa08c6..f73cee7 100644 --- a/rust/src/api/invocation_arg.rs +++ b/rust/src/api/invocation_arg.rs @@ -446,6 +446,30 @@ impl<'a> TryFrom<&'a [i16]> for InvocationArg { } } +impl TryFrom for InvocationArg { + type Error = errors::J4RsError; + fn try_from(arg: u16) -> errors::Result { + InvocationArg::new_2( + &arg, + JavaClass::Character.into(), + cache::get_thread_local_env()?, + ) + } +} + +impl<'a> TryFrom<&'a [u16]> for InvocationArg { + type Error = errors::J4RsError; + fn try_from(vec: &'a [u16]) -> errors::Result { + let args: errors::Result> = vec + .iter() + .map(|elem| InvocationArg::try_from(elem)) + .collect(); + let res = + Jvm::do_create_java_list(cache::get_thread_local_env()?, cache::J4RS_ARRAY, &args?); + Ok(InvocationArg::from(res?)) + } +} + impl TryFrom for InvocationArg { type Error = errors::J4RsError; fn try_from(arg: i32) -> errors::Result { @@ -592,6 +616,13 @@ impl<'a> TryFrom<&'a i16> for InvocationArg { } } +impl<'a> TryFrom<&'a u16> for InvocationArg { + type Error = errors::J4RsError; + fn try_from(arg: &'a u16) -> errors::Result { + InvocationArg::new_2(arg, JavaClass::Character.into(), cache::get_thread_local_env()?) + } +} + impl<'a, 'b> TryFrom<&'a i32> for InvocationArg { type Error = errors::J4RsError; fn try_from(arg: &'a i32) -> errors::Result { From cfd90ce050675cc100ccd0cedfe3805ef75f0825 Mon Sep 17 00:00:00 2001 From: Pickle Burger Date: Wed, 17 Apr 2024 11:05:29 -0700 Subject: [PATCH 3/7] implement conversion between u16 and char --- rust/src/api/invocation_arg.rs | 9 ++++ rust/src/api/mod.rs | 22 ++++++++ rust/src/cache.rs | 97 ++++++++++++++++++++++++++++++++++ rust/src/jni_utils.rs | 28 ++++++++++ 4 files changed, 156 insertions(+) diff --git a/rust/src/api/invocation_arg.rs b/rust/src/api/invocation_arg.rs index f73cee7..176c081 100644 --- a/rust/src/api/invocation_arg.rs +++ b/rust/src/api/invocation_arg.rs @@ -105,6 +105,15 @@ impl InvocationArg { class_name: class_name.to_string(), serialized: false, }) + } else if let Some(a) = arg_any.downcast_ref::() { + Ok(InvocationArg::RustBasic { + instance: Instance::new( + jni_utils::global_jobject_from_u16(a, jni_env)?, + class_name, + )?, + class_name: class_name.to_string(), + serialized: false, + }) } else if let Some(a) = arg_any.downcast_ref::() { Ok(InvocationArg::RustBasic { instance: Instance::new( diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index e5e8d37..cd0aa97 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -259,6 +259,9 @@ impl Jvm { let _ = cache::get_jni_call_short_method().or_else(|| { cache::set_jni_call_short_method(Some((**jni_environment).v1_6.CallShortMethod)) }); + let _ = cache::get_jni_call_char_method().or_else(|| { + cache::set_jni_call_char_method(Some((**jni_environment).v1_6.CallCharMethod)) + }); let _ = cache::get_jni_call_int_method().or_else(|| { cache::set_jni_call_int_method(Some((**jni_environment).v1_6.CallIntMethod)) }); @@ -1259,6 +1262,10 @@ impl Jvm { && (JavaClass::Short.get_class_str() == class_name || PRIMITIVE_SHORT == class_name) { rust_box_from_java_object!(jni_utils::i16_from_jobject) + } else if t_type == TypeId::of::() + && (JavaClass::Character.get_class_str() == class_name || PRIMITIVE_CHAR == class_name) + { + rust_box_from_java_object!(jni_utils::u16_from_jobject) } else if t_type == TypeId::of::() && (JavaClass::Long.get_class_str() == class_name || PRIMITIVE_LONG == class_name) { @@ -2244,6 +2251,21 @@ mod api_unit_tests { Ok(()) } + #[test] + fn test_char_to_rust() -> errors::Result<()> { + let jvm = create_tests_jvm()?; + let rust_value: u16 = 3; + let ia = InvocationArg::try_from(rust_value)?.into_primitive()?; + let java_instance = jvm.create_instance(CLASS_CHARACTER, &[ia])?; + let java_primitive_instance = jvm.invoke(&java_instance, "charValue", InvocationArg::empty())?; + let rust_value_from_java: u16 = jvm.to_rust(java_instance)?; + assert_eq!(rust_value_from_java, rust_value); + let rust_value_from_java: u16 = jvm.to_rust(java_primitive_instance)?; + assert_eq!(rust_value_from_java, rust_value); + + Ok(()) + } + #[test] fn test_long_to_rust() -> errors::Result<()> { let jvm = create_tests_jvm()?; diff --git a/rust/src/cache.rs b/rust/src/cache.rs index 0111d70..e2bd300 100644 --- a/rust/src/cache.rs +++ b/rust/src/cache.rs @@ -71,6 +71,9 @@ pub(crate) type JniCallByteMethod = pub(crate) type JniCallShortMethod = unsafe extern "C" fn(_: *mut JNIEnv, _: jobject, _: jmethodID, ...) -> jshort; #[allow(non_snake_case)] +pub(crate) type JniCallCharMethod = + unsafe extern "C" fn(_: *mut JNIEnv, _: jobject, _: jmethodID, ...) -> jchar; +#[allow(non_snake_case)] pub(crate) type JniCallLongMethod = unsafe extern "C" fn(_: *mut JNIEnv, _: jobject, _: jmethodID, ...) -> jlong; #[allow(non_snake_case)] @@ -229,6 +232,7 @@ thread_local! { pub(crate) static JNI_CALL_INT_METHOD: RefCell> = RefCell::new(None); pub(crate) static JNI_CALL_BYTE_METHOD: RefCell> = RefCell::new(None); pub(crate) static JNI_CALL_SHORT_METHOD: RefCell> = RefCell::new(None); + pub(crate) static JNI_CALL_CHAR_METHOD: RefCell> = RefCell::new(None); pub(crate) static JNI_CALL_LONG_METHOD: RefCell> = RefCell::new(None); pub(crate) static JNI_CALL_FLOAT_METHOD: RefCell> = RefCell::new(None); pub(crate) static JNI_CALL_DOUBLE_METHOD: RefCell> = RefCell::new(None); @@ -306,6 +310,9 @@ thread_local! { pub(crate) static SHORT_CONSTRUCTOR_METHOD: RefCell> = RefCell::new(None); pub(crate) static SHORT_TO_SHORT_METHOD: RefCell> = RefCell::new(None); pub(crate) static SHORT_CLASS: RefCell> = RefCell::new(None); + pub(crate) static CHARACTER_CONSTRUCTOR_METHOD: RefCell> = RefCell::new(None); + pub(crate) static CHARACTER_TO_CHAR_METHOD: RefCell> = RefCell::new(None); + pub(crate) static CHARACTER_CLASS: RefCell> = RefCell::new(None); pub(crate) static BYTE_CONSTRUCTOR_METHOD: RefCell> = RefCell::new(None); pub(crate) static BYTE_TO_BYTE_METHOD: RefCell> = RefCell::new(None); pub(crate) static BYTE_CLASS: RefCell> = RefCell::new(None); @@ -504,6 +511,20 @@ pub(crate) fn get_jni_call_short_method() -> Option { JNI_CALL_SHORT_METHOD.with(|opt| *opt.borrow()) } +pub(crate) fn set_jni_call_char_method( + j: Option, +) -> Option { + debug("Called set_jni_call_char_method"); + JNI_CALL_CHAR_METHOD.with(|opt| { + *opt.borrow_mut() = j; + }); + get_jni_call_char_method() +} + +pub(crate) fn get_jni_call_char_method() -> Option { + JNI_CALL_CHAR_METHOD.with(|opt| *opt.borrow()) +} + pub(crate) fn set_jni_call_int_method(j: Option) -> Option { debug("Called set_jni_call_int_method"); JNI_CALL_INT_METHOD.with(|opt| { @@ -1763,6 +1784,82 @@ pub(crate) fn get_short_to_short_method() -> errors::Result { ) } +pub(crate) fn set_character_class(j: jclass) { + debug("Called set_character_class"); + CHARACTER_CLASS.with(|opt| { + *opt.borrow_mut() = Some(j); + }); +} + +pub(crate) fn get_character_class() -> errors::Result { + get_cached!( + CHARACTER_CLASS, + { + let env = get_thread_local_env()?; + + let c = tweaks::find_class(env, "java/lang/Character")?; + jni_utils::create_global_ref_from_local_ref(c, env)? + }, + set_character_class + ) +} + +pub(crate) fn set_character_constructor_method(j: jmethodID) { + debug("Called set_character_constructor_method"); + CHARACTER_CONSTRUCTOR_METHOD.with(|opt| { + *opt.borrow_mut() = Some(j); + }); +} + +pub(crate) fn get_character_constructor_method() -> errors::Result { + get_cached!( + CHARACTER_CONSTRUCTOR_METHOD, + { + let env = get_thread_local_env()?; + + let constructor_signature = "(C)V"; + let cstr1 = utils::to_c_string(""); + let cstr2 = utils::to_c_string(&constructor_signature); + let j = unsafe { + (opt_to_res(get_jni_get_method_id())?)(env, get_character_class()?, cstr1, cstr2) + }; + utils::drop_c_string(cstr1); + utils::drop_c_string(cstr2); + + j + }, + set_character_constructor_method + ) +} + +pub(crate) fn set_character_to_char_method(j: jmethodID) { + debug("Called set_character_to_char_method"); + CHARACTER_TO_CHAR_METHOD.with(|opt| { + *opt.borrow_mut() = Some(j); + }); +} + +pub(crate) fn get_character_to_char_method() -> errors::Result { + get_cached!( + CHARACTER_TO_CHAR_METHOD, + { + let env = get_thread_local_env()?; + + let signature = "()C"; + let cstr1 = utils::to_c_string("charValue"); + let cstr2 = utils::to_c_string(&signature); + let j = unsafe { + (opt_to_res(get_jni_get_method_id())?)(env, get_short_class()?, cstr1, cstr2) + }; + utils::drop_c_string(cstr1); + utils::drop_c_string(cstr2); + + j + }, + set_character_to_char_method + ) +} + pub(crate) fn set_byte_class(j: jclass) { debug("Called set_byte_class"); BYTE_CLASS.with(|opt| { diff --git a/rust/src/jni_utils.rs b/rust/src/jni_utils.rs index 8e94532..2d42ebf 100644 --- a/rust/src/jni_utils.rs +++ b/rust/src/jni_utils.rs @@ -314,6 +314,19 @@ pub(crate) fn global_jobject_from_i16(a: &i16, jni_env: *mut JNIEnv) -> errors:: } } +pub(crate) fn global_jobject_from_u16(a: &u16, jni_env: *mut JNIEnv) -> errors::Result { + unsafe { + let tmp = a.clone() as *const u16; + let o = (opt_to_res(cache::get_jni_new_object())?)( + jni_env, + cache::get_character_class()?, + cache::get_character_constructor_method()?, + tmp as *const u16, + ); + create_global_ref_from_local_ref(o, jni_env) + } +} + pub(crate) unsafe fn i16_from_jobject(obj: jobject, jni_env: *mut JNIEnv) -> errors::Result { if obj.is_null() { Err(errors::J4RsError::JniError( @@ -329,6 +342,21 @@ pub(crate) unsafe fn i16_from_jobject(obj: jobject, jni_env: *mut JNIEnv) -> err } } +pub(crate) unsafe fn u16_from_jobject(obj: jobject, jni_env: *mut JNIEnv) -> errors::Result { + if obj.is_null() { + Err(errors::J4RsError::JniError( + "Attempt to create an u16 from null".to_string(), + )) + } else { + let v = (opt_to_res(cache::get_jni_call_char_method())?)( + jni_env, + obj, + cache::get_character_to_char_method()?, + ); + Ok(v as u16) + } +} + pub(crate) fn global_jobject_from_i32(a: &i32, jni_env: *mut JNIEnv) -> errors::Result { unsafe { let tmp = a.clone() as *const i32; From 8ed3e887c857be6d06044be2cf875783b153b371 Mon Sep 17 00:00:00 2001 From: Pickle Burger Date: Wed, 17 Apr 2024 11:42:25 -0700 Subject: [PATCH 4/7] use stringify --- rust/src/cache.rs | 6 +++--- rust/src/jni_utils.rs | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/rust/src/cache.rs b/rust/src/cache.rs index e2bd300..6f4782d 100644 --- a/rust/src/cache.rs +++ b/rust/src/cache.rs @@ -119,7 +119,7 @@ macro_rules! primitive_array_definitions { pub(crate) fn $set_jni_get_array_elements_cell( j: Option<$jni_get_array_elements_type>, ) -> Option<$jni_get_array_elements_type> { - debug("Called set_jni_get__array_elements"); + debug(&format!("Called {}", stringify!($set_jni_get_array_elements_cell))); $jni_get_array_elements_cell.with(|opt| { *opt.borrow_mut() = j; }); @@ -133,7 +133,7 @@ macro_rules! primitive_array_definitions { pub(crate) fn $set_jni_release_array_elements_cell( j: Option<$jni_release_array_elements_type>, ) -> Option<$jni_release_array_elements_type> { - debug("Called set_jni_release__array_elements"); + debug(&format!("Called {}", stringify!($set_jni_release_array_elements_cell))); $jni_release_array_elements_cell.with(|opt| { *opt.borrow_mut() = j; }); @@ -598,7 +598,7 @@ pub(crate) fn get_jni_call_static_object_method() -> Option, ) -> Option { - debug("Called set_jni_get_byte_array_elements"); + debug("Called set_jni_get_array_length"); JNI_GET_ARRAY_LENGTH.with(|opt| { *opt.borrow_mut() = j; }); diff --git a/rust/src/jni_utils.rs b/rust/src/jni_utils.rs index 2d42ebf..fd8c76d 100644 --- a/rust/src/jni_utils.rs +++ b/rust/src/jni_utils.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::any::TypeId; use std::os::raw::{c_char, c_double}; use std::ptr; @@ -474,7 +473,7 @@ macro_rules! primitive_array_from_jobject { pub(crate) unsafe fn $fn_name(obj: jobject, jni_env: *mut JNIEnv) -> errors::Result> { if obj.is_null() { Err(errors::J4RsError::JniError( - format!("Attempt to create an {:?} array from null", TypeId::of::<$rust_type>()), + format!("Attempt to create an {} array from null", stringify!($rust_type)), )) } else { let length = (opt_to_res(cache::get_jni_get_array_length())?)( @@ -486,7 +485,7 @@ macro_rules! primitive_array_from_jobject { obj, ptr::null_mut() ); - if bytes.is_null() { return Err(errors::J4RsError::JniError("get__array_elements failed".to_string())) } + if bytes.is_null() { return Err(errors::J4RsError::JniError(format!("{} failed", stringify!($get_array_element)))) } let parts_mut = std::slice::from_raw_parts_mut(bytes as *mut $rust_type, length as usize); let mut vec = Vec::with_capacity(length as usize); vec.extend_from_slice(parts_mut); From 0b697a11eca0988a23d431e6ab55cd1ab1e0bc4b Mon Sep 17 00:00:00 2001 From: Pickle Burger Date: Wed, 17 Apr 2024 14:16:14 -0700 Subject: [PATCH 5/7] improve unit tests --- rust/src/api/mod.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index cd0aa97..0f00afd 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -2173,7 +2173,7 @@ mod api_unit_tests { #[test] fn test_long_array_to_rust() -> errors::Result<()> { let jvm = create_tests_jvm()?; - let rust_value: Vec = vec![3, 7, 8]; + let rust_value: Vec = vec![-100_000, -1_000_000, 1_000_000]; let ia: Vec<_> = rust_value.iter().map(|x| InvocationArg::try_from(x).unwrap().into_primitive().unwrap()).collect(); let java_instance = jvm.create_java_array(PRIMITIVE_LONG, &ia)?; let rust_value_from_java: Vec = jvm.to_rust(java_instance)?; @@ -2185,7 +2185,7 @@ mod api_unit_tests { #[test] fn test_float_array_to_rust() -> errors::Result<()> { let jvm = create_tests_jvm()?; - let rust_value: Vec = vec![3_f32, 7_f32, 8_f32]; + let rust_value: Vec = vec![3_f32, 7.5_f32, -1000.5_f32]; let ia: Vec<_> = rust_value.iter().map(|x| InvocationArg::try_from(x).unwrap().into_primitive().unwrap()).collect(); let java_instance = jvm.create_java_array(PRIMITIVE_FLOAT, &ia)?; let rust_value_from_java: Vec = jvm.to_rust(java_instance)?; @@ -2197,7 +2197,7 @@ mod api_unit_tests { #[test] fn test_double_array_to_rust() -> errors::Result<()> { let jvm = create_tests_jvm()?; - let rust_value: Vec = vec![3_f64, 7_f64, 8_f64]; + let rust_value: Vec = vec![3_f64, 7.5_f64, -1000.5_f64]; let ia: Vec<_> = rust_value.iter().map(|x| InvocationArg::try_from(x).unwrap().into_primitive().unwrap()).collect(); let java_instance = jvm.create_java_array(PRIMITIVE_DOUBLE, &ia)?; let rust_value_from_java: Vec = jvm.to_rust(java_instance)?; @@ -2206,6 +2206,18 @@ mod api_unit_tests { Ok(()) } + #[test] + fn test_boolean_array_to_rust() -> errors::Result<()> { + let jvm = create_tests_jvm()?; + let rust_value: Vec = vec![false, true, false]; + let ia: Vec<_> = rust_value.iter().map(|x| InvocationArg::try_from(x).unwrap().into_primitive().unwrap()).collect(); + let java_instance = jvm.create_java_array(PRIMITIVE_BOOLEAN, &ia)?; + let rust_value_from_java: Vec = jvm.to_rust(java_instance)?; + assert_eq!(rust_value_from_java, rust_value); + + Ok(()) + } + #[test] fn test_int_to_rust() -> errors::Result<()> { let jvm = create_tests_jvm()?; From 55f9c20d52722e3a9860cbd70d88ad0cdf46b463 Mon Sep 17 00:00:00 2001 From: Pickle Burger Date: Wed, 17 Apr 2024 15:25:06 -0700 Subject: [PATCH 6/7] fix unit tests --- rust/src/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/src/cache.rs b/rust/src/cache.rs index 6f4782d..635cabe 100644 --- a/rust/src/cache.rs +++ b/rust/src/cache.rs @@ -1849,7 +1849,7 @@ pub(crate) fn get_character_to_char_method() -> errors::Result { let cstr1 = utils::to_c_string("charValue"); let cstr2 = utils::to_c_string(&signature); let j = unsafe { - (opt_to_res(get_jni_get_method_id())?)(env, get_short_class()?, cstr1, cstr2) + (opt_to_res(get_jni_get_method_id())?)(env, get_character_class()?, cstr1, cstr2) }; utils::drop_c_string(cstr1); utils::drop_c_string(cstr2); From 9b50e050d1d31e3c75c425780746d5b316635069 Mon Sep 17 00:00:00 2001 From: Pickle Burger Date: Mon, 29 Apr 2024 20:34:46 -0700 Subject: [PATCH 7/7] use copy_nonoverlapping --- rust/src/jni_utils.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/rust/src/jni_utils.rs b/rust/src/jni_utils.rs index fd8c76d..61735af 100644 --- a/rust/src/jni_utils.rs +++ b/rust/src/jni_utils.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::mem; use std::os::raw::{c_char, c_double}; use std::ptr; @@ -476,23 +477,27 @@ macro_rules! primitive_array_from_jobject { format!("Attempt to create an {} array from null", stringify!($rust_type)), )) } else { + // length is at most 2^31-1, which should be smaller than the usize::MAX on a 32/64-bit host let length = (opt_to_res(cache::get_jni_get_array_length())?)( jni_env, obj - ); - let bytes = (opt_to_res($get_array_element())?)( + ) as usize; + let val_ptr = (opt_to_res($get_array_element())?)( jni_env, obj, ptr::null_mut() ); - if bytes.is_null() { return Err(errors::J4RsError::JniError(format!("{} failed", stringify!($get_array_element)))) } - let parts_mut = std::slice::from_raw_parts_mut(bytes as *mut $rust_type, length as usize); - let mut vec = Vec::with_capacity(length as usize); - vec.extend_from_slice(parts_mut); + if val_ptr.is_null() { return Err(errors::J4RsError::JniError(format!("{} failed", stringify!($get_array_element)))) } + let total_bytes = length.checked_mul(mem::size_of::<$rust_type>()).expect("array bytes overflow"); + let mut vec = Vec::<$rust_type>::with_capacity(length); + // `copy_nonoverlapping` requires that both src and dst are aligned. Since JVM does not + // enforce alignment, we copy the source buffer as bytes. + ptr::copy_nonoverlapping(val_ptr as *const u8, vec.as_mut_ptr() as *mut u8, total_bytes); + vec.set_len(length); (opt_to_res($release_array_element())?)( jni_env, obj, - bytes, + val_ptr, jni_sys::JNI_ABORT, ); Ok(vec)